From c2832a63547e6328afde25bb3b8145267eadef47 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Thu, 15 Oct 2026 20:38:14 +0300 Subject: Make the loop public and let the display drive it An application that has its own descriptors and timers needed a second loop next to yasl's, and could not see yasl's key-repeat timer, clipboard pipes or reconnect timer. Now there is one wait: the application owns a yasl_loop, and yasl_init can run the display on it. - Public yasl_loop_*: descriptors, timers and signals. Descriptor callbacks return void. Timer IDs are 64 bits and never reused; a zero delay runs in the next step. A nested step or run fails with EBUSY, a step off the loop's thread with EPERM, and a quit made before a run ends it at once. An interrupted wait keeps its deadline. Creating a loop fails when it cannot make its wake pipe. - The display drives the loop through prepare, wait and check hooks instead of wrapping its step. Wayland finishes its read before any callback runs, so loop callbacks may create surfaces and do roundtrips. Readiness (queued events, an owed REFRESH) is derived in the step, so the wait never blocks over pending work. A draw that cannot proceed for want of a buffer is no longer counted as work. - yasl_init takes struct yasl_options. With loop and on_event, each step delivers events to on_event and yasl_wait_event fails with EBUSY. EALREADY only for the same options, EBUSY for others. - yasl_surface_request_frame: a REFRESH once the window can show a new frame, for pacing animation. - DISPLAY_LOST reports whether the display reconnects. Pull waits still fail with ENOTCONN after a final loss. - Events pushed from another thread wake the loop. Co-Authored-By: Claude Opus 5.5 --- src/common/backend.h | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) (limited to 'src/common/backend.h') diff --git a/src/common/backend.h b/src/common/backend.h index b111d3e..7633609 100644 --- a/src/common/backend.h +++ b/src/common/backend.h @@ -23,11 +23,18 @@ struct yasl_private_display { /* Set by the backend while a native modal loop (macOS live resize) blocks the * event loop; refresh callbacks run only then. */ bool modal; - struct yasl_private_loop *loop; + struct yasl_loop *loop; /* Backend's copy of yasl_private_display_spec.app_id, or NULL; for desktop services. */ const char *app_id; struct yasl_private_event_state *events; struct yasl_surface *surfaces; + /* Set by the backend: a lost connection comes back (Wayland). */ + bool reconnects; + /* Set by the public layer, or NULL. pending: an event is ready that the + * queue does not hold, such as an owed REFRESH. deliver: hand queued events + * to the application during the loop step. */ + bool (*pending)(struct yasl_private_display *d); + void (*deliver)(struct yasl_private_display *d); }; struct yasl_surface { @@ -43,6 +50,11 @@ struct yasl_surface { struct yasl_private_vector2i reported_size; uint32_t reported_scale_120, reported_state; bool refresh_owed; + /* yasl_surface_request_frame waits for the frame in flight. */ + bool frame_requested; + /* Written by GL presents on a rendering thread. */ + _Atomic uint64_t presented_ns; + yasl_private_timer_id frame_timer; void (*refresh)(struct yasl_surface *); bool text_input_active; unsigned text_purpose; @@ -89,6 +101,8 @@ bool yasl_private_events_push(struct yasl_private_display *d, struct yasl_surfac const struct yasl_private_event *event); void yasl_private_surface_notify_configure(struct yasl_surface *s, struct yasl_private_geometry geometry); void yasl_private_surface_notify_refresh(struct yasl_surface *s); +/* The system showed the surface's last frame; answers a pending frame request. */ +void yasl_private_surface_notify_frame_shown(struct yasl_surface *s); void yasl_private_surface_notify_close(struct yasl_surface *s); void yasl_private_surface_notify_state(struct yasl_surface *s, uint32_t state); void yasl_private_surface_notify_drag(struct yasl_surface *s, enum yasl_private_drag_event ev, struct yasl_private_vector2i pos); @@ -132,10 +146,15 @@ struct yasl_private_backend_ops { bool (*surface_update_text_input)(struct yasl_surface *); enum yasl_private_backend type; - struct yasl_private_display *(*display_open)(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); + struct yasl_private_display *(*display_open)(struct yasl_loop *loop, const struct yasl_private_display_spec *spec); void (*display_close)(struct yasl_private_display *d); uint32_t (*display_caps)(const struct yasl_private_display *d); - int (*display_dispatch_timeout)(struct yasl_private_display *d, int timeout_ms); + /* The loop driver hooks; see struct yasl_private_loop_driver. prepare and + * check are required. wait is optional, for platforms whose events do not + * arrive on a descriptor. */ + bool (*display_prepare)(struct yasl_private_display *d); + int (*display_wait)(struct yasl_private_display *d, int timeout_ms); + void (*display_check)(struct yasl_private_display *d); void (*display_flush)(struct yasl_private_display *d); int (*display_modifiers)(const struct yasl_private_display *d); /* Optional; returns zero or less when the system reports no interval. */ @@ -161,6 +180,9 @@ struct yasl_private_backend_ops { void (*surface_set_callbacks)(struct yasl_surface *s, const struct yasl_private_window_cbs *cbs, void *ud); void (*surface_set_title)(struct yasl_surface *s, const char *title); void (*surface_schedule_redraw)(struct yasl_surface *s); + /* Optional; true while the system has not yet shown the last presented + * frame. Without it, frame requests are paced by the refresh interval. */ + bool (*surface_frame_in_flight)(const struct yasl_surface *s); bool (*surface_present)(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, const struct yasl_private_damage_list *damage); /* Optional; NULL means only YASL_PRIVATE_POINTER_NORMAL is supported. Backends that * implement RELATIVE push POINTER_MOTION with delta set and pos frozen. */ -- cgit v1.2.3