diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-15 20:38:14 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-15 20:38:14 +0300 |
| commit | c2832a63547e6328afde25bb3b8145267eadef47 (patch) | |
| tree | bf03ace79f01df10ddaffd70bea74678ab4ca219 /src/common | |
| parent | 3d012d707d0242780cd386868ab511846dc9b0af (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/backend.c | 136 | ||||
| -rw-r--r-- | src/common/backend.h | 28 | ||||
| -rw-r--r-- | src/common/events.c | 2 | ||||
| -rw-r--r-- | src/common/loop.h | 6 | ||||
| -rw-r--r-- | src/common/private_loop.h | 225 | ||||
| -rw-r--r-- | src/common/surface.c | 122 | ||||
| -rw-r--r-- | src/common/types.h | 25 |
7 files changed, 287 insertions, 257 deletions
diff --git a/src/common/backend.c b/src/common/backend.c index ad9e218..9910e08 100644 --- a/src/common/backend.c +++ b/src/common/backend.c @@ -22,8 +22,38 @@ static const struct yasl_private_backend_ops *backend_ops(enum yasl_private_back } } +/* The display drives the loop it runs on; see struct yasl_private_loop_driver. */ +static bool driver_prepare(void *ud) +{ + struct yasl_private_display *d = ud; + bool ready = d->ops->display_prepare(d); + /* Readiness the backends cannot see: events already queued, an error to + * report, or an owed REFRESH. */ + return ready || yasl_private_events_pending(d) || yasl_private_events_error(d) || + (d->pending && d->pending(d)); +} + +static int driver_wait(void *ud, int timeout_ms) +{ + struct yasl_private_display *d = ud; + return d->ops->display_wait(d, timeout_ms); +} + +static void driver_check(void *ud) +{ + struct yasl_private_display *d = ud; + d->ops->display_check(d); +} + +static void driver_deliver(void *ud) +{ + struct yasl_private_display *d = ud; + if (d->deliver) + d->deliver(d); +} + static struct yasl_private_display *open_backend(enum yasl_private_backend backend, - struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) + struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { const struct yasl_private_backend_ops *ops = backend_ops(backend); if (!ops) { @@ -33,18 +63,26 @@ static struct yasl_private_display *open_backend(enum yasl_private_backend backe errno = 0; struct yasl_private_display *d = ops->display_open(loop, spec); if (!d && !errno) errno = ENODEV; - if (d) { - if (!yasl_private_events_open(d)) { - int error = errno; - ops->display_close(d); - errno = error; - return NULL; - } + if (!d) + return NULL; + struct yasl_private_loop_driver driver = { + .prepare = driver_prepare, + .wait = ops->display_wait ? driver_wait : NULL, + .check = driver_check, + .deliver = driver_deliver, + .ud = d, + }; + if (!yasl_private_events_open(d) || !yasl_private_loop_set_driver(d->loop, &driver)) { + int error = errno; + yasl_private_events_close(d); + ops->display_close(d); + errno = error; + return NULL; } return d; } -struct yasl_private_display *yasl_private_display_open(struct yasl_private_loop *loop, +struct yasl_private_display *yasl_private_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { enum yasl_private_backend backend = spec ? spec->backend : YASL_PRIVATE_BACKEND_AUTO; @@ -78,6 +116,7 @@ void yasl_private_display_close(struct yasl_private_display *d) { if (!d || !d->ops->display_close) return; + yasl_private_loop_set_driver(d->loop, NULL); yasl_private_events_close(d); d->ops->display_close(d); } @@ -89,27 +128,6 @@ uint32_t yasl_private_display_caps(const struct yasl_private_display *d) return d->ops->display_caps(d); } -int yasl_private_display_dispatch(struct yasl_private_display *d, int timeout_ms) -{ - if (!d) { - errno = EINVAL; - return -1; - } - if (!d->ops->display_dispatch_timeout) { - errno = ENOTSUP; - return -1; - } - int queue_error = yasl_private_events_error(d); - if (queue_error) { errno = queue_error; return -1; } - if (yasl_private_events_pending(d)) timeout_ms = 0; - errno = 0; - int result = d->ops->display_dispatch_timeout(d, timeout_ms); - queue_error = yasl_private_events_error(d); - if (queue_error) { errno = queue_error; return -1; } - if (result < 0 && !errno) errno = EIO; - return result; -} - void yasl_private_display_flush(struct yasl_private_display *d) { if (!d || !d->ops->display_flush) @@ -317,7 +335,10 @@ bool yasl_private_surface_present(struct yasl_surface *s, const struct yasl_priv }; } } - return s->display->ops->surface_present(s, fb, &clipped); + if (!s->display->ops->surface_present(s, fb, &clipped)) + return false; + atomic_store(&s->presented_ns, yasl_private_loop_now_ns()); + return true; } bool yasl_private_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap, @@ -473,10 +494,10 @@ bool yasl_private_surface_place_under_pointer(struct yasl_surface *s, struct yas void yasl_private_surface_clear_timers(struct yasl_surface *s) { if (!s || !s->timers) return; - struct yasl_private_loop *loop = s->display ? s->display->loop : NULL; + struct yasl_loop *loop = s->display ? s->display->loop : NULL; for (int i = 0; i < s->timer_count; i++) { if (loop && s->timers[i]) { - yasl_private_loop_del_timer(loop, s->timers[i]); + yasl_loop_del_timer(loop, s->timers[i]); } } free(s->timers); @@ -491,7 +512,7 @@ yasl_private_timer_id yasl_private_surface_add_timer(struct yasl_surface *s, uin if (!s || !s->display || !s->display->loop || !cb || interval_ns == 0) return 0; - yasl_private_timer_id id = yasl_private_loop_add_timer(s->display->loop, interval_ns, cb, ud); + yasl_private_timer_id id = yasl_loop_add_timer(s->display->loop, interval_ns, cb, ud); if (!id) return 0; /* A one-shot timer retires itself in the loop. Drop retired ids before @@ -507,7 +528,7 @@ yasl_private_timer_id yasl_private_surface_add_timer(struct yasl_surface *s, uin int new_cap = s->timer_capacity ? s->timer_capacity * 2 : 4; yasl_private_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt)); if (!nt) { - yasl_private_loop_del_timer(s->display->loop, id); + yasl_loop_del_timer(s->display->loop, id); return 0; } s->timers = nt; @@ -523,13 +544,13 @@ bool yasl_private_surface_del_timer(struct yasl_surface *s, yasl_private_timer_i for (int i = 0; i < s->timer_count; i++) { if (s->timers[i] == id) { s->timers[i] = s->timers[--s->timer_count]; - return s->display && yasl_private_loop_del_timer(s->display->loop, id); + return s->display && yasl_loop_del_timer(s->display->loop, id); } } return false; } -struct yasl_private_loop *yasl_private_display_loop(struct yasl_private_display *d) +struct yasl_loop *yasl_private_display_loop(struct yasl_private_display *d) { return d ? d->loop : NULL; } @@ -560,7 +581,46 @@ void yasl_private_surface_request_frame(struct yasl_surface *s) { if (!s) return; yasl_private_surface_schedule_redraw(s); - if (s->display->loop) yasl_private_loop_kick(s->display->loop); + yasl_private_loop_wake(s->display->loop); +} + +void yasl_private_surface_notify_frame_shown(struct yasl_surface *s) +{ + if (!s || !s->frame_requested) + return; + s->frame_requested = false; + s->refresh_owed = true; +} + +static uint64_t frame_interval_done(void *ud, yasl_private_timer_id id, uint64_t interval_ns) +{ + (void)id; (void)interval_ns; + yasl_private_surface_notify_frame_shown(ud); + return 0; +} + +bool yasl_private_surface_request_shown_frame(struct yasl_surface *s) +{ + s->frame_requested = true; + const struct yasl_private_backend_ops *ops = s->display->ops; + if (ops->surface_frame_in_flight) { + if (!ops->surface_frame_in_flight(s)) + yasl_private_surface_notify_frame_shown(s); + return true; + } + /* No frame signal: one frame per refresh interval since the last present. */ + if (yasl_private_loop_timer_armed(s->display->loop, s->frame_timer)) + return true; + double refresh = yasl_private_surface_timing(s).refresh_ns; + uint64_t interval = refresh >= 1000000.0 && refresh <= 1000000000.0 ? (uint64_t)refresh : 16666667; + uint64_t presented = atomic_load(&s->presented_ns); + uint64_t since = yasl_private_loop_now_ns() - presented; + if (!presented || since >= interval) { + yasl_private_surface_notify_frame_shown(s); + return true; + } + s->frame_timer = yasl_private_surface_add_timer(s, interval - since, frame_interval_done, s); + return s->frame_timer != 0; } static uint64_t pacing_tick(void *ud, yasl_private_timer_id id, uint64_t interval) 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. */ diff --git a/src/common/events.c b/src/common/events.c index b318d0c..83baf11 100644 --- a/src/common/events.c +++ b/src/common/events.c @@ -200,6 +200,8 @@ bool yasl_private_events_push(struct yasl_private_display *d, struct yasl_surfac if (!ok) d->events->error = errno; yasl_private_mutex_unlock(&d->events->mutex); if (!ok) dispose(node); + /* The owner's next step sees the queue; another thread must wake it. */ + yasl_private_loop_wake(d->loop); return ok; failed: yasl_private_mutex_lock(&d->events->mutex); diff --git a/src/common/loop.h b/src/common/loop.h index 3a1c542..437466b 100644 --- a/src/common/loop.h +++ b/src/common/loop.h @@ -5,11 +5,9 @@ struct pollfd; -int yasl_private_loop_next_timeout_ms(const struct yasl_private_loop *l); +int yasl_private_loop_next_timeout_ms(const yasl_loop *l); /* The loop's current poll set, valid until the next watch change. Backends * that wait on native events use it to wake on loop readiness. */ -int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct pollfd **fds); -/* Whether id is armed: added, not deleted, and not retired by its callback. */ -bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_private_timer_id id); +int yasl_private_loop_poll_set(const yasl_loop *l, const struct pollfd **fds); #endif /* YASL_PRIVATE_COMMON_LOOP_H */ diff --git a/src/common/private_loop.h b/src/common/private_loop.h index 432bbc3..a70997a 100644 --- a/src/common/private_loop.h +++ b/src/common/private_loop.h @@ -1,168 +1,22 @@ /** * @file - * Optional event loop, file watches, and timers. - * - * Use the loop's owning thread for all calls and callbacks, except - * yasl_private_loop_now_ns(), yasl_private_loop_kick(), and yasl_private_loop_post(), which any thread - * may call. A display borrows a supplied loop. For window applications, - * drive yasl_private_display_dispatch(), drain input, then yasl_private_display_flush(); the - * standalone loop_run() function does not replace this cycle. Timer intervals are - * nanoseconds; timeout_ms arguments are milliseconds. Do not recursively step/run - * or destroy a loop from a callback. + * The internal parts of the loop. The loop itself is public: see yasl_loop_* in + * yasl.h. Use the loop's owning thread for all calls and callbacks, except + * yasl_private_loop_now_ns(), yasl_private_loop_kick(), yasl_private_loop_wake() + * and yasl_private_loop_post(), which any thread may call. */ #ifndef YASL_PRIVATE_LOOP_H #define YASL_PRIVATE_LOOP_H #include "common/types.h" +#include "yasl/yasl.h" #ifdef __cplusplus extern "C" { #endif -/* Optional event loop. Use the display's loop when adding application timers. - * A step waits at most timeout_ms: zero polls, a negative value waits indefinitely. - * Calls and callbacks use the loop's owning thread. */ -/** - * Create an owned standalone file-descriptor and timer loop. - * @return A loop to destroy with yasl_private_loop_destroy(), or NULL on allocation failure. - * Use on one owning thread. Passing it to yasl_private_display_open() only lends it to - * the display; the caller retains ownership. - */ -struct yasl_private_loop *yasl_private_loop_create(void); -/** - * Release a loop, its watches, its timers, and its signal watch. - * @param l Loop, or NULL for no action. - * Close displays borrowing this loop first. Must not run during a loop callback - * or an active step/run. Watched file descriptors are not closed. Posted - * functions that have not run are dropped; no thread may still post or kick. - */ -void yasl_private_loop_destroy(struct yasl_private_loop *l); -/** - * Step a standalone loop until yasl_private_loop_quit() is called. - * @param l Loop. - * @return Zero after quit, or -1 on failure. - * For a window application, use a dispatch, input-poll, flush cycle instead; - * loop_run alone does not perform that full cycle on every backend. - */ -int yasl_private_loop_run(struct yasl_private_loop *l); -/** - * Wait for file-descriptor readiness or a timer deadline and run callbacks. - * @param l Loop. - * @param timeout_ms Maximum wait: zero polls, negative waits indefinitely. - * @return Number of callbacks handled, or -1 on failure. - * Timer deadlines shorten the wait. Callbacks can add/delete sources, including - * themselves. Do not recursively step/run this loop. - */ -int yasl_private_loop_step(struct yasl_private_loop *l, int timeout_ms); -/** - * Ask the currently running yasl_private_loop_run() to return after this step. - * @param l Loop, or NULL for no action. - * Call on the owning thread, usually from a callback. - */ -void yasl_private_loop_quit(struct yasl_private_loop *l); -/** - * Wake a loop that is waiting, without running any callback. - * @param l Loop, or NULL for no action. - * May be called from any thread and from a signal handler. The loop must stay - * alive until the call returns. A waiting yasl_private_loop_step(), yasl_private_loop_run(), or - * yasl_private_display_dispatch() on this loop returns early; a loop that is not - * waiting returns early from its next wait instead. Kicks coalesce. This does - * not schedule a redraw or enqueue an event; use yasl_private_loop_post() to run code - * on the owning thread. - */ -void yasl_private_loop_kick(struct yasl_private_loop *l); -/** - * Run a function on the loop's owning thread. - * @param l Loop. - * @param fn Required function, called once with ud during a later step. - * @param ud Borrowed userdata until fn runs. - * @return true if queued; false for NULL arguments, a loop without a wake - * pipe, or allocation failure. - * May be called from any thread, but not from a signal handler. Wakes the loop - * like yasl_private_loop_kick(). Functions run in posting order, during a - * yasl_private_loop_step() or display dispatch on the owning thread. They may post - * again; a function posted while posts are running waits for a later step. - * Stop posting before yasl_private_loop_destroy(), which drops functions that have - * not run. - */ -bool yasl_private_loop_post(struct yasl_private_loop *l, void (*fn)(void *ud), void *ud); -/** - * Deliver POSIX signals to a callback on the loop's owning thread. - * @param l Loop. - * @param signals Signal numbers to watch; the array is read during the call. - * @param n Number of entries, at least one. - * @param cb Required callback, given ud and one signal number per delivery. - * @param ud Borrowed userdata until the watch is removed. - * @return true if every signal is watched; false with errno set and no - * dispositions changed. EINVAL means a NULL or invalid argument, including - * SIGKILL and SIGSTOP. EBUSY means another loop watches one of the signals. - * Replaces this loop's previous watch, if any. Installs one process-wide - * handler per signal with SA_RESTART and an empty handler mask, and saves the - * previous action. The handler only records the signal and wakes the loop, - * so it may run on any thread. YASL does not change any thread's signal mask: - * a signal blocked in every thread stays pending and is not delivered. Caught - * signals reset to their default action across exec, so spawned programs - * do not inherit the handler. Repeated deliveries of one signal before the - * callback runs coalesce into one callback. SIGCHLD reports stopped and - * continued children as well as exits. Callbacks run during yasl_private_loop_step() - * or display dispatch, and may remove the watch. - */ -bool yasl_private_loop_watch_signals(struct yasl_private_loop *l, const int *signals, int n, - void (*cb)(void *ud, int signo), void *ud); -/** - * Stop watching signals and restore their previous actions. - * @param l Loop, or NULL for no action. - * Does nothing if the loop watches no signals. Signals that arrived but have - * not reached the callback are dropped. yasl_private_loop_destroy() also unwatches. - */ -void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l); -/** - * Read the monotonic clock used by YASL timers and timing results. - * @return Nanoseconds from CLOCK_MONOTONIC, unrelated to wall-clock time. - * May be called from any thread. - */ -uint64_t yasl_private_loop_now_ns(void); -/** - * Get the loop used for a display's file watches and application timers. - * @param d Display, or NULL. - * @return Borrowed loop, or NULL. Do not destroy a display-owned loop yourself. - */ -struct yasl_private_loop *yasl_private_display_loop(struct yasl_private_display *d); -/** - * Register a borrowed file descriptor for poll-style readiness. - * @param l Loop. - * @param fd Open file descriptor; it remains owned by the caller. - * @param events Bitmask from <poll.h>, for example POLLIN or POLLOUT. - * @param fn Callback receiving poll revents (including errors/hangup); return value is ignored. - * @param ud Borrowed userdata until watch removal or loop destruction. - * @return Positive watch ID, or -1 on failure. - * Delete the watch before closing fd. The callback must consume readiness or - * change/remove its watch to avoid repeated immediate wakeups. - */ -int yasl_private_loop_add_fd(struct yasl_private_loop *l, int fd, short events, - int (*fn)(int fd, short revents, void *ud), void *ud); -/** - * Remove a file watch without closing its descriptor. - * @param l Loop. - * @param id Watch ID returned by yasl_private_loop_add_fd(). - * @return Zero if removed, or -1 for an invalid/missing watch. - */ -int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id); -/** - * Replace a watch's poll event mask. - * @param l Loop. - * @param id Watch ID. - * @param events New <poll.h> mask; zero disables requested readiness (errors may still arrive). - * @return Zero if updated, or -1 for an invalid/missing watch. - */ -int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events); - -/* Timers use nanoseconds. Return the next interval to repeat/reschedule, or zero - * to remove the timer. A zero ID indicates failure. Loop timers live until they - * stop, are deleted, or the loop is destroyed. Surface timers are also canceled - * when their surface is destroyed. */ -/** Timer identity scoped to its loop. Zero is invalid; do not reuse a retired ID. */ -typedef uint32_t yasl_private_timer_id; +/** Timer identity scoped to its loop. Zero is invalid; IDs are never reissued. */ +typedef uint64_t yasl_private_timer_id; /** Timer callback. interval_ns is the last scheduled delay; return the next delay or zero to stop. */ typedef uint64_t (*yasl_private_timer_cb)(void *ud, yasl_private_timer_id id, uint64_t interval_ns); @@ -170,29 +24,60 @@ typedef uint64_t (*yasl_private_timer_cb)(void *ud, yasl_private_timer_id id, ui #define YASL_PRIVATE_MS_TO_NS(ms) ((uint64_t)(ms) * 1000000ull) /** - * Schedule a timer on a loop. - * @param l Loop. - * @param interval_ns Nonzero delay in monotonic nanoseconds before first firing. - * @param cb Required callback; returns a new nonzero interval to repeat, or zero to stop. - * @param ud Borrowed userdata until the timer stops, is deleted, or the loop is destroyed. - * @return Nonzero timer ID, or zero for invalid arguments or allocation failure. - * Repeating timers coalesce missed deadlines; do not use them to count elapsed - * wall time. The callback receives the last scheduled interval, not elapsed time. + * How a display drives the loop it runs on. Each step runs prepare, the wait, + * check, deliver, then descriptor callbacks and timers. + * prepare: dispatch what the backend already holds, draw due surfaces, flush + * requests, and arm the read. True when progress is possible now, so the + * wait must not block. + * wait: replaces poll(2) for platforms whose events do not arrive on a + * descriptor; NULL uses poll. The loop reads descriptor readiness after it. + * check: runs after every wait, failed or not, before any callback; it closes + * what prepare opened. + * deliver: optional; hands queued events to the application. + */ +struct yasl_private_loop_driver { + bool (*prepare)(void *ud); + int (*wait)(void *ud, int timeout_ms); + void (*check)(void *ud); + void (*deliver)(void *ud); + void *ud; +}; +/** Install a driver, or remove it with NULL. False with EBUSY when one is set. */ +bool yasl_private_loop_set_driver(yasl_loop *l, const struct yasl_private_loop_driver *driver); +/** True on the thread that created the loop. */ +bool yasl_private_loop_owned(const yasl_loop *l); +/** True while a step runs. */ +bool yasl_private_loop_stepping(const yasl_loop *l); +/** The revents the last wait reported for watch id, until its callback runs. */ +short yasl_private_loop_revents(const yasl_loop *l, int id); + +/** Wake a waiting loop from any thread or a signal handler. Kicks coalesce. */ +void yasl_private_loop_kick(yasl_loop *l); +/** Kick, but only from another thread: the owner's next step sees its own work. */ +void yasl_private_loop_wake(yasl_loop *l); +/** + * Run fn on the loop's owning thread during a later step. Any thread, not a + * signal handler. False for NULL arguments or allocation failure. Functions run + * in posting order; one posted while posts run waits for a later step. */ -yasl_private_timer_id yasl_private_loop_add_timer(struct yasl_private_loop *l, uint64_t interval_ns, - yasl_private_timer_cb cb, void *ud); +bool yasl_private_loop_post(yasl_loop *l, void (*fn)(void *ud), void *ud); +/** CLOCK_MONOTONIC nanoseconds. Any thread. */ +uint64_t yasl_private_loop_now_ns(void); + /** - * Cancel a loop timer, including from its own callback. - * @param l Loop. - * @param id Timer ID. - * @return true if an armed timer was removed, false if invalid or already retired. + * Get the loop used for a display's file watches and application timers. + * @param d Display, or NULL. + * @return Borrowed loop, or NULL. Do not destroy a display-owned loop yourself. */ -bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id); +yasl_loop *yasl_private_display_loop(struct yasl_private_display *d); + +/** Whether id is armed: added, not deleted, and not retired by its callback. */ +bool yasl_private_loop_timer_armed(const yasl_loop *l, yasl_private_timer_id id); /** * Schedule a timer canceled automatically on surface destruction. * @param s Surface whose display loop will run the timer. - * @param interval_ns Nonzero initial delay in nanoseconds. - * @param cb Required timer callback, with the same rules as yasl_private_loop_add_timer(). + * @param interval_ns Initial delay in nanoseconds. + * @param cb Required timer callback, with the same rules as yasl_loop_add_timer(). * @param ud Borrowed userdata until the timer ends or the surface is destroyed. * @return Nonzero timer ID, or zero on failure. * The surface does not own userdata. Cancel with yasl_private_surface_del_timer(). diff --git a/src/common/surface.c b/src/common/surface.c index aeca3d8..5c29a10 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -13,9 +13,12 @@ static struct yasl_private_display *display; static pthread_t owner; static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER; static atomic_bool wake_requested; -static bool pumping; static unsigned refresh_depth; static char *delivered_text; /* Text of the last returned event; freed by the next wait. */ +/* The options of the open display; on_event set means callback mode. */ +static struct yasl_options active; +static char *active_app_id; +static bool queue_failure_reported; bool yasl_public_fail(int error) { @@ -62,20 +65,74 @@ bool yasl_public_connected(yasl_surface *s) /* Runs a private call that reports failure through errno. */ #define STATUS(call) (errno = 0, yasl_public_status((call) || yasl_public_fail(errno))) -int yasl_init(enum yasl_backend backend, const char *app_id) +static bool refresh_ready(yasl_surface *s); +static int take_event(yasl_event *out); +/* An owed REFRESH is an event the queue does not hold. */ +static bool public_pending(struct yasl_private_display *d) +{ + for (yasl_surface *s = d->surfaces; s; s = s->event_next) + if (s->refresh_owed && refresh_ready(s)) + return true; + return false; +} +/* Callback mode: runs in each loop step, after the display is read and before + * descriptor callbacks. Bounded, so a handler that keeps making events cannot + * starve the loop's other sources; the rest go in the next step, which does + * not wait while they are pending. */ +static void deliver_events(struct yasl_private_display *d) { - if (display) - return yasl_public_owner() ? yasl_public_error(EALREADY) : -errno; - if ((unsigned)backend > YASL_BACKEND_COCOA) + if (yasl_private_events_error(d) && !queue_failure_reported) { + queue_failure_reported = true; + active.on_event(active.userdata, &(yasl_event){ + .type = YASL_EVENT_DISPLAY_LOST, .action = YASL_LOST_FINAL, .time_ns = yasl_time_ns()}); + } + for (int i = 0; i < 256; i++) { + yasl_event event; + if (take_event(&event) <= 0) + break; + active.on_event(active.userdata, &event); + free(delivered_text); + delivered_text = NULL; + } +} +static bool same_options(const struct yasl_options *o) +{ + const char *app_id = o->app_id ? o->app_id : ""; + return o->backend == active.backend && o->loop == active.loop && + o->on_event == active.on_event && o->userdata == active.userdata && + !strcmp(app_id, active_app_id ? active_app_id : ""); +} +int yasl_init(const struct yasl_options *options) +{ + struct yasl_options o = options ? *options : (struct yasl_options){0}; + if (display) { + if (!yasl_public_owner()) + return -errno; + return yasl_public_error(same_options(&o) ? EALREADY : EBUSY); + } + if ((unsigned)o.backend > YASL_BACKEND_COCOA || !o.loop != !o.on_event) return yasl_public_error(EINVAL); + if (o.loop && !yasl_private_loop_owned(o.loop)) + return yasl_public_error(EPERM); + char *app_id = o.app_id ? strdup(o.app_id) : NULL; + if (o.app_id && !app_id) + return yasl_public_error(ENOMEM); struct yasl_private_display_spec spec = { - .app_id = app_id, - .backend = (enum yasl_private_backend)backend, + .app_id = o.app_id, + .backend = (enum yasl_private_backend)o.backend, }; errno = 0; - struct yasl_private_display *d = yasl_private_display_open(NULL, &spec); - if (!d) + struct yasl_private_display *d = yasl_private_display_open(o.loop, &spec); + if (!d) { + free(app_id); return yasl_public_error(errno); + } + d->pending = public_pending; + d->deliver = o.on_event ? deliver_events : NULL; + active = o; + active.app_id = NULL; + active_app_id = app_id; + queue_failure_reported = false; pthread_mutex_lock(&lifecycle_mutex); owner = pthread_self(); display = d; @@ -84,7 +141,7 @@ int yasl_init(enum yasl_backend backend, const char *app_id) } bool yasl_public_initialize(void) { - return display ? yasl_public_owner() : yasl_init(YASL_BACKEND_AUTO, NULL) == 0; + return display ? yasl_public_owner() : yasl_init(NULL) == 0; } static void close_display(void) { @@ -98,7 +155,7 @@ int yasl_shutdown(void) { if (display && !yasl_public_owner()) return -errno; - if (pumping || refresh_depth) + if (refresh_depth || (display && yasl_private_loop_stepping(display->loop))) return yasl_public_error(EBUSY); if (display) { for (yasl_surface *s = display->surfaces; s; s = s->event_next) @@ -110,6 +167,9 @@ int yasl_shutdown(void) } free(delivered_text); delivered_text = NULL; + free(active_app_id); + active_app_id = NULL; + active = (struct yasl_options){0}; atomic_store(&wake_requested, false); #if YASL_HAS_VULKAN extern void yasl_private_vulkan_reset(void); @@ -260,7 +320,7 @@ yasl_surface *yasl_surface_create(const struct yasl_surface_options *o) { if (!o || (unsigned)o->type > YASL_SURFACE_NATIVE || !size_valid(o->width, o->height)) return yasl_public_fail(EINVAL), NULL; - if (pumping || refresh_depth) + if (refresh_depth) return yasl_public_fail(EBUSY), NULL; unsigned flags = (o->type == YASL_SURFACE_CPU ? YASL_PRIVATE_SURFACE_CPU : 0u) | (o->undecorated ? YASL_PRIVATE_SURFACE_UNDECORATED : 0u) | @@ -387,6 +447,12 @@ int yasl_surface_request_size(yasl_surface *s, int w, int h) return yasl_public_error(EINVAL); return STATUS(yasl_private_surface_set_size(s, (struct yasl_private_vector2i){w, h})); } +int yasl_surface_request_frame(yasl_surface *s) +{ + if (!yasl_public_connected(s)) + return -errno; + return STATUS(yasl_private_surface_request_shown_frame(s)); +} int yasl_surface_set_cursor(yasl_surface *s, enum yasl_cursor cursor) { if (!yasl_public_surface(s, false)) @@ -409,7 +475,8 @@ void yasl_private_surface_notify_refresh(yasl_surface *s) { if (!s || !s->display) return; - if (s->refresh && s->display->modal && pumping && !refresh_depth) { + /* modal holds only inside the system's own loop, within the wait. */ + if (s->refresh && s->display->modal && !refresh_depth) { ++refresh_depth; s->refresh(s); --refresh_depth; @@ -703,6 +770,7 @@ static bool convert_event(const struct yasl_private_event *p, yasl_event *e) return true; case YASL_PRIVATE_EVENT_DISPLAY_LOST: e->type = YASL_EVENT_DISPLAY_LOST; + e->action = display->reconnects ? YASL_LOST_RECONNECTING : YASL_LOST_FINAL; return true; case YASL_PRIVATE_EVENT_DISPLAY_RESTORED: e->type = YASL_EVENT_DISPLAY_RESTORED; @@ -869,11 +937,14 @@ static int pump(yasl_event *out, int timeout) int n = take_event(out); if (n) return n; - yasl_private_display_flush(display); - if ((n = take_event(out))) - return n; + int error = yasl_private_events_error(display); + if (error) + return yasl_public_error(error); if (atomic_exchange(&wake_requested, false)) return 0; + /* A loss that does not reconnect ends the waits once its event is out. */ + if (atomic_load(&display->offline) && !display->reconnects) + return yasl_public_error(ENOTCONN); int wait = timeout; if (timeout > 0) { uint64_t now = yasl_time_ns(); @@ -881,11 +952,11 @@ static int pump(yasl_event *out, int timeout) return 0; wait = (int)((deadline - now + 999999u) / 1000000u); } - if (yasl_private_display_dispatch(display, wait) < 0) - return yasl_public_error(errno); - yasl_private_display_flush(display); - if ((n = take_event(out)) || timeout == 0) - return n; + int stepped = yasl_loop_step(display->loop, wait); + if (stepped < 0) + return yasl_public_error(-stepped); + if (timeout == 0) + return take_event(out); } } int yasl_wait_event(yasl_event *out, int timeout) @@ -895,13 +966,11 @@ int yasl_wait_event(yasl_event *out, int timeout) *out = (yasl_event){.type = YASL_EVENT_NONE}; if (!yasl_public_owner()) return -errno; - if (pumping || refresh_depth) + if (active.on_event || refresh_depth || yasl_private_loop_stepping(display->loop)) return yasl_public_error(EBUSY); free(delivered_text); delivered_text = NULL; - pumping = true; int n = pump(out, timeout); - pumping = false; if (n < 0) *out = (yasl_event){.type = YASL_EVENT_NONE}; return n; @@ -1014,5 +1083,8 @@ int yasl_gl_present(yasl_surface *s) return -errno; if (!display->ops->gl_swap_buffers) return yasl_public_error(ENOTSUP); - return STATUS(display->ops->gl_swap_buffers(display, s)); + int status = STATUS(display->ops->gl_swap_buffers(display, s)); + if (status == 0) + atomic_store(&s->presented_ns, yasl_private_loop_now_ns()); + return status; } diff --git a/src/common/types.h b/src/common/types.h index 3fffd47..8d00d0b 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -4,8 +4,9 @@ * * All calls and callbacks use the display's opening thread (the main thread on * Cocoa), unless explicitly documented otherwise. Userdata is borrowed; callback - * tables and creation specs are copied. Do not recursively dispatch/flush or - * destroy a display/loop while dispatch or a callback is active. Destroy child + * tables and creation specs are copied. The display drives its loop: each + * yasl_loop_step dispatches it. Do not recursively step or flush, or destroy a + * display/loop while a step or a callback is active. Destroy child * popups before parents, surfaces before displays, displays before borrowed loops. * Surface destruction from its own callback is supported; stop using it immediately. * @@ -43,7 +44,7 @@ extern "C" { struct yasl_private_display; struct yasl_surface; -struct yasl_private_loop; +struct yasl_loop; /* Geometry */ /** A two-dimensional integer size, position, or offset; units depend on the caller. */ @@ -569,7 +570,7 @@ struct yasl_private_display_spec { * opening fails without a more specific platform error. Explicit choices never * fall back. Close with yasl_private_display_close() after destroying its surfaces. */ -struct yasl_private_display *yasl_private_display_open(struct yasl_private_loop *loop, +struct yasl_private_display *yasl_private_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec); /** * Read the selected backend. @@ -592,19 +593,6 @@ void yasl_private_display_close(struct yasl_private_display *d); * permission to perform an operation at any arbitrary moment. */ uint32_t yasl_private_display_caps(const struct yasl_private_display *d); -/* Process events, waiting at most timeout_ms: zero polls, negative waits. - * Pending input or redraw work returns immediately. Returns -1 on failure. */ -/** - * Receive events and run ready loop sources. - * @param d Display to dispatch. - * @param timeout_ms Maximum wait in milliseconds: zero polls, negative waits indefinitely. - * @return A nonnegative backend-specific work count on success, or -1 with errno - * set on failure (EINVAL for NULL, EIO if no specific backend error is available). - * Do not interpret the count as the number of queued input events. Pending input - * or runnable redraw work prevents waiting. Drain input and call - * yasl_private_display_flush() after updating application state. Do not dispatch recursively. - */ -int yasl_private_display_dispatch(struct yasl_private_display *d, int timeout_ms); /** * Draw scheduled surfaces and send pending display requests. * @param d Display, or NULL for no action. @@ -886,6 +874,9 @@ bool yasl_private_frame_present(struct yasl_private_frame *frame, const struct yasl_private_rect2i *rects, size_t count); /** Coalesce a redraw request; use on the display thread. NULL is ignored. */ void yasl_private_surface_request_frame(struct yasl_surface *s); +/** Owe s a REFRESH once the system shows its frame in flight, or at once when + * none is. False with errno when the fallback timer cannot start. */ +bool yasl_private_surface_request_shown_frame(struct yasl_surface *s); /** Set drawing policy for either loop style. Returns false with errno on failure. */ bool yasl_private_surface_set_pacing(struct yasl_surface *s, yasl_private_pacing_mode mode); |
