diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-15 20:38:14 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-15 20:38:14 +0300 |
| commit | c2832a63547e6328afde25bb3b8145267eadef47 (patch) | |
| tree | bf03ace79f01df10ddaffd70bea74678ab4ca219 /src/common/private_loop.h | |
| parent | 3d012d707d0242780cd386868ab511846dc9b0af (diff) | |
Make the loop public and let the display drive it
An application that has its own descriptors and timers needed a second
loop next to yasl's, and could not see yasl's key-repeat timer, clipboard
pipes or reconnect timer. Now there is one wait: the application owns a
yasl_loop, and yasl_init can run the display on it.
- Public yasl_loop_*: descriptors, timers and signals. Descriptor
callbacks return void. Timer IDs are 64 bits and never reused; a zero
delay runs in the next step. A nested step or run fails with EBUSY, a
step off the loop's thread with EPERM, and a quit made before a run ends
it at once. An interrupted wait keeps its deadline. Creating a loop
fails when it cannot make its wake pipe.
- The display drives the loop through prepare, wait and check hooks
instead of wrapping its step. Wayland finishes its read before any
callback runs, so loop callbacks may create surfaces and do roundtrips.
Readiness (queued events, an owed REFRESH) is derived in the step, so
the wait never blocks over pending work. A draw that cannot proceed for
want of a buffer is no longer counted as work.
- yasl_init takes struct yasl_options. With loop and on_event, each step
delivers events to on_event and yasl_wait_event fails with EBUSY.
EALREADY only for the same options, EBUSY for others.
- yasl_surface_request_frame: a REFRESH once the window can show a new
frame, for pacing animation.
- DISPLAY_LOST reports whether the display reconnects. Pull waits still
fail with ENOTCONN after a final loss.
- Events pushed from another thread wake the loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/common/private_loop.h')
| -rw-r--r-- | src/common/private_loop.h | 225 |
1 files changed, 55 insertions, 170 deletions
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(). |
