diff options
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); |
