diff options
Diffstat (limited to 'src')
| -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 | ||||
| -rw-r--r-- | src/platform/cocoa/api.h | 5 | ||||
| -rw-r--r-- | src/platform/cocoa/window.m | 79 | ||||
| -rw-r--r-- | src/platform/posix/loop.c | 646 | ||||
| -rw-r--r-- | src/platform/wayland/api.h | 5 | ||||
| -rw-r--r-- | src/platform/wayland/backend.c | 20 | ||||
| -rw-r--r-- | src/platform/wayland/buffer.c | 8 | ||||
| -rw-r--r-- | src/platform/wayland/clipboard.c | 25 | ||||
| -rw-r--r-- | src/platform/wayland/clipboard.h | 2 | ||||
| -rw-r--r-- | src/platform/wayland/internal.h | 4 | ||||
| -rw-r--r-- | src/platform/wayland/keyboard.c | 9 | ||||
| -rw-r--r-- | src/platform/wayland/pointer.c | 2 | ||||
| -rw-r--r-- | src/platform/wayland/surface.c | 147 | ||||
| -rw-r--r-- | src/platform/x11/window.c | 117 |
20 files changed, 806 insertions, 807 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); diff --git a/src/platform/cocoa/api.h b/src/platform/cocoa/api.h index 006954c..0660ffd 100644 --- a/src/platform/cocoa/api.h +++ b/src/platform/cocoa/api.h @@ -6,10 +6,11 @@ struct cocoa_display; struct cocoa_surface; -struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); +struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec); void cocoa_display_close(struct cocoa_display *d); uint32_t cocoa_display_caps(const struct cocoa_display *d); -int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms); +bool cocoa_display_prepare(struct cocoa_display *d); +int cocoa_display_wait(struct cocoa_display *d, int timeout_ms); void cocoa_display_flush(struct cocoa_display *d); int cocoa_display_modifiers(const struct cocoa_display *d); bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *origin, const struct yasl_private_drag_image *image, const struct yasl_private_drag_listener *l, void *ud); diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m index a78c2ce..c5022dd 100644 --- a/src/platform/cocoa/window.m +++ b/src/platform/cocoa/window.m @@ -129,7 +129,7 @@ struct cocoa_display { int count; int capacity; char *app_id; - struct yasl_private_loop *loop; + struct yasl_loop *loop; id key_up_monitor; NSCursor *hidden_cursor; YaslAppDelegate *delegate; @@ -1413,10 +1413,18 @@ bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_private_fr return true; } +/* render_and_commit draws only when this holds; readiness uses it too, so a + * window that cannot draw never keeps the loop from waiting. */ +static bool draw_possible(const struct cocoa_surface *w) +{ + return w->base.presenter == YASL_PRIVATE_PRESENTER_CPU && !w->destroyed && !w->closed && + w->cbs->draw && !w->rendering && w->size.x > 0 && w->size.y > 0 && + !(w->base.state & YASL_PRIVATE_STATE_SUSPENDED); +} + static void render_and_commit(struct cocoa_surface *w) { - if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU || w->destroyed || w->closed || !w->cbs->draw || w->rendering || w->size.x <= 0 || w->size.y <= 0 || - (w->base.state & YASL_PRIVATE_STATE_SUSPENDED)) + if (!draw_possible(w)) return; size_t count = (size_t)w->size.x * (size_t)w->size.y; @@ -1482,7 +1490,7 @@ static void loop_ready(CFFileDescriptorRef fd, CFOptionFlags flags, void *info) /* A kqueue mirroring the loop's poll set, or -1. complete is false when some * descriptor could not be registered. */ -static int loop_kqueue(struct yasl_private_loop *loop, bool *complete) +static int loop_kqueue(struct yasl_loop *loop, bool *complete) { *complete = true; int kq = kqueue(); @@ -1511,30 +1519,30 @@ static bool is_loop_wake(NSEvent *ev) return ev.type == NSEventTypeApplicationDefined && ev.subtype == LOOP_WAKE_SUBTYPE; } -int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) +/* Before the wait: draw due windows. True when a window can draw now. */ +bool cocoa_display_prepare(struct cocoa_display *d) { assert([NSThread isMainThread]); - if (!d || d->pumping) - return -1; + cocoa_display_flush(d); for (int i = 0; i < d->count; i++) { struct cocoa_surface *w = d->windows[i]; - if (w->input.count || w->input.reset_pending || - (w->want_draw && (!w->link || w->frame_due || w->unpaced))) { - timeout_ms = 0; - break; - } + if (w->want_draw && (!w->link || w->frame_due || w->unpaced) && draw_possible(w)) + return true; } + return false; +} + +/* The loop's wait. The loop has already shortened timeout_ms to its next + * timer, and reads descriptor readiness after this returns. */ +int cocoa_display_wait(struct cocoa_display *d, int timeout_ms) +{ + assert([NSThread isMainThread]); + if (d->pumping) + return -EBUSY; d->pumping = true; d->depth++; int count = 0; @try { - if (d->loop) { - int timer_to = yasl_private_loop_next_timeout_ms(d->loop); - if (timer_to >= 0 && (timeout_ms < 0 || timer_to < timeout_ms)) { - timeout_ms = timer_to; - } - } - /* AppKit waits on Mach ports, not on loop descriptors. While waiting, * a run loop source on a kqueue of the loop's descriptors posts a wake * event, so kicks, posts, signals, and watches end the wait. */ @@ -1592,10 +1600,6 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) dequeue:YES]; } } - - if (d->loop) { - yasl_private_loop_step(d->loop, 0); - } } @finally { d->depth--; d->pumping = false; @@ -1632,8 +1636,7 @@ void cocoa_surface_schedule_redraw(struct cocoa_surface *w) w->frame_due = true; w->link.paused = NO; } - if (darwin_ctx.loop) - yasl_private_loop_kick(darwin_ctx.loop); + yasl_private_loop_wake(darwin_ctx.loop); } static void app_init(const char *app_name) @@ -1688,7 +1691,7 @@ static void place_window(NSWindow *window) darwin_ctx.cascade = [window cascadeTopLeftFromPoint:darwin_ctx.cascade]; } -struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { if (![NSThread isMainThread]) { errno = EPERM; @@ -1701,7 +1704,7 @@ struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const s darwin_ctx.base.native_handle = (__bridge void *)[NSApplication sharedApplication]; darwin_ctx.open = true; if (!loop) { - darwin_ctx.loop = yasl_private_loop_create(); + darwin_ctx.loop = yasl_loop_create(); darwin_ctx.owns_loop = true; } else { darwin_ctx.loop = loop; @@ -1726,7 +1729,7 @@ void cocoa_display_close(struct cocoa_display *d) d->key_up_monitor = nil; } if (d->owns_loop && d->loop) { - yasl_private_loop_destroy(d->loop); + yasl_loop_destroy(d->loop); d->loop = NULL; d->base.loop = NULL; } @@ -2538,7 +2541,7 @@ struct yasl_private_timing cocoa_surface_timing(const struct cocoa_surface *w) return timing; } -static struct yasl_private_display *impl_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +static struct yasl_private_display *impl_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct cocoa_display *result = cocoa_display_open(loop, spec); return result ? &result->base : NULL; @@ -2554,9 +2557,19 @@ static uint32_t impl_display_caps(const struct yasl_private_display *d) return cocoa_display_caps((const struct cocoa_display *)d); } -static int impl_display_dispatch_timeout(struct yasl_private_display *d, int timeout_ms) +static bool impl_display_prepare(struct yasl_private_display *d) +{ + return cocoa_display_prepare((struct cocoa_display *)d); +} + +static int impl_display_wait(struct yasl_private_display *d, int timeout_ms) +{ + return cocoa_display_wait((struct cocoa_display *)d, timeout_ms); +} + +static void impl_display_check(struct yasl_private_display *d) { - return cocoa_display_dispatch_timeout((struct cocoa_display *)d, timeout_ms); + (void)d; } static void impl_display_flush(struct yasl_private_display *d) @@ -2787,7 +2800,9 @@ const struct yasl_private_backend_ops yasl_private_cocoa_backend = { .display_open = impl_display_open, .display_close = impl_display_close, .display_caps = impl_display_caps, - .display_dispatch_timeout = impl_display_dispatch_timeout, + .display_prepare = impl_display_prepare, + .display_wait = impl_display_wait, + .display_check = impl_display_check, .display_flush = impl_display_flush, .display_modifiers = impl_display_modifiers, .display_double_click_ms = impl_display_double_click_ms, diff --git a/src/platform/posix/loop.c b/src/platform/posix/loop.c index 49132f8..6624575 100644 --- a/src/platform/posix/loop.c +++ b/src/platform/posix/loop.c @@ -6,7 +6,9 @@ #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <poll.h> +#include <pthread.h> #include <signal.h> #include <stdatomic.h> #include <stdlib.h> @@ -16,33 +18,33 @@ #define YASL_PRIVATE_LOOP_INIT_CAP 32 -typedef int (*yasl_private_loop_fd_fn)(int fd, short revents, void *ud); +typedef void (*fd_fn)(void *ud, int fd, short revents); -struct yasl_private_fd_source { - yasl_private_loop_fd_fn fn; +struct fd_source { + fd_fn fn; void *ud; int id; int fd; short events; }; -struct yasl_private_timer_source { +struct timer_source { yasl_private_timer_cb fn; void *ud; uint64_t interval_ns; uint64_t deadline_ns; - yasl_private_timer_id id; + uint64_t id; bool deleted; }; -struct yasl_private_post { +struct post { void (*fn)(void *ud); void *ud; - struct yasl_private_post *next; + struct post *next; }; /* Signals this loop watches; the previous actions are restored on unwatch. */ -struct yasl_private_signal_watch { +struct signal_watch { void (*cb)(void *ud, int signo); void *ud; int count; @@ -50,28 +52,34 @@ struct yasl_private_signal_watch { struct sigaction old[NSIG]; }; -struct yasl_private_loop { - struct yasl_private_fd_source *fds; +struct yasl_loop { + struct fd_source *fds; struct pollfd *pollfds; int count; int capacity; int next_id; - bool running; + pthread_t owner; + /* Set by yasl_loop_quit; run clears it when it returns. */ + bool quit; + bool stepping; int wake_rfd; int wake_wfd; int wake_watch; - struct yasl_private_timer_source *timers; + struct timer_source *timers; int timer_count; int timer_capacity; - uint32_t next_timer_id; + uint64_t next_timer_id; int timer_firing_depth; yasl_private_mutex post_mutex; - struct yasl_private_post *post_head; - struct yasl_private_post *post_tail; + struct post *post_head; + struct post *post_tail; - struct yasl_private_signal_watch *signals; + struct signal_watch *signals; + + struct yasl_private_loop_driver driver; + bool has_driver; }; /* Handlers run on any thread, so they only touch lock-free atomics: they mark @@ -79,7 +87,7 @@ struct yasl_private_loop { * stored as fd + 1 so that the zero initializer means no pipe. */ static atomic_int signal_wake_fd[NSIG]; static atomic_bool signal_pending[NSIG]; -static struct yasl_private_loop *signal_owner[NSIG]; +static struct yasl_loop *signal_owner[NSIG]; uint64_t yasl_private_loop_now_ns(void) { @@ -88,25 +96,25 @@ uint64_t yasl_private_loop_now_ns(void) return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; } -static void run_posts(struct yasl_private_loop *l) +static void run_posts(struct yasl_loop *l) { yasl_private_mutex_lock(&l->post_mutex); - struct yasl_private_post *p = l->post_head; + struct post *p = l->post_head; l->post_head = l->post_tail = NULL; yasl_private_mutex_unlock(&l->post_mutex); while (p) { - struct yasl_private_post *next = p->next; + struct post *next = p->next; p->fn(p->ud); free(p); p = next; } } -static void run_signals(struct yasl_private_loop *l) +static void run_signals(struct yasl_loop *l) { /* A callback may unwatch, so re-check the watch before each delivery. */ for (int signo = 1; signo < NSIG; signo++) { - struct yasl_private_signal_watch *w = l->signals; + struct signal_watch *w = l->signals; if (!w) return; if (signal_owner[signo] != l || !atomic_exchange(&signal_pending[signo], false)) continue; @@ -114,148 +122,143 @@ static void run_signals(struct yasl_private_loop *l) } } -static int loop_wake_cb(int fd, short revents, void *ud) +static void loop_wake_cb(void *ud, int fd, short revents) { (void)revents; char buf[128]; ssize_t n; while ((n = read(fd, buf, sizeof(buf))) > 0 || (n < 0 && errno == EINTR)) {} - struct yasl_private_loop *l = ud; + struct yasl_loop *l = ud; run_posts(l); run_signals(l); - return 0; } -struct yasl_private_loop *yasl_private_loop_create(void) +static bool open_wake_pipe(int p[2]) { - struct yasl_private_loop *l = calloc(1, sizeof(*l)); - if (!l) return NULL; +#if defined(__linux__) && defined(O_CLOEXEC) + return pipe2(p, O_CLOEXEC | O_NONBLOCK) == 0; +#else + if (pipe(p) != 0) return false; + for (int i = 0; i < 2; i++) { + int flags = fcntl(p[i], F_GETFL, 0); + if (flags < 0 || fcntl(p[i], F_SETFL, flags | O_NONBLOCK) < 0 || + fcntl(p[i], F_SETFD, FD_CLOEXEC) < 0) { + int err = errno; + close(p[0]); + close(p[1]); + errno = err; + return false; + } + } + return true; +#endif +} +yasl_loop *yasl_loop_create(void) +{ + struct yasl_loop *l = calloc(1, sizeof(*l)); + if (!l) return NULL; l->capacity = YASL_PRIVATE_LOOP_INIT_CAP; l->fds = malloc((size_t)l->capacity * sizeof(*l->fds)); l->pollfds = malloc((size_t)l->capacity * sizeof(*l->pollfds)); - if (!l->fds || !l->pollfds) { - free(l->fds); - free(l->pollfds); - free(l); - return NULL; + int p[2]; + int err = 0; + if (!l->fds || !l->pollfds) + err = ENOMEM; + else if ((err = yasl_private_mutex_init(&l->post_mutex)) == 0 && !open_wake_pipe(p)) { + err = errno; + yasl_private_mutex_destroy(&l->post_mutex); } - - if (yasl_private_mutex_init(&l->post_mutex) != 0) { + if (err) { free(l->fds); free(l->pollfds); free(l); + errno = err; return NULL; } - + l->owner = pthread_self(); l->next_id = 1; l->next_timer_id = 1; - l->wake_rfd = -1; - l->wake_wfd = -1; - - int p[2]; -#if defined(__linux__) && defined(O_CLOEXEC) - if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == 0) { - l->wake_rfd = p[0]; - l->wake_wfd = p[1]; - } -#else - if (pipe(p) == 0) { - int f0 = fcntl(p[0], F_GETFL, 0); - int f1 = fcntl(p[1], F_GETFL, 0); - if (f0 >= 0) fcntl(p[0], F_SETFL, f0 | O_NONBLOCK); - if (f1 >= 0) fcntl(p[1], F_SETFL, f1 | O_NONBLOCK); - fcntl(p[0], F_SETFD, FD_CLOEXEC); - fcntl(p[1], F_SETFD, FD_CLOEXEC); - l->wake_rfd = p[0]; - l->wake_wfd = p[1]; - } -#endif - - if (l->wake_rfd >= 0) { - l->wake_watch = yasl_private_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); - } + l->wake_rfd = p[0]; + l->wake_wfd = p[1]; + /* The first watch fits the initial capacity, so it cannot fail. */ + l->wake_watch = yasl_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); return l; } -void yasl_private_loop_destroy(struct yasl_private_loop *l) +int yasl_loop_destroy(yasl_loop *l) { - if (!l) return; - yasl_private_loop_unwatch_signals(l); - for (struct yasl_private_post *p = l->post_head; p;) { - struct yasl_private_post *next = p->next; + if (!l) return 0; + if (l->stepping || l->has_driver) return -EBUSY; + yasl_loop_unwatch_signals(l); + for (struct post *p = l->post_head; p;) { + struct post *next = p->next; free(p); p = next; } yasl_private_mutex_destroy(&l->post_mutex); - if (l->wake_watch > 0) - yasl_private_loop_del_fd(l, l->wake_watch); - if (l->wake_rfd >= 0) - close(l->wake_rfd); - if (l->wake_wfd >= 0) - close(l->wake_wfd); + close(l->wake_rfd); + close(l->wake_wfd); free(l->fds); free(l->pollfds); free(l->timers); free(l); + return 0; } -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) +int yasl_loop_add_fd(yasl_loop *l, int fd, short events, + void (*fn)(void *ud, int fd, short revents), void *ud) { - if (!l || fd < 0) - return -1; - + if (!l || fd < 0 || !fn) return -EINVAL; if (l->count >= l->capacity) { - int new_cap = l->capacity ? l->capacity * 2 : YASL_PRIVATE_LOOP_INIT_CAP; - struct yasl_private_fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); - if (!new_fds) return -1; + int new_cap = l->capacity * 2; + struct fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); + if (!new_fds) return -ENOMEM; l->fds = new_fds; - struct pollfd *new_pollfds = realloc(l->pollfds, (size_t)new_cap * sizeof(*new_pollfds)); - if (!new_pollfds) return -1; + if (!new_pollfds) return -ENOMEM; l->pollfds = new_pollfds; l->capacity = new_cap; } - int id = l->next_id++; if (l->next_id <= 0) l->next_id = 1; - - l->fds[l->count] = (struct yasl_private_fd_source){ - .fn = fn, .ud = ud, .id = id, .fd = fd, .events = events, - }; - l->pollfds[l->count] = (struct pollfd){ - .fd = fd, .events = events, .revents = 0, - }; + l->fds[l->count] = (struct fd_source){.fn = fn, .ud = ud, .id = id, .fd = fd, .events = events}; + l->pollfds[l->count] = (struct pollfd){.fd = fd, .events = events}; l->count++; return id; } -int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id) +static int fd_index(const struct yasl_loop *l, int id) { - if (!l || id <= 0) return -1; - for (int i = 0; i < l->count; i++) { - if (l->fds[i].id == id) { - l->count--; - l->fds[i] = l->fds[l->count]; - l->pollfds[i] = l->pollfds[l->count]; - return 0; - } - } + for (int i = 0; l && id > 0 && i < l->count; i++) + if (l->fds[i].id == id) + return i; return -1; } -int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events) +int yasl_loop_del_fd(yasl_loop *l, int id) { - if (!l || id <= 0) return -1; - for (int i = 0; i < l->count; i++) { - if (l->fds[i].id == id) { - l->fds[i].events = events; - l->pollfds[i].events = events; - return 0; - } - } - return -1; + int i = fd_index(l, id); + if (i < 0) return -ENOENT; + l->count--; + l->fds[i] = l->fds[l->count]; + l->pollfds[i] = l->pollfds[l->count]; + return 0; +} + +int yasl_loop_set_fd_events(yasl_loop *l, int id, short events) +{ + int i = fd_index(l, id); + if (i < 0) return -ENOENT; + l->fds[i].events = events; + l->pollfds[i].events = events; + return 0; +} + +short yasl_private_loop_revents(const yasl_loop *l, int id) +{ + int i = fd_index(l, id); + return i < 0 ? 0 : l->pollfds[i].revents; } static void wake_write(int fd) @@ -270,18 +273,23 @@ static void wake_write(int fd) errno = saved; } -void yasl_private_loop_kick(struct yasl_private_loop *l) +void yasl_private_loop_kick(yasl_loop *l) { - if (!l || l->wake_wfd < 0) return; - wake_write(l->wake_wfd); + if (l) wake_write(l->wake_wfd); } -bool yasl_private_loop_post(struct yasl_private_loop *l, void (*fn)(void *ud), void *ud) +void yasl_private_loop_wake(yasl_loop *l) { - if (!l || !fn || l->wake_wfd < 0) return false; - struct yasl_private_post *p = malloc(sizeof(*p)); + if (l && !pthread_equal(l->owner, pthread_self())) + wake_write(l->wake_wfd); +} + +bool yasl_private_loop_post(yasl_loop *l, void (*fn)(void *ud), void *ud) +{ + if (!l || !fn) return false; + struct post *p = malloc(sizeof(*p)); if (!p) return false; - *p = (struct yasl_private_post){.fn = fn, .ud = ud}; + *p = (struct post){.fn = fn, .ud = ud}; yasl_private_mutex_lock(&l->post_mutex); if (l->post_tail) l->post_tail->next = p; @@ -305,85 +313,49 @@ static bool signal_valid(int signo) return signo > 0 && signo < NSIG && signo != SIGKILL && signo != SIGSTOP; } -bool yasl_private_loop_watch_signals(struct yasl_private_loop *l, const int *signals, int n, - void (*cb)(void *ud, int signo), void *ud) +int yasl_loop_watch_signals(yasl_loop *l, const int *signals, int n, + void (*cb)(void *ud, int signo), void *ud) { - if (!l || !signals || n <= 0 || !cb || l->wake_wfd < 0) { - errno = EINVAL; - return false; - } + if (!l || !signals || n <= 0 || !cb) return -EINVAL; + if (l->signals) return -EBUSY; bool wanted[NSIG] = {0}; for (int i = 0; i < n; i++) { - if (!signal_valid(signals[i])) { - errno = EINVAL; - return false; - } - if (signal_owner[signals[i]] && signal_owner[signals[i]] != l) { - errno = EBUSY; - return false; - } + if (!signal_valid(signals[i])) return -EINVAL; + if (signal_owner[signals[i]]) return -EBUSY; wanted[signals[i]] = true; } - struct yasl_private_signal_watch *w = calloc(1, sizeof(*w)); - if (!w) return false; + struct signal_watch *w = calloc(1, sizeof(*w)); + if (!w) return -ENOMEM; w->cb = cb; w->ud = ud; - - /* Keep the old watch's saved actions for signals that stay watched, so - * that unwatch restores what the application had before any watch. */ - struct yasl_private_signal_watch *old = l->signals; struct sigaction sa = {.sa_handler = signal_handler, .sa_flags = SA_RESTART}; sigemptyset(&sa.sa_mask); for (int signo = 1; signo < NSIG; signo++) { if (!wanted[signo]) continue; - int prev = -1; - for (int i = 0; old && i < old->count; i++) - if (old->signals[i] == signo) prev = i; - if (prev >= 0) { - w->old[w->count] = old->old[prev]; - } else { - atomic_store(&signal_pending[signo], false); - atomic_store(&signal_wake_fd[signo], l->wake_wfd + 1); - if (sigaction(signo, &sa, &w->old[w->count]) != 0) { - int err = errno; - atomic_store(&signal_wake_fd[signo], 0); - for (int i = 0; i < w->count; i++) { - int done = w->signals[i]; - bool kept = false; - for (int j = 0; old && j < old->count; j++) - if (old->signals[j] == done) kept = true; - if (kept) continue; - sigaction(done, &w->old[i], NULL); - atomic_store(&signal_wake_fd[done], 0); - } - free(w); - errno = err; - return false; + atomic_store(&signal_pending[signo], false); + atomic_store(&signal_wake_fd[signo], l->wake_wfd + 1); + if (sigaction(signo, &sa, &w->old[w->count]) != 0) { + int err = errno; + atomic_store(&signal_wake_fd[signo], 0); + for (int i = 0; i < w->count; i++) { + sigaction(w->signals[i], &w->old[i], NULL); + atomic_store(&signal_wake_fd[w->signals[i]], 0); } + free(w); + return -err; } w->signals[w->count++] = signo; } - - /* Restore signals the old watch had that the new one drops. */ - for (int i = 0; old && i < old->count; i++) { - int signo = old->signals[i]; - if (wanted[signo]) continue; - sigaction(signo, &old->old[i], NULL); - atomic_store(&signal_wake_fd[signo], 0); - atomic_store(&signal_pending[signo], false); - signal_owner[signo] = NULL; - } for (int i = 0; i < w->count; i++) signal_owner[w->signals[i]] = l; - free(old); l->signals = w; - return true; + return 0; } -void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l) +void yasl_loop_unwatch_signals(yasl_loop *l) { if (!l || !l->signals) return; - struct yasl_private_signal_watch *w = l->signals; + struct signal_watch *w = l->signals; l->signals = NULL; for (int i = 0; i < w->count; i++) { int signo = w->signals[i]; @@ -395,19 +367,19 @@ void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l) free(w); } -static void timer_cleanup_deleted(struct yasl_private_loop *l) +static void timer_cleanup_deleted(struct yasl_loop *l) { - if (!l || l->timer_firing_depth > 0) return; + if (l->timer_firing_depth > 0) return; int dest = 0; for (int src = 0; src < l->timer_count; src++) { - struct yasl_private_timer_source t = l->timers[src]; + struct timer_source t = l->timers[src]; l->timers[dest] = t; dest += !t.deleted; } l->timer_count = dest; } -bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_private_timer_id id) +bool yasl_private_loop_timer_armed(const yasl_loop *l, yasl_private_timer_id id) { if (!l || id == 0) return false; for (int i = 0; i < l->timer_count; i++) @@ -416,7 +388,7 @@ bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_priva return false; } -int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct pollfd **fds) +int yasl_private_loop_poll_set(const yasl_loop *l, const struct pollfd **fds) { if (!l) { *fds = NULL; @@ -426,215 +398,213 @@ int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct p return l->count; } -int yasl_private_loop_next_timeout_ms(const struct yasl_private_loop *l) +int yasl_private_loop_next_timeout_ms(const yasl_loop *l) { - if (!l || l->timer_count <= 0) return -1; + if (!l) return -1; uint64_t now = yasl_private_loop_now_ns(); uint64_t min_deadline = UINT64_MAX; - bool has_active = false; - for (int i = 0; i < l->timer_count; i++) { - if (!l->timers[i].deleted) { - has_active = true; - if (l->timers[i].deadline_ns < min_deadline) - min_deadline = l->timers[i].deadline_ns; - } - } - if (!has_active) return -1; + for (int i = 0; i < l->timer_count; i++) + if (!l->timers[i].deleted && l->timers[i].deadline_ns < min_deadline) + min_deadline = l->timers[i].deadline_ns; + if (min_deadline == UINT64_MAX) return -1; if (min_deadline <= now) return 0; - uint64_t diff = min_deadline - now; - uint64_t ms = (diff + 999999ull) / 1000000ull; - return (ms > (uint64_t)INT_MAX) ? INT_MAX : (int)ms; + uint64_t ms = (min_deadline - now + 999999ull) / 1000000ull; + return ms > (uint64_t)INT_MAX ? INT_MAX : (int)ms; } -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) +uint64_t yasl_loop_add_timer(yasl_loop *l, uint64_t interval_ns, + yasl_private_timer_cb cb, void *ud) { - if (!l || !cb || interval_ns == 0) return 0; - + if (!l || !cb) { + errno = EINVAL; + return 0; + } if (l->timer_count >= l->timer_capacity) { int new_cap = l->timer_capacity ? l->timer_capacity * 2 : 16; - struct yasl_private_timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); - if (!new_timers) return 0; + struct timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); + if (!new_timers) { + errno = ENOMEM; + return 0; + } l->timers = new_timers; l->timer_capacity = new_cap; } - - uint32_t id = l->next_timer_id++; - if (l->next_timer_id == 0) l->next_timer_id = 1; - - uint64_t now = yasl_private_loop_now_ns(); - l->timers[l->timer_count++] = (struct yasl_private_timer_source){ + /* 64 bits never wrap in practice, so an ID never names a newer timer. */ + uint64_t id = l->next_timer_id++; + l->timers[l->timer_count++] = (struct timer_source){ .fn = cb, .ud = ud, .id = id, .interval_ns = interval_ns, - .deadline_ns = now + interval_ns, - .deleted = false, + .deadline_ns = yasl_private_loop_now_ns() + interval_ns, }; - - yasl_private_loop_kick(l); + /* Owner-thread only, and the next wait computes its timeout from the + * timers, so no wake is needed. */ return id; } -bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id) +bool yasl_loop_del_timer(yasl_loop *l, uint64_t id) { if (!l || id == 0) return false; for (int i = l->timer_count - 1; i >= 0; i--) { if (l->timers[i].id == id && !l->timers[i].deleted) { l->timers[i].deleted = true; - if (l->timer_firing_depth == 0) { - if (i == l->timer_count - 1) { - while (l->timer_count > 0 && l->timers[l->timer_count - 1].deleted) - l->timer_count--; - } else { - timer_cleanup_deleted(l); - } - } + timer_cleanup_deleted(l); return true; } } return false; } -int yasl_private_loop_step(struct yasl_private_loop *l, int timeout_ms) +bool yasl_private_loop_set_driver(yasl_loop *l, const struct yasl_private_loop_driver *driver) { - if (!l) return -1; - - int timer_to = yasl_private_loop_next_timeout_ms(l); - int effective_timeout = timeout_ms; - if (timer_to >= 0) { - if (effective_timeout < 0 || timer_to < effective_timeout) - effective_timeout = timer_to; - } - - if (l->count <= 0) { - if (effective_timeout > 0) { - struct timespec req = { - .tv_sec = effective_timeout / 1000, - .tv_nsec = (long)(effective_timeout % 1000) * 1000000L, - }; - nanosleep(&req, NULL); - } - } else { - int n; - do { - n = poll(l->pollfds, (nfds_t)l->count, effective_timeout); - } while (n < 0 && errno == EINTR); - - if (n < 0) return -1; + if (!l) return false; + if (driver && l->has_driver) { + errno = EBUSY; + return false; } + l->driver = driver ? *driver : (struct yasl_private_loop_driver){0}; + l->has_driver = driver != NULL; + return true; +} - int handled = 0; +bool yasl_private_loop_owned(const yasl_loop *l) +{ + return l && pthread_equal(l->owner, pthread_self()); +} - if (l->count > 0) { - int count = l->count; - struct yasl_private_fd_source sstack[32]; - struct pollfd pstack[32]; - struct yasl_private_fd_source *scopy = (count <= 32) ? sstack : malloc((size_t)count * sizeof(*scopy)); - struct pollfd *pcopy = (count <= 32) ? pstack : malloc((size_t)count * sizeof(*pcopy)); - if (!scopy || !pcopy) { - if (scopy && scopy != sstack) free(scopy); - if (pcopy && pcopy != pstack) free(pcopy); - return -1; - } +bool yasl_private_loop_stepping(const yasl_loop *l) +{ + return l && l->stepping; +} - memcpy(scopy, l->fds, (size_t)count * sizeof(*scopy)); - memcpy(pcopy, l->pollfds, (size_t)count * sizeof(*pcopy)); - - for (int i = 0; i < count; i++) { - if (pcopy[i].revents) { - short revents = pcopy[i].revents; - // Fast-path: check if source is still at index i before linear scan - bool valid = false; - if (i < l->count && l->fds[i].id == scopy[i].id) { - l->pollfds[i].revents = 0; - valid = true; - } else { - for (int j = 0; j < l->count; j++) { - if (l->fds[j].id == scopy[i].id) { - l->pollfds[j].revents = 0; - valid = true; - break; - } - } - } - if (valid && scopy[i].fn) { - scopy[i].fn(scopy[i].fd, revents, scopy[i].ud); - handled++; - } - } +/* poll with an EINTR retry that keeps the deadline. */ +static int poll_all(struct yasl_loop *l, int timeout_ms) +{ + uint64_t deadline = timeout_ms > 0 ? yasl_private_loop_now_ns() + YASL_PRIVATE_MS_TO_NS(timeout_ms) : 0; + for (;;) { + if (poll(l->pollfds, (nfds_t)l->count, timeout_ms) >= 0) return 0; + if (errno != EINTR) return -errno; + if (timeout_ms > 0) { + uint64_t now = yasl_private_loop_now_ns(); + timeout_ms = now >= deadline ? 0 : (int)((deadline - now + 999999ull) / 1000000ull); } + } +} +static int dispatch_fds(struct yasl_loop *l) +{ + int handled = 0; + int count = l->count; + struct fd_source sstack[32]; + struct pollfd pstack[32]; + struct fd_source *scopy = count <= 32 ? sstack : malloc((size_t)count * sizeof(*scopy)); + struct pollfd *pcopy = count <= 32 ? pstack : malloc((size_t)count * sizeof(*pcopy)); + if (!scopy || !pcopy) { if (scopy != sstack) free(scopy); if (pcopy != pstack) free(pcopy); + return -ENOMEM; + } + memcpy(scopy, l->fds, (size_t)count * sizeof(*scopy)); + memcpy(pcopy, l->pollfds, (size_t)count * sizeof(*pcopy)); + for (int i = 0; i < count; i++) { + if (!pcopy[i].revents) continue; + /* A callback may have removed or moved this watch. */ + int j = i < l->count && l->fds[i].id == scopy[i].id ? i : fd_index(l, scopy[i].id); + if (j < 0) continue; + l->pollfds[j].revents = 0; + scopy[i].fn(scopy[i].ud, scopy[i].fd, pcopy[i].revents); + handled++; } + if (scopy != sstack) free(scopy); + if (pcopy != pstack) free(pcopy); + return handled; +} - if (l->timer_count > 0) { - uint64_t now = yasl_private_loop_now_ns(); - l->timer_firing_depth++; - int initial_count = l->timer_count; - for (int i = 0; i < initial_count; i++) { - if (i >= l->timer_count) break; - if (l->timers[i].deleted || l->timers[i].deadline_ns > now) - continue; - - yasl_private_timer_id tid = l->timers[i].id; - uint64_t interval = l->timers[i].interval_ns; - void *ud = l->timers[i].ud; - struct yasl_private_timer_source timer_src = l->timers[i]; - - uint64_t next_interval = timer_src.fn(ud, tid, interval); - handled++; - - // Fast-path: timer is usually still at index i unless an earlier timer was deleted - if (i < l->timer_count && l->timers[i].id == tid) { - if (next_interval == 0 || l->timers[i].deleted) { - l->timers[i].deleted = true; - } else { - l->timers[i].interval_ns = next_interval; - if (l->timers[i].deadline_ns + next_interval > now) - l->timers[i].deadline_ns += next_interval; - else - l->timers[i].deadline_ns = now + next_interval; - } - } else { - for (int j = 0; j < l->timer_count; j++) { - if (l->timers[j].id == tid) { - if (next_interval == 0 || l->timers[j].deleted) { - l->timers[j].deleted = true; - } else { - l->timers[j].interval_ns = next_interval; - if (l->timers[j].deadline_ns + next_interval > now) - l->timers[j].deadline_ns += next_interval; - else - l->timers[j].deadline_ns = now + next_interval; - } - break; - } - } - } - } - l->timer_firing_depth--; - if (l->timer_firing_depth == 0) { - timer_cleanup_deleted(l); - } +static void reschedule(struct timer_source *t, uint64_t next, uint64_t now) +{ + if (next == 0 || t->deleted) { + t->deleted = true; + return; } + t->interval_ns = next; + t->deadline_ns = t->deadline_ns + next > now ? t->deadline_ns + next : now + next; +} +static int fire_timers(struct yasl_loop *l) +{ + int handled = 0; + uint64_t now = yasl_private_loop_now_ns(); + l->timer_firing_depth++; + /* Timers added by a callback wait for the next step. */ + int initial_count = l->timer_count; + for (int i = 0; i < initial_count && i < l->timer_count; i++) { + if (l->timers[i].deleted || l->timers[i].deadline_ns > now) + continue; + struct timer_source t = l->timers[i]; + uint64_t next = t.fn(t.ud, t.id, t.interval_ns); + handled++; + /* Compaction waits for depth zero, so index i still holds this timer. */ + reschedule(&l->timers[i], next, now); + } + l->timer_firing_depth--; + timer_cleanup_deleted(l); return handled; } -int yasl_private_loop_run(struct yasl_private_loop *l) +static int step(struct yasl_loop *l, int timeout_ms) { - if (!l) return -1; - l->running = true; - while (l->running) { - if (yasl_private_loop_step(l, -1) < 0) - return -1; + /* A driver cannot change during a step: its display cannot close then. */ + const struct yasl_private_loop_driver *d = l->has_driver ? &l->driver : NULL; + if (d && d->prepare(d->ud)) + timeout_ms = 0; + int timer_to = yasl_private_loop_next_timeout_ms(l); + if (timer_to >= 0 && (timeout_ms < 0 || timer_to < timeout_ms)) + timeout_ms = timer_to; + int r; + if (d && d->wait) { + r = d->wait(d->ud, timeout_ms); + /* The platform wait does not fill revents; read them now. */ + if (r >= 0) r = poll_all(l, 0); + } else { + r = poll_all(l, timeout_ms); } - return 0; + /* check closes what prepare opened, whatever the wait returned. */ + if (d) d->check(d->ud); + if (r < 0) return r; + if (d && d->deliver) d->deliver(d->ud); + int fds = dispatch_fds(l); + if (fds < 0) return fds; + return fds + fire_timers(l); +} + +int yasl_loop_step(yasl_loop *l, int timeout_ms) +{ + if (!l) return -EINVAL; + if (!pthread_equal(l->owner, pthread_self())) return -EPERM; + if (l->stepping) return -EBUSY; + l->stepping = true; + int r = step(l, timeout_ms); + l->stepping = false; + return r; +} + +int yasl_loop_run(yasl_loop *l) +{ + if (!l) return -EINVAL; + if (!pthread_equal(l->owner, pthread_self())) return -EPERM; + /* A nested run must not consume the outer run's quit. */ + if (l->stepping) return -EBUSY; + int r = 0; + while (!l->quit) { + r = yasl_loop_step(l, -1); + if (r < 0) break; + } + l->quit = false; + return r < 0 ? r : 0; } -void yasl_private_loop_quit(struct yasl_private_loop *l) +void yasl_loop_quit(yasl_loop *l) { - if (l) l->running = false; + if (l) l->quit = true; } diff --git a/src/platform/wayland/api.h b/src/platform/wayland/api.h index 69f61b1..1fb6d5c 100644 --- a/src/platform/wayland/api.h +++ b/src/platform/wayland/api.h @@ -6,10 +6,11 @@ struct wayland_display; struct wayland_surface; -struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); +struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec); void wayland_display_close(struct wayland_display *d); uint32_t wayland_display_caps(const struct wayland_display *d); -int wayland_display_dispatch_timeout(struct wayland_display *d, int timeout_ms); +bool wayland_display_prepare(struct wayland_display *d); +void wayland_display_check(struct wayland_display *d); void wayland_display_flush(struct wayland_display *d); int wayland_display_modifiers(const struct wayland_display *d); bool wayland_display_copy_text(struct wayland_display *d, enum yasl_private_selection sel, const char *text); diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index 3ff696c..f6207ed 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -2,7 +2,7 @@ #include "platform/freedesktop/dbus.h" #include <errno.h> -static struct yasl_private_display *impl_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +static struct yasl_private_display *impl_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct wayland_display *result = wayland_display_open(loop, spec); return result ? &result->base : NULL; @@ -18,9 +18,19 @@ static uint32_t impl_display_caps(const struct yasl_private_display *d) return wayland_display_caps((const struct wayland_display *)d); } -static int impl_display_dispatch_timeout(struct yasl_private_display *d, int timeout_ms) +static bool impl_display_prepare(struct yasl_private_display *d) { - return wayland_display_dispatch_timeout((struct wayland_display *)d, timeout_ms); + return wayland_display_prepare((struct wayland_display *)d); +} + +static void impl_display_check(struct yasl_private_display *d) +{ + wayland_display_check((struct wayland_display *)d); +} + +static bool impl_surface_frame_in_flight(const struct yasl_surface *s) +{ + return ((const struct wayland_surface *)s)->frame_cb != NULL; } static void impl_display_flush(struct yasl_private_display *d) @@ -497,7 +507,9 @@ const struct yasl_private_backend_ops yasl_private_wayland_backend = { .display_open = impl_display_open, .display_close = impl_display_close, .display_caps = impl_display_caps, - .display_dispatch_timeout = impl_display_dispatch_timeout, + .display_prepare = impl_display_prepare, + .display_check = impl_display_check, + .surface_frame_in_flight = impl_surface_frame_in_flight, .display_flush = impl_display_flush, .display_modifiers = impl_display_modifiers, .display_double_click_ms = impl_display_double_click_ms, diff --git a/src/platform/wayland/buffer.c b/src/platform/wayland/buffer.c index 7ff4bd0..4f42b60 100644 --- a/src/platform/wayland/buffer.c +++ b/src/platform/wayland/buffer.c @@ -335,6 +335,7 @@ static void frame_done(void *data, struct wl_callback *cb, uint32_t ms) w->clock.source = YASL_PRIVATE_TIMING_FRAME_CALLBACK; } + yasl_private_surface_notify_frame_shown(&w->base); if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) yasl_private_surface_notify_refresh(&w->base); else if (w->want_draw) @@ -550,6 +551,13 @@ bool wayland_surface_read_pixels(struct wayland_surface *w, uint32_t *out, size_ return yasl_private_copy_frame(b->pixels, (struct yasl_private_vector2i){b->width, b->height}, out, cap, size); } +bool surface_draw_ready(const struct wayland_surface *w) +{ + return w->want_draw && w->configured && !w->closed && + w->base.presenter == YASL_PRIVATE_PRESENTER_CPU && + buffer_pick_free_index(w->buffers, w->cur) >= 0; +} + void surface_draw_frame(struct wayland_surface *w) { if (w->closed || !w->configured || w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) diff --git a/src/platform/wayland/clipboard.c b/src/platform/wayland/clipboard.c index 9a75953..9c019a1 100644 --- a/src/platform/wayland/clipboard.c +++ b/src/platform/wayland/clipboard.c @@ -48,7 +48,7 @@ struct clipboard { struct offer *drag_offer; struct primary_offer *primary_selection; struct primary_offer *primary_pending_offers; - struct yasl_private_loop *loop; + struct yasl_loop *loop; struct transfer *transfers; char *text; char *primary_text; @@ -106,9 +106,9 @@ static void transfer_end(struct transfer *t, bool complete) struct transfer **p = &owner->transfers; while (*p && *p != t) p = &(*p)->next; if (*p) *p = t->next; - yasl_private_loop_del_fd(owner->loop, t->id); + yasl_loop_del_fd(owner->loop, t->id); if (t->timeout) - yasl_private_loop_del_timer(owner->loop, t->timeout); + yasl_loop_del_timer(owner->loop, t->timeout); close(t->fd); if (t->drop) { if (complete && wl_data_offer_get_version(t->drop->proxy) >= @@ -133,10 +133,10 @@ static void transfer_end(struct transfer *t, bool complete) free(t); } -static int transfer_io(int fd, short events, void *ud) +static void transfer_io(void *ud, int fd, short events) { struct transfer *t = ud; - if (events & (POLLERR | POLLNVAL)) { transfer_end(t, false); return 0; } + if (events & (POLLERR | POLLNVAL)) { transfer_end(t, false); return; } if (t->sending) { ssize_t n = write(fd, t->text + t->offset, t->len - t->offset); if (n > 0) t->offset += (size_t)n; @@ -146,9 +146,9 @@ static int transfer_io(int fd, short events, void *ud) if (t->capacity - t->len < 4096) { size_t capacity = t->capacity ? t->capacity * 2 : 8192; if (capacity > CLIPBOARD_TEXT_MAX) capacity = CLIPBOARD_TEXT_MAX; - if (capacity <= t->len) { transfer_end(t, false); return 0; } + if (capacity <= t->len) { transfer_end(t, false); return; } char *text = realloc(t->text, capacity); - if (!text) { transfer_end(t, false); return 0; } + if (!text) { transfer_end(t, false); return; } t->text = text; t->capacity = capacity; } @@ -160,7 +160,6 @@ static int transfer_io(int fd, short events, void *ud) transfer_end(t, valid); } else if (errno != EAGAIN && errno != EINTR) transfer_end(t, false); } - return 0; } static struct transfer *start_transfer_text(struct clipboard *c, int fd, bool sending, @@ -176,12 +175,12 @@ static struct transfer *start_transfer_text(struct clipboard *c, int fd, bool se if (!t->text) { close(fd); free(t); return NULL; } t->len = strlen(t->text); } - t->id = yasl_private_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t); - if (t->id < 0) { close(fd); free(t->text); free(t); return NULL; } - t->timeout = yasl_private_loop_add_timer(c->loop, CLIPBOARD_TRANSFER_TIMEOUT_NS, + t->id = yasl_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t); + if (t->id <= 0) { close(fd); free(t->text); free(t); return NULL; } + t->timeout = yasl_loop_add_timer(c->loop, CLIPBOARD_TRANSFER_TIMEOUT_NS, transfer_timeout, t); if (!t->timeout) { - yasl_private_loop_del_fd(c->loop, t->id); + yasl_loop_del_fd(c->loop, t->id); close(fd); free(t->text); free(t); @@ -522,7 +521,7 @@ struct wl_data_device *clipboard_data_device(const struct clipboard *c) } struct clipboard *clipboard_create(struct wl_display *display, struct wl_data_device_manager *manager, - struct wl_seat *seat, struct yasl_private_loop *loop, + struct wl_seat *seat, struct yasl_loop *loop, clipboard_receive_fn receive, void *ud) { if (!manager || !seat) return NULL; diff --git a/src/platform/wayland/clipboard.h b/src/platform/wayland/clipboard.h index 7ab1719..738d32e 100644 --- a/src/platform/wayland/clipboard.h +++ b/src/platform/wayland/clipboard.h @@ -19,7 +19,7 @@ typedef void (*clipboard_receive_fn)(const char *, size_t, bool uri_list, enum yasl_private_transfer_status, void *); struct clipboard *clipboard_create(struct wl_display *, struct wl_data_device_manager *, - struct wl_seat *, struct yasl_private_loop *, + struct wl_seat *, struct yasl_loop *, clipboard_receive_fn, void *); void clipboard_destroy(struct clipboard *); bool clipboard_set_seat(struct clipboard *, struct wl_seat *); diff --git a/src/platform/wayland/internal.h b/src/platform/wayland/internal.h index d5b7de1..a580e71 100644 --- a/src/platform/wayland/internal.h +++ b/src/platform/wayland/internal.h @@ -249,7 +249,7 @@ struct wayland_display { struct wp_fifo_manager_v1 *fifo_mgr; struct wp_commit_timing_manager_v1 *commit_timing_mgr; struct xkb_context *xkb_ctx; - struct yasl_private_loop *loop; + struct yasl_loop *loop; char *app_id; struct xdg_toplevel_drag_manager_v1 *toplevel_drag_mgr; struct conn_drag drag; @@ -405,6 +405,8 @@ void conn_destroy_outputs(struct wayland_display *c); void surface_follow_outputs(struct wayland_surface *w); uint64_t conn_pres_to_mono(const struct wayland_display *c, uint64_t ns); uint64_t conn_mono_to_pres(const struct wayland_display *c, uint64_t ns); +/* True when surface_draw_frame would draw now. */ +bool surface_draw_ready(const struct wayland_surface *w); void surface_draw_frame(struct wayland_surface *w); void wayland_surface_arm_frame(struct wayland_surface *w); void surface_fail(struct wayland_surface *w); diff --git a/src/platform/wayland/keyboard.c b/src/platform/wayland/keyboard.c index cf6d56c..294f553 100644 --- a/src/platform/wayland/keyboard.c +++ b/src/platform/wayland/keyboard.c @@ -336,23 +336,22 @@ static void keyboard_key(void *data, struct wl_keyboard *keyboard, } } -static int keyboard_repeat_ready(int fd, short revents, void *data) +static void keyboard_repeat_ready(void *data, int fd, short revents) { struct seat *s = data; uint64_t count; - if (!(revents & POLLIN) || read(fd, &count, sizeof(count)) != sizeof(count)) return 0; + if (!(revents & POLLIN) || read(fd, &count, sizeof(count)) != sizeof(count)) return; struct wayland_surface *w = s->keyboard_focus; if (!w) { keyboard_repeat_stop(s); - return 0; + return; } if (count > 32) count = 32; while (count-- && s->repeat_held && s->repeat_rate > 0) { if (!keyboard_dispatch_key(s, w, s->repeat_key, YASL_KEY_REPEAT)) keyboard_repeat_stop(s); } - return 0; } static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, @@ -391,7 +390,7 @@ int keyboard_repeat_init(struct seat *s) if (s->repeat_fd >= 0) return 0; s->repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (s->repeat_fd < 0) return -1; - s->repeat_watch = yasl_private_loop_add_fd(s->conn->loop, s->repeat_fd, POLLIN, + s->repeat_watch = yasl_loop_add_fd(s->conn->loop, s->repeat_fd, POLLIN, keyboard_repeat_ready, s); if (s->repeat_watch <= 0) { s->repeat_watch = 0; diff --git a/src/platform/wayland/pointer.c b/src/platform/wayland/pointer.c index 571bb12..6cf2710 100644 --- a/src/platform/wayland/pointer.c +++ b/src/platform/wayland/pointer.c @@ -1552,7 +1552,7 @@ void seat_destroy(struct seat *s) } seat_drop_touch(s); clipboard_destroy(s->clipboard); - if (s->repeat_watch > 0) yasl_private_loop_del_fd(c->loop, s->repeat_watch); + if (s->repeat_watch > 0) yasl_loop_del_fd(c->loop, s->repeat_watch); if (s->repeat_fd >= 0) close(s->repeat_fd); if (s->cursor_viewport) wp_viewport_destroy(s->cursor_viewport); if (s->cursor_surface) wl_surface_destroy(s->cursor_surface); diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c index cc0c715..bc37c10 100644 --- a/src/platform/wayland/surface.c +++ b/src/platform/wayland/surface.c @@ -49,7 +49,7 @@ static void display_lose(struct wayland_display *c) report_display_error(c); display_cancel_read(c); if (c->loop && c->watch > 0) - yasl_private_loop_del_fd(c->loop, c->watch); + yasl_loop_del_fd(c->loop, c->watch); c->watch = 0; c->depth++; conn_drag_end(c); @@ -62,7 +62,7 @@ static void display_lose(struct wayland_display *c) yasl_private_display_notify_event(&c->base, YASL_PRIVATE_EVENT_DISPLAY_LOST); c->reconnect_delay_ns = YASL_PRIVATE_MS_TO_NS(100); if (c->loop && !c->reconnect_timer) - c->reconnect_timer = yasl_private_loop_add_timer(c->loop, c->reconnect_delay_ns, reconnect_tick, c); + c->reconnect_timer = yasl_loop_add_timer(c->loop, c->reconnect_delay_ns, reconnect_tick, c); } void surface_fail(struct wayland_surface *w) @@ -73,43 +73,18 @@ void surface_fail(struct wayland_surface *w) yasl_private_surface_notify_close(&w->base); } -static int display_ready(int fd, short revents, void *ud) +/* The display fd is read in wayland_display_check, before any callback. */ +static void display_ready(void *ud, int fd, short revents) { - (void)fd; - struct wayland_display *c = ud; - if (c->lost) - return 0; - c->depth++; - if (c->reading) { - c->reading = false; - if (!(revents & POLLIN)) - wl_display_cancel_read(c->wl); - else if (wl_display_read_events(c->wl) < 0) - revents |= POLLERR; - } - if (revents & POLLOUT) { - if (wl_display_flush(c->wl) >= 0) { - if (c->loop && c->watch > 0) - yasl_private_loop_set_fd_events(c->loop, c->watch, POLLIN); - } else if (errno != EAGAIN) { - display_lose(c); - } - } - if (wl_display_dispatch_pending(c->wl) < 0 || - (revents & (POLLERR | POLLHUP | POLLNVAL))) - display_lose(c); - display_leave(c); - return 0; + (void)ud; (void)fd; (void)revents; } -static bool display_arm(struct wayland_display *c) +static void display_arm(struct wayland_display *c) { - bool had_pending = false; while (!c->reading && wl_display_prepare_read(c->wl) != 0) { - had_pending = true; if (wl_display_dispatch_pending(c->wl) < 0) { display_lose(c); - return false; + return; } } c->reading = true; @@ -118,74 +93,65 @@ static bool display_arm(struct wayland_display *c) if (errno != EAGAIN) { display_cancel_read(c); display_lose(c); - return false; + return; } events |= POLLOUT; } - if (c->loop && c->watch > 0) - yasl_private_loop_set_fd_events(c->loop, c->watch, events); - return had_pending; + yasl_loop_set_fd_events(c->loop, c->watch, events); } -static inline bool display_has_immediate_work(const struct wayland_display *c) +/* A draw that cannot proceed now, for want of a buffer or a configure, is not + * work: the release or configure that unblocks it schedules it again. */ +static bool display_has_immediate_work(const struct wayland_display *c) { - if (unlikely(!c || c->lost)) return false; - for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next) { - bool ready_draw = w->want_draw & (w->frame_cb == NULL) & w->configured; - bool has_input = (w->input.count > 0 || w->input.reset_pending); - if (unlikely((ready_draw | has_input) & !w->closed)) + for (const struct wayland_surface *w = c->surfaces; w; w = w->conn_next) + if (surface_draw_ready(w)) return true; - } return false; } -int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms) +/* Before the wait: dispatch what libwayland already holds, draw due surfaces, + * and arm the read. While lost, only the reconnect timer runs. */ +bool wayland_display_prepare(struct wayland_display *c) { - if (!c) - return -1; - + if (c->lost) + return false; c->depth++; - if (c->lost) { - /* Reconnect attempts run as a loop timer. */ - int n = c->loop ? yasl_private_loop_step(c->loop, timeout_ms) : 0; - display_leave(c); - return n; - } - /* Losing the connection is reported as DISPLAY_LOST, not as a failed wait. */ - if (wl_display_dispatch_pending(c->wl) < 0) { + if (wl_display_dispatch_pending(c->wl) < 0) display_lose(c); - display_leave(c); - return 0; - } - - display_arm(c); - if (c->lost) { - display_leave(c); - return 0; - } - - if (timeout_ms != 0 && display_has_immediate_work(c)) { - timeout_ms = 0; - } - - int n = 0; - if (c->loop) { - n = yasl_private_loop_step(c->loop, timeout_ms); - } + if (!c->lost) + wayland_display_flush(c); + if (!c->lost) + display_arm(c); + bool ready = !c->lost && display_has_immediate_work(c); + display_leave(c); + return ready; +} - if (c->reading) { - display_cancel_read(c); +/* After the wait, before any callback: finish the read prepare armed, so + * callbacks may do roundtrips, and dispatch what it brought. */ +void wayland_display_check(struct wayland_display *c) +{ + if (!c->reading) + return; + c->depth++; + short revents = yasl_private_loop_revents(c->loop, c->watch); + c->reading = false; + if (!(revents & POLLIN)) + wl_display_cancel_read(c->wl); + else if (wl_display_read_events(c->wl) < 0) + revents |= POLLERR; + if (revents & POLLOUT) { + if (wl_display_flush(c->wl) >= 0) + yasl_loop_set_fd_events(c->loop, c->watch, POLLIN); + else if (errno != EAGAIN) + display_lose(c); } - - if (c->wl && wl_display_dispatch_pending(c->wl) < 0) { + if (!c->lost && (wl_display_dispatch_pending(c->wl) < 0 || + (revents & (POLLERR | POLLHUP | POLLNVAL)))) display_lose(c); - display_leave(c); - return 0; - } - display_leave(c); - return n; } void wayland_display_flush(struct wayland_display *c) @@ -212,7 +178,7 @@ static void connection_release(struct wayland_display *c) for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next) surface_release_native(w); conn_drag_end(c); - if (c->loop && c->watch > 0) yasl_private_loop_del_fd(c->loop, c->watch); + if (c->loop && c->watch > 0) yasl_loop_del_fd(c->loop, c->watch); c->watch = 0; if (c->wl) display_cancel_read(c); while (c->seats) @@ -271,7 +237,7 @@ static bool connection_open(struct wayland_display *c) return false; surface_init_cursor(c); if (c->loop) { - c->watch = yasl_private_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c); + c->watch = yasl_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c); if (c->watch <= 0) { c->watch = 0; return false; } } return display_input_init(c) == 0; @@ -329,10 +295,10 @@ static uint64_t reconnect_tick(void *ud, yasl_private_timer_id id, uint64_t inte static void display_free(struct wayland_display *c) { connection_release(c); - if (c->loop && c->reconnect_timer) yasl_private_loop_del_timer(c->loop, c->reconnect_timer); + if (c->loop && c->reconnect_timer) yasl_loop_del_timer(c->loop, c->reconnect_timer); if (c->xkb_ctx) xkb_context_unref(c->xkb_ctx); if (c->owns_loop && c->loop) { - yasl_private_loop_destroy(c->loop); + yasl_loop_destroy(c->loop); c->loop = NULL; c->base.loop = NULL; } @@ -340,14 +306,15 @@ static void display_free(struct wayland_display *c) free(c); } -struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct wayland_display *c = calloc(1, sizeof(*c)); if (!c) return NULL; c->owner = pthread_self(); c->base.ops = &yasl_private_wayland_backend; + c->base.reconnects = true; if (!loop) { - c->loop = yasl_private_loop_create(); + c->loop = yasl_loop_create(); c->owns_loop = true; } else { c->loop = loop; @@ -605,8 +572,8 @@ void wayland_surface_schedule_redraw(struct wayland_surface *w) return; } w->want_draw = true; - if (w->conn && w->conn->loop) - yasl_private_loop_kick(w->conn->loop); + if (w->conn) + yasl_private_loop_wake(w->conn->loop); } struct yasl_private_geometry wayland_surface_geometry(const struct wayland_surface *w) diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c index 630eb91..2751c91 100644 --- a/src/platform/x11/window.c +++ b/src/platform/x11/window.c @@ -143,7 +143,7 @@ struct x11_display { struct x11_display *next_open; Display *native; int screen; - struct yasl_private_loop *loop; + struct yasl_loop *loop; int watch; struct x11_surface *surfaces, *dead; int depth; @@ -354,7 +354,8 @@ static int yasl_private_x11_error_handler(Display *native, XErrorEvent *event) } /* Marks the display lost once and queues DISPLAY_LOST. The poll watch is removed - * by ready(), on the thread that owns the loop, because this can run on a GL thread. */ + * by prepare() or check(), on the thread that owns the loop, because this can run + * on a GL thread. */ static void display_lost(struct x11_display *d) { if (d->lost) return; @@ -1025,7 +1026,7 @@ static void cancel_pastes(struct x11_display *d, struct x11_surface *target) } else if (!target || r->target == target) { /* An INCR transfer in progress is abandoned; its owner times out. */ *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); if (!d->lost) XDestroyWindow(d->native, r->window); free(r->buf); free(r); @@ -1058,8 +1059,8 @@ static uint64_t paste_timeout(void *ud, yasl_private_timer_id id, uint64_t inter static void paste_rearm(struct paste_request *r) { struct x11_display *d = r->display; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); - r->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); + r->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); } /* Unlink a paste request at *link and report its outcome to the surface, if alive. */ @@ -1068,7 +1069,7 @@ static void paste_complete(struct x11_display *d, struct paste_request **link, { struct paste_request *r = *link; *link = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); if (r->target && !r->target->destroyed) yasl_private_surface_complete_paste(&r->target->base, r->id, status, text, len); @@ -2027,7 +2028,7 @@ static void incr_send_retire(struct x11_display *d, struct incr_send *st, bool c *p = st->next; break; } - if (cancel_timer && st->timeout) yasl_private_loop_del_timer(d->loop, st->timeout); + if (cancel_timer && st->timeout) yasl_loop_del_timer(d->loop, st->timeout); free(st->data); free(st); } @@ -2045,8 +2046,8 @@ static uint64_t incr_send_timeout(void *ud, yasl_private_timer_id id, uint64_t i static void incr_send_arm(struct incr_send *st) { struct x11_display *d = st->display; - if (st->timeout) yasl_private_loop_del_timer(d->loop, st->timeout); - st->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); + if (st->timeout) yasl_loop_del_timer(d->loop, st->timeout); + st->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); } /* Start an INCR send: publish the total size, then wait for the requestor to @@ -2062,7 +2063,7 @@ static bool incr_send_start(struct x11_display *d, Window requestor, Atom proper st->target = target; st->data = data; st->length = length; - st->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); + st->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); if (!st->timeout) { free(st); return false; @@ -2325,14 +2326,14 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) /* Surface destruction detached this accepted request. The reply means * the owner has finished writing the initial property; retire safely. */ *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); free(r); return; } if (event->property == None) { *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); enum yasl_private_transfer_status status = XGetSelectionOwner(d->native, r->selection) == None ? YASL_PRIVATE_TRANSFER_UNAVAILABLE : YASL_PRIVATE_TRANSFER_FAILED; @@ -2361,7 +2362,7 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) return; } *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); Atom actual = None; int format = 0; unsigned long count = 0, left = 0; @@ -2938,56 +2939,49 @@ static void flush(struct yasl_private_display *base) leave(d); } -static int ready(int fd, short events, void *ud) +/* The connection is read in check(), before any callback. */ +static void ready(void *ud, int fd, short events) +{ + (void)ud; (void)fd; (void)events; +} + +/* A hung-up socket stays readable; stop watching it so the loop does not spin. */ +static void drop_watch(struct x11_display *d) +{ + if (d->watch > 0) yasl_loop_del_fd(d->loop, d->watch); + d->watch = 0; +} + +/* Before the wait: dispatch what Xlib already holds, draw due surfaces, and send + * requests. Loss is final, so a lost display only drops its watch. */ +static bool prepare(struct yasl_private_display *base) { - (void)fd; - struct yasl_private_display *base = ud; struct x11_display *d = (struct x11_display *)base; - if (events & (POLLERR | POLLHUP)) display_lost(d); - if (d->lost) { - /* A hung-up socket stays readable; stop watching it so the loop does not spin. */ - if (d->watch > 0) yasl_private_loop_del_fd(d->loop, d->watch); - d->watch = 0; - return 0; - } - if (events & POLLIN) { + if (!d->lost) { dispatch(base); flush(base); } - return 0; + if (d->lost) { + drop_watch(d); + return false; + } + /* Replies awaited while drawing can read events into Xlib's queue. */ + return XEventsQueued(d->native, QueuedAlready) > 0; } -static int dispatch_timeout(struct yasl_private_display *base, int timeout_ms) +static void check(struct yasl_private_display *base) { struct x11_display *d = (struct x11_display *)base; - if (unlikely(!d || !d->native)) { errno = ENODEV; return -1; } - /* The DISPLAY_LOST event is delivered by the wait that found the loss. */ - if (d->lost) { errno = ENOTCONN; return -1; } - dispatch(base); - if (d->lost) return 0; - for (struct x11_surface *s = d->surfaces; s; s = s->next) { - if (s->want_draw || s->input.count || s->input.reset_pending) { - timeout_ms = 0; - break; - } - } - if (d->loop) { - XFlush(d->native); - if (d->lost) return 0; - if (XEventsQueued(d->native, QueuedAlready) > 0) - timeout_ms = 0; - if (!XPending(d->native) && timeout_ms != 0) { - yasl_private_loop_step(d->loop, timeout_ms); - dispatch(base); - } else { - yasl_private_loop_step(d->loop, 0); - dispatch(base); - } + short revents = d->watch > 0 ? yasl_private_loop_revents(d->loop, d->watch) : 0; + if (revents & (POLLERR | POLLHUP)) display_lost(d); + if (d->lost) { + drop_watch(d); + return; } - return 0; + if (revents & POLLIN) dispatch(base); } -static struct yasl_private_display *display_open(struct yasl_private_loop *loop, +static struct yasl_private_display *display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct x11_display *d = calloc(1, sizeof(*d)); @@ -3046,7 +3040,7 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, XISelectEvents(d->native, RootWindow(d->native, d->screen), &mask, 1); } if (!loop) { - d->loop = yasl_private_loop_create(); + d->loop = yasl_loop_create(); d->owns_loop = true; } else { d->loop = loop; @@ -3055,7 +3049,7 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, d->app_id = strdup(spec && spec->app_id ? spec->app_id : "YASL"); d->base.app_id = d->app_id; if (!d->loop || !d->app_id) { - if (d->owns_loop) yasl_private_loop_destroy(d->loop); + if (d->owns_loop) yasl_loop_destroy(d->loop); free(d->app_id); XCloseDisplay(d->native); display_unregister(d); @@ -3095,14 +3089,14 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, ATOM(uri_list, "text/uri-list"); #undef ATOM if (d->loop) { - d->watch = yasl_private_loop_add_fd(d->loop, ConnectionNumber(d->native), POLLIN, ready, &d->base); + d->watch = yasl_loop_add_fd(d->loop, ConnectionNumber(d->native), POLLIN, ready, &d->base); if (d->watch < 0) { if (d->im) XCloseIM(d->im); XDestroyWindow(d->native, d->selection_window); XCloseDisplay(d->native); display_unregister(d); free(d->app_id); - if (d->owns_loop) yasl_private_loop_destroy(d->loop); + if (d->owns_loop) yasl_loop_destroy(d->loop); free(d); return NULL; } @@ -3118,9 +3112,9 @@ static void display_close(struct yasl_private_display *base) cancel_pastes(d, NULL); incr_cancel_all(d); d->pending_drop.active = false; - if (d->loop && d->watch > 0) yasl_private_loop_del_fd(d->loop, d->watch); + if (d->loop && d->watch > 0) yasl_loop_del_fd(d->loop, d->watch); if (d->owns_loop && d->loop) { - yasl_private_loop_destroy(d->loop); + yasl_loop_destroy(d->loop); d->loop = NULL; d->base.loop = NULL; } @@ -3208,7 +3202,7 @@ static bool request_paste(struct yasl_private_display *base, enum yasl_private_s r->selection = selection; r->id = id; errno = 0; - r->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, + r->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); if (!r->timeout) { XDestroyWindow(d->native, r->window); @@ -3236,8 +3230,8 @@ static void schedule_redraw(struct yasl_surface *base) if (!s->destroyed) { s->want_draw = true; struct x11_display *d = (struct x11_display *)base->display; - if (d && d->loop) - yasl_private_loop_kick(d->loop); + if (d) + yasl_private_loop_wake(d->loop); } } @@ -3668,7 +3662,8 @@ const struct yasl_private_backend_ops yasl_private_x11_backend = { .display_open = display_open, .display_close = display_close, .display_caps = caps, - .display_dispatch_timeout = dispatch_timeout, + .display_prepare = prepare, + .display_check = check, .display_flush = flush, .display_modifiers = mods, .display_copy_text = copy_text, |
