From c2832a63547e6328afde25bb3b8145267eadef47 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Thu, 15 Oct 2026 20:38:14 +0300 Subject: Make the loop public and let the display drive it An application that has its own descriptors and timers needed a second loop next to yasl's, and could not see yasl's key-repeat timer, clipboard pipes or reconnect timer. Now there is one wait: the application owns a yasl_loop, and yasl_init can run the display on it. - Public yasl_loop_*: descriptors, timers and signals. Descriptor callbacks return void. Timer IDs are 64 bits and never reused; a zero delay runs in the next step. A nested step or run fails with EBUSY, a step off the loop's thread with EPERM, and a quit made before a run ends it at once. An interrupted wait keeps its deadline. Creating a loop fails when it cannot make its wake pipe. - The display drives the loop through prepare, wait and check hooks instead of wrapping its step. Wayland finishes its read before any callback runs, so loop callbacks may create surfaces and do roundtrips. Readiness (queued events, an owed REFRESH) is derived in the step, so the wait never blocks over pending work. A draw that cannot proceed for want of a buffer is no longer counted as work. - yasl_init takes struct yasl_options. With loop and on_event, each step delivers events to on_event and yasl_wait_event fails with EBUSY. EALREADY only for the same options, EBUSY for others. - yasl_surface_request_frame: a REFRESH once the window can show a new frame, for pacing animation. - DISPLAY_LOST reports whether the display reconnects. Pull waits still fail with ENOTCONN after a final loss. - Events pushed from another thread wake the loop. Co-Authored-By: Claude Opus 5.5 --- examples/notepad.c | 2 +- examples/tablet.c | 5 +- include/yasl/yasl.h | 88 ++++- src/common/backend.c | 136 +++++-- src/common/backend.h | 28 +- src/common/events.c | 2 + src/common/loop.h | 6 +- src/common/private_loop.h | 225 +++-------- src/common/surface.c | 122 ++++-- src/common/types.h | 25 +- src/platform/cocoa/api.h | 5 +- src/platform/cocoa/window.m | 79 ++-- src/platform/posix/loop.c | 646 +++++++++++++++----------------- src/platform/wayland/api.h | 5 +- src/platform/wayland/backend.c | 20 +- src/platform/wayland/buffer.c | 8 + src/platform/wayland/clipboard.c | 25 +- src/platform/wayland/clipboard.h | 2 +- src/platform/wayland/internal.h | 4 +- src/platform/wayland/keyboard.c | 9 +- src/platform/wayland/pointer.c | 2 +- src/platform/wayland/surface.c | 147 +++----- src/platform/x11/window.c | 117 +++--- tests/benchmark.c | 8 +- tests/cocoa/test_native.c | 2 +- tests/common/test_contracts.c | 10 +- tests/common/test_gl.c | 4 +- tests/common/test_loop.c | 204 ++++++++-- tests/common/test_surface_integration.c | 110 +++++- tests/common/test_timers.c | 128 ++++--- tests/common/test_vulkan.c | 2 +- tests/test_lifecycle.c | 14 +- tests/wayland/test_clipboard.c | 8 +- tests/wayland/test_reconnect.c | 5 +- tests/x11/test_input.c | 8 +- tests/x11/test_tablet_native.c | 2 +- 36 files changed, 1280 insertions(+), 933 deletions(-) diff --git a/examples/notepad.c b/examples/notepad.c index d0b38b9..470d976 100644 --- a/examples/notepad.c +++ b/examples/notepad.c @@ -1696,7 +1696,7 @@ int main(int argc, char **argv) } } - if (yasl_init(YASL_BACKEND_AUTO, "org.yasl.notepad") < 0 || !editor_init(&ed)) { + if (yasl_init(&(struct yasl_options){.app_id = "org.yasl.notepad"}) < 0 || !editor_init(&ed)) { fprintf(stderr, "Could not initialize YASL Notepad\n"); editor_shutdown(&ed); yasl_shutdown(); diff --git a/examples/tablet.c b/examples/tablet.c index 8c62fc0..bbe242c 100644 --- a/examples/tablet.c +++ b/examples/tablet.c @@ -42,8 +42,9 @@ int main(int argc, char **argv) if (!strcmp(argv[i], "--smoke-test")) smoke = true; else if (!strcmp(argv[i], "--backend") && i + 1 < argc) { const char *b = argv[++i]; - if (yasl_init(!strcmp(b, "wayland") ? YASL_BACKEND_WAYLAND : - !strcmp(b, "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA, NULL) < 0) return 1; + enum yasl_backend backend = !strcmp(b, "wayland") ? YASL_BACKEND_WAYLAND : + !strcmp(b, "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA; + if (yasl_init(&(struct yasl_options){.backend = backend}) < 0) return 1; } else return 1; } yasl_surface *s = yasl_surface_create(&(struct yasl_surface_options){.title="YASL pen canvas — C clears, Escape exits", .width=800, .height=600}); diff --git a/include/yasl/yasl.h b/include/yasl/yasl.h index 15bbe16..dab1091 100644 --- a/include/yasl/yasl.h +++ b/include/yasl/yasl.h @@ -27,6 +27,7 @@ extern "C" { #endif typedef struct yasl_surface yasl_surface; typedef struct yasl_event yasl_event; +typedef struct yasl_loop yasl_loop; /* Lifecycle */ enum yasl_backend { YASL_BACKEND_AUTO, YASL_BACKEND_WAYLAND, YASL_BACKEND_X11, YASL_BACKEND_COCOA }; @@ -36,11 +37,25 @@ enum yasl_feature { YASL_FEATURE_TEXT_INPUT = 64, YASL_FEATURE_LOCKED_CURSOR = 128, YASL_FEATURE_OPENGL = 256, YASL_FEATURE_VULKAN = 512 }; -/** Optional: the first surface initializes with AUTO and no app ID. app_id is a - * reverse-DNS name for window grouping and notifications. EALREADY once - * initialized; a failed initialization may be retried. */ -YASL_API int yasl_init(enum yasl_backend backend, const char *app_id); -/** Destroys every surface. EBUSY while a Vulkan surface exists. */ +/** Zero is the default: AUTO, no app ID, and yasl_wait_event delivers events. + * app_id is a reverse-DNS name for window grouping and notifications. Set loop + * and on_event together to run the display on an application loop: each + * yasl_loop_step then delivers events to on_event, and yasl_wait_event fails + * with EBUSY. */ +struct yasl_options { + enum yasl_backend backend; + const char *app_id; + yasl_loop *loop; + void (*on_event)(void *userdata, const yasl_event *event); + void *userdata; +}; +/** Optional: the first surface initializes with defaults; NULL options too. + * EINVAL when only one of loop and on_event is set; EPERM off the loop's + * thread. EALREADY once initialized with the same options, EBUSY with others; + * a failed initialization may be retried. */ +YASL_API int yasl_init(const struct yasl_options *options); +/** Destroys every surface; on_event is not called again. EBUSY inside a loop + * step or while a Vulkan surface exists. */ YASL_API int yasl_shutdown(void); /** AUTO and 0 before initialization. */ YASL_API enum yasl_backend yasl_get_backend(void); @@ -99,6 +114,10 @@ YASL_API int yasl_surface_set_title(yasl_surface *surface, const char *title); enum yasl_request { YASL_REQUEST_RESTORE, YASL_REQUEST_FULLSCREEN, YASL_REQUEST_MAXIMIZE, YASL_REQUEST_MINIMIZE }; YASL_API int yasl_surface_request(yasl_surface *surface, enum yasl_request request); YASL_API int yasl_surface_request_size(yasl_surface *surface, int width, int height); +/** Asks for a REFRESH when the window can show a new frame: at once when none + * is in flight, otherwise once the system shows the current one. Requests + * coalesce. Use it to pace animation; draw at once for input. */ +YASL_API int yasl_surface_request_frame(yasl_surface *surface); enum yasl_cursor { YASL_CURSOR_ARROW, YASL_CURSOR_TEXT, YASL_CURSOR_LINK, YASL_CURSOR_CROSSHAIR, YASL_CURSOR_MOVE, @@ -196,10 +215,11 @@ enum yasl_event_type { YASL_EVENT_DROP, /**< External data: x, y; action is a yasl_drop_action; * text on RELEASE is a text/uri-list. */ YASL_EVENT_DRAG, /**< A yasl_drag_start drag: x, y; action is a yasl_drop_action. */ - /** The display server went away. Before the next wait, destroy Vulkan - * surfaces and detach GL contexts on every thread; popups close. Wayland - * reconnects and sends DISPLAY_RESTORED: recreate them (GL contexts are new). - * Elsewhere loss is final and waits fail with ENOTCONN. */ + /** The display server went away; action is a yasl_lost. Before the next + * wait, or before returning to the loop, destroy Vulkan surfaces and detach + * GL contexts on every thread; popups close. On RECONNECTING, DISPLAY_RESTORED + * follows: recreate them (GL contexts are new). After FINAL, yasl_wait_event + * fails with ENOTCONN once no event is left. */ YASL_EVENT_DISPLAY_LOST, YASL_EVENT_DISPLAY_RESTORED }; @@ -218,6 +238,7 @@ enum yasl_scroll_source { YASL_SCROLL_UNKNOWN, YASL_SCROLL_WHEEL, YASL_SCROLL_FI enum yasl_scroll_phase { YASL_SCROLL_PHASE_NONE, YASL_SCROLL_BEGIN, YASL_SCROLL_UPDATE, YASL_SCROLL_END }; enum yasl_transfer { YASL_TRANSFER_OK, YASL_TRANSFER_UNAVAILABLE, YASL_TRANSFER_FAILED }; enum yasl_drop_action { YASL_DROP_OVER, YASL_DROP_LEAVE, YASL_DROP_RELEASE }; +enum yasl_lost { YASL_LOST_RECONNECTING, YASL_LOST_FINAL }; /** Physical key, layout labels (codepoint unshifted, shifted; 0 if none) and the * native code (evdev, X11 keycode or macOS virtual key). Type with TEXT, not this. */ @@ -256,7 +277,8 @@ struct yasl_event { uint64_t time_ns; unsigned action, mods; /**< mods: YASL_MOD_* for KEY, POINTER and SCROLL. */ double x, y; - const char *text; /**< NUL-terminated UTF-8 until the next wait, or NULL. */ + const char *text; /**< NUL-terminated UTF-8 until the next wait or until + * on_event returns, or NULL. */ size_t length; union { struct yasl_surface_info window; @@ -268,18 +290,58 @@ struct yasl_event { }; }; /** 1 with an event, 0 once timeout_ms passes or after yasl_wake. 0 polls and -1 - * waits forever. On failure out->type is NONE. */ + * waits forever. On failure out->type is NONE. EBUSY with on_event. */ YASL_API int yasl_wait_event(yasl_event *out, int timeout_ms); /** Makes a waiting, or the next, yasl_wait_event return 0 once no event is - * pending. Thread-safe. */ + * pending. With on_event, wakes the loop. Thread-safe. */ YASL_API void yasl_wake(void); /** Monotonic nanoseconds, the clock of event times. Thread-safe. */ YASL_API uint64_t yasl_time_ns(void); +/* Loop: one wait for the whole application. Give it to yasl_init to run the + * display on it; it also runs descriptors, timers and signals without one. + * Call it and get its callbacks on the thread that created it. */ +/** NULL with errno on failure. */ +YASL_API yasl_loop *yasl_loop_create(void); +/** EBUSY while a step runs or the display runs on it. Leaves descriptors open. */ +YASL_API int yasl_loop_destroy(yasl_loop *loop); +/** Waits at most timeout_ms (0 polls, -1 waits) and runs what is ready: + * display events, then descriptors, then due timers. Returns how many + * descriptor and timer callbacks ran. EBUSY inside a step; EPERM off the + * loop's thread. */ +YASL_API int yasl_loop_step(yasl_loop *loop, int timeout_ms); +/** Steps until yasl_loop_quit. A quit made before the run ends it at once. */ +YASL_API int yasl_loop_run(yasl_loop *loop); +YASL_API void yasl_loop_quit(yasl_loop *loop); +/** Calls callback with the poll(2) revents, errors and hangup included; it + * must consume them or change the watch. Returns an ID above zero. */ +YASL_API int yasl_loop_add_fd(yasl_loop *loop, int fd, short events, + void (*callback)(void *userdata, int fd, short revents), + void *userdata); +/** ENOENT for an unknown ID. Leaves the descriptor open. */ +YASL_API int yasl_loop_del_fd(yasl_loop *loop, int id); +YASL_API int yasl_loop_set_fd_events(yasl_loop *loop, int id, short events); +/** Calls callback after delay_ns, 0 meaning the next step. It returns the next + * delay, or 0 to stop. Returns an ID that is never reused, or 0 with errno. */ +YASL_API uint64_t yasl_loop_add_timer(yasl_loop *loop, uint64_t delay_ns, + uint64_t (*callback)(void *userdata, uint64_t id, + uint64_t delay_ns), + void *userdata); +/** True when it cancels an armed timer, also from its own callback. */ +YASL_API bool yasl_loop_del_timer(yasl_loop *loop, uint64_t id); +/** Calls callback on the loop's thread for each signal; repeats before the + * call coalesce. EBUSY when this loop already watches signals or another loop + * watches one of them. Unwatching restores the previous actions. */ +YASL_API int yasl_loop_watch_signals(yasl_loop *loop, const int *signals, int count, + void (*callback)(void *userdata, int signo), + void *userdata); +YASL_API void yasl_loop_unwatch_signals(yasl_loop *loop); + /* Rendering */ /** CPU surfaces: the window's pixel_width * pixel_height premultiplied * 0xAARRGGBB pixels, rows tightly packed. They persist, and a resize keeps the - * overlap and zeroes the rest. Valid until the next wait. Present to show them; + * overlap and zeroes the rest. Valid until the next wait, the return of the + * callback that got them, or the next yasl_cpu_ call. Present to show them; * the system may also show them when it redraws the window. */ struct yasl_damage { int x, y, width, height; 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 , 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 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 #include +#include #include +#include #include #include #include @@ -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 -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, diff --git a/tests/benchmark.c b/tests/benchmark.c index 8fa8066..53c24f4 100644 --- a/tests/benchmark.c +++ b/tests/benchmark.c @@ -188,15 +188,15 @@ static uint64_t timer_dummy_cb(void *ud, yasl_private_timer_id id, uint64_t inte static void bench_timers(void) { const int iters = 2000000; - struct yasl_private_loop *loop = yasl_private_loop_create(); + struct yasl_loop *loop = yasl_loop_create(); uint64_t t0 = get_time_ns(); for (int i = 0; i < iters; i++) { - yasl_private_timer_id id = yasl_private_loop_add_timer(loop, 1000000, timer_dummy_cb, NULL); - yasl_private_loop_del_timer(loop, id); + yasl_private_timer_id id = yasl_loop_add_timer(loop, 1000000, timer_dummy_cb, NULL); + yasl_loop_del_timer(loop, id); } uint64_t t1 = get_time_ns(); - yasl_private_loop_destroy(loop); + yasl_loop_destroy(loop); double ms = (double)(t1 - t0) / 1e6; double per_cycle = (double)(t1 - t0) / iters; char details[128]; diff --git a/tests/cocoa/test_native.c b/tests/cocoa/test_native.c index d26a89d..7ff4426 100644 --- a/tests/cocoa/test_native.c +++ b/tests/cocoa/test_native.c @@ -61,7 +61,7 @@ static void test_window(enum yasl_private_backend request, bool null_spec) yasl_private_surface_schedule_redraw(state.surface); uint64_t deadline = yasl_private_loop_now_ns() + 3000000000ull; while (!state.draws && yasl_private_loop_now_ns() < deadline) { - yasl_private_display_dispatch(state.display, 0); + yasl_loop_step(yasl_private_display_loop(state.display), 0); yasl_private_display_flush(state.display); nanosleep(&(struct timespec){.tv_nsec = 1000000}, NULL); } diff --git a/tests/common/test_contracts.c b/tests/common/test_contracts.c index f9f427f..004cd64 100644 --- a/tests/common/test_contracts.c +++ b/tests/common/test_contracts.c @@ -347,9 +347,6 @@ static bool popup_reposition(struct yasl_surface *popup, static uint32_t popup_caps(const struct yasl_private_display *d) { (void)d; return YASL_PRIVATE_CAP_POPUPS; } -static int dispatch_failure(struct yasl_private_display *d, int timeout) -{ (void)d; (void)timeout; return -1; } - static struct yasl_surface *create_failure(struct yasl_private_display *d, const struct yasl_private_window_spec *spec, void *device) { (void)d; (void)spec; (void)device; return NULL; } @@ -372,8 +369,7 @@ static void test_errors(void) .surface_create_toplevel = create_failure, .surface_create_popup = popup_create, .surface_popup_reposition = popup_reposition, - .display_drag_start = drag_without_listener, - .display_dispatch_timeout = dispatch_failure}; + .display_drag_start = drag_without_listener}; struct yasl_private_display d = {.ops = &ops}; struct yasl_surface s = {.display = &d}; struct yasl_private_popup_spec spec = {.size = {120, 80}, .anchor = {0, 0, 1, 1}}; @@ -399,11 +395,7 @@ static void test_errors(void) d.ops = &unsupported; assert(!yasl_private_surface_create_popup(&s, &spec) && errno == ENOTSUP); assert(!yasl_private_surface_popup_reposition(&s, &spec) && errno == ENOTSUP); - assert(yasl_private_display_dispatch(&d, 0) == -1 && errno == ENOTSUP); - assert(yasl_private_display_dispatch(NULL, 0) == -1 && errno == EINVAL); d.ops = &ops; - errno = ERANGE; - assert(yasl_private_display_dispatch(&d, 0) == -1 && errno == EIO); struct yasl_private_window_spec toplevel = {.size = {120, 80}}; errno = ERANGE; assert(!yasl_private_surface_create_toplevel(&d, &toplevel) && errno == ENODEV); diff --git a/tests/common/test_gl.c b/tests/common/test_gl.c index 2bf3d8c..1bb5bec 100644 --- a/tests/common/test_gl.c +++ b/tests/common/test_gl.c @@ -77,8 +77,8 @@ static void wait_for_size(yasl_surface *surface, int width, int height) int main(int argc, char **argv) { REQUIRE(argc == 2, "expected backend argument: wayland or x11"); - if (!strcmp(argv[1], "wayland")) REQUIRE(yasl_init(YASL_BACKEND_WAYLAND, NULL) == 0, "select Wayland backend"); - else if (!strcmp(argv[1], "x11")) REQUIRE(yasl_init(YASL_BACKEND_X11, NULL) == 0, "select X11 backend"); + if (!strcmp(argv[1], "wayland")) REQUIRE(yasl_init(&(struct yasl_options){.backend = YASL_BACKEND_WAYLAND}) == 0, "select Wayland backend"); + else if (!strcmp(argv[1], "x11")) REQUIRE(yasl_init(&(struct yasl_options){.backend = YASL_BACKEND_X11}) == 0, "select X11 backend"); else REQUIRE(false, "backend must be wayland or x11"); primary = yasl_surface_create(&(struct yasl_surface_options){.title="YASL GL test", .width=320, .height=240, .type=YASL_SURFACE_GL}); diff --git a/tests/common/test_loop.c b/tests/common/test_loop.c index 5c49691..bdc33b7 100644 --- a/tests/common/test_loop.c +++ b/tests/common/test_loop.c @@ -4,7 +4,9 @@ #include #include +#include #include +#include #include #include #include @@ -29,7 +31,7 @@ static void on_alarm(int signo) } struct post_log { - struct yasl_private_loop *loop; + struct yasl_loop *loop; char order[8]; int count; }; @@ -57,7 +59,7 @@ static void post_b(void *ud) static void test_post_order(void) { - struct yasl_private_loop *l = yasl_private_loop_create(); + struct yasl_loop *l = yasl_loop_create(); assert(l); assert(!yasl_private_loop_post(NULL, post_b, NULL)); assert(!yasl_private_loop_post(l, NULL, NULL)); @@ -65,20 +67,20 @@ static void test_post_order(void) struct post_log log = {.loop = l}; assert(yasl_private_loop_post(l, post_a, &log)); assert(yasl_private_loop_post(l, post_b, &log)); - assert(yasl_private_loop_step(l, 0) >= 1); + assert(yasl_loop_step(l, 0) >= 1); /* A post made while posts run waits for a later step. */ assert(log.count == 2 && memcmp(log.order, "ab", 2) == 0); - assert(yasl_private_loop_step(l, 0) >= 1); + assert(yasl_loop_step(l, 0) >= 1); assert(log.count == 3 && log.order[2] == 'c'); /* Destroy drops posts that never ran. */ assert(yasl_private_loop_post(l, post_b, &log)); - yasl_private_loop_destroy(l); + yasl_loop_destroy(l); assert(log.count == 3); } struct thread_ctx { - struct yasl_private_loop *loop; + struct yasl_loop *loop; bool kick; int signo; _Atomic bool ran; @@ -105,27 +107,27 @@ static void *wake_thread(void *ud) static void test_cross_thread(void) { - struct yasl_private_loop *l = yasl_private_loop_create(); + struct yasl_loop *l = yasl_loop_create(); assert(l); struct thread_ctx post = {.loop = l}; pthread_t t; assert(pthread_create(&t, NULL, wake_thread, &post) == 0); while (!post.ran) - assert(yasl_private_loop_step(l, -1) >= 0); + assert(yasl_loop_step(l, -1) >= 0); pthread_join(t, NULL); /* A kick ends an indefinite wait without running anything. */ struct thread_ctx kick = {.loop = l, .kick = true}; assert(pthread_create(&t, NULL, wake_thread, &kick) == 0); - assert(yasl_private_loop_step(l, -1) >= 1); + assert(yasl_loop_step(l, -1) >= 1); pthread_join(t, NULL); - yasl_private_loop_destroy(l); + yasl_loop_destroy(l); } struct signal_log { - struct yasl_private_loop *loop; + struct yasl_loop *loop; int seen[NSIG]; int total; bool unwatch; @@ -138,7 +140,7 @@ static void on_signal(void *ud, int signo) log->seen[signo]++; log->total++; if (log->unwatch) - yasl_private_loop_unwatch_signals(log->loop); + yasl_loop_unwatch_signals(log->loop); } static volatile sig_atomic_t previous_handler_hits; @@ -149,10 +151,10 @@ static void previous_handler(int signo) previous_handler_hits++; } -static void step_until(struct yasl_private_loop *l, const int *counter, int want) +static void step_until(struct yasl_loop *l, const int *counter, int want) { while (*counter < want) - assert(yasl_private_loop_step(l, -1) >= 0); + assert(yasl_loop_step(l, -1) >= 0); } static void test_signals(void) @@ -161,30 +163,26 @@ static void test_signals(void) sigemptyset(&prev.sa_mask); assert(sigaction(SIGUSR2, &prev, NULL) == 0); - struct yasl_private_loop *l = yasl_private_loop_create(); - struct yasl_private_loop *other = yasl_private_loop_create(); + struct yasl_loop *l = yasl_loop_create(); + struct yasl_loop *other = yasl_loop_create(); assert(l && other); struct signal_log log = {.loop = l}; const int bad[] = {SIGKILL}; - errno = 0; - assert(!yasl_private_loop_watch_signals(l, bad, 1, on_signal, &log) && errno == EINVAL); + assert(yasl_loop_watch_signals(l, bad, 1, on_signal, &log) == -EINVAL); const int sigs[] = {SIGUSR1, SIGUSR2, SIGCHLD}; - errno = 0; - assert(!yasl_private_loop_watch_signals(l, sigs, 0, on_signal, &log) && errno == EINVAL); - errno = 0; - assert(!yasl_private_loop_watch_signals(l, sigs, 3, NULL, &log) && errno == EINVAL); - assert(yasl_private_loop_watch_signals(l, sigs, 3, on_signal, &log)); + assert(yasl_loop_watch_signals(l, sigs, 0, on_signal, &log) == -EINVAL); + assert(yasl_loop_watch_signals(l, sigs, 3, NULL, &log) == -EINVAL); + assert(yasl_loop_watch_signals(l, sigs, 3, on_signal, &log) == 0); /* One signal belongs to one loop at a time. */ - errno = 0; - assert(!yasl_private_loop_watch_signals(other, sigs, 1, on_signal, &log) && errno == EBUSY); + assert(yasl_loop_watch_signals(other, sigs, 1, on_signal, &log) == -EBUSY); /* Deliveries before the callback runs coalesce per signal. */ raise(SIGUSR1); raise(SIGUSR1); raise(SIGUSR2); - assert(yasl_private_loop_step(l, 0) >= 1); + assert(yasl_loop_step(l, 0) >= 1); assert(log.seen[SIGUSR1] == 1 && log.seen[SIGUSR2] == 1); assert(previous_handler_hits == 0); @@ -203,13 +201,16 @@ static void test_signals(void) int status; assert(waitpid(child, &status, 0) == child); - /* Replacing the watch restores the signals it drops. */ + /* A loop has one watch, so a second cannot silently drop the first one's + * signals. Unwatching restores the previous actions. */ const int only_usr1[] = {SIGUSR1}; - assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); + assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == -EBUSY); + yasl_loop_unwatch_signals(l); + assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == 0); raise(SIGUSR2); assert(previous_handler_hits == 1); - assert(yasl_private_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log)); - yasl_private_loop_unwatch_signals(other); + assert(yasl_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log) == 0); + yasl_loop_unwatch_signals(other); assert(previous_handler_hits == 1); raise(SIGUSR2); assert(previous_handler_hits == 2); @@ -221,21 +222,153 @@ static void test_signals(void) struct sigaction now; assert(sigaction(SIGUSR1, NULL, &now) == 0); assert(now.sa_handler == SIG_DFL); - yasl_private_loop_unwatch_signals(l); + yasl_loop_unwatch_signals(l); /* Destroy unwatches. */ log.unwatch = false; - assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); - yasl_private_loop_destroy(l); + assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == 0); + assert(yasl_loop_destroy(l) == 0); assert(sigaction(SIGUSR1, NULL, &now) == 0); assert(now.sa_handler == SIG_DFL); - yasl_private_loop_destroy(other); + yasl_loop_destroy(other); struct sigaction dfl = {.sa_handler = SIG_DFL}; sigemptyset(&dfl.sa_mask); sigaction(SIGUSR2, &dfl, NULL); } +struct fd_log { + int calls; + short revents; +}; + +static void on_readable(void *ud, int fd, short revents) +{ + struct fd_log *log = ud; + char b; + if (revents & POLLIN) + assert(read(fd, &b, 1) == 1); + log->calls++; + log->revents = revents; +} + +static void test_fds(void) +{ + yasl_loop *l = yasl_loop_create(); + assert(l); + int p[2]; + assert(pipe(p) == 0); + struct fd_log log = {0}; + assert(yasl_loop_add_fd(l, -1, POLLIN, on_readable, &log) == -EINVAL); + assert(yasl_loop_add_fd(l, p[0], POLLIN, NULL, &log) == -EINVAL); + int id = yasl_loop_add_fd(l, p[0], POLLIN, on_readable, &log); + assert(id > 0); + assert(yasl_loop_step(l, 0) == 0 && log.calls == 0); + assert(write(p[1], "x", 1) == 1); + assert(yasl_loop_step(l, -1) == 1 && log.calls == 1 && (log.revents & POLLIN)); + /* No requested events: a ready descriptor stays quiet. */ + assert(yasl_loop_set_fd_events(l, id, 0) == 0); + assert(write(p[1], "x", 1) == 1); + assert(yasl_loop_step(l, 0) == 0 && log.calls == 1); + assert(yasl_loop_set_fd_events(l, id, POLLIN) == 0); + assert(yasl_loop_step(l, 0) == 1 && log.calls == 2); + assert(yasl_loop_del_fd(l, id) == 0); + assert(yasl_loop_del_fd(l, id) == -ENOENT); + assert(yasl_loop_set_fd_events(l, id, POLLIN) == -ENOENT); + assert(write(p[1], "x", 1) == 1); + assert(yasl_loop_step(l, 0) == 0 && log.calls == 2); + close(p[0]); + close(p[1]); + assert(yasl_loop_destroy(l) == 0); +} + +struct reentry { + yasl_loop *loop; + int step, run; + bool quit; +}; + +static uint64_t reenter(void *ud, uint64_t id, uint64_t interval_ns) +{ + (void)id; (void)interval_ns; + struct reentry *r = ud; + r->step = yasl_loop_step(r->loop, 0); + r->run = yasl_loop_run(r->loop); + if (r->quit) + yasl_loop_quit(r->loop); + return 0; +} + +static void *step_elsewhere(void *ud) +{ + return (void *)(intptr_t)yasl_loop_step(ud, 0); +} + +static void test_reentry_and_quit(void) +{ + yasl_loop *l = yasl_loop_create(); + assert(l); + /* A callback cannot step its own loop; the nested run keeps the outer quit. */ + struct reentry r = {.loop = l, .quit = true}; + assert(yasl_loop_add_timer(l, 0, reenter, &r)); + assert(yasl_loop_run(l) == 0); + assert(r.step == -EBUSY && r.run == -EBUSY); + + /* A quit before the run ends it at once, and the run clears it. */ + yasl_loop_quit(l); + assert(yasl_loop_run(l) == 0); + r = (struct reentry){.loop = l, .quit = true}; + assert(yasl_loop_add_timer(l, YASL_PRIVATE_MS_TO_NS(5), reenter, &r)); + assert(yasl_loop_run(l) == 0 && r.step == -EBUSY); + + /* Only the creating thread may step. */ + pthread_t t; + void *result; + assert(pthread_create(&t, NULL, step_elsewhere, l) == 0); + pthread_join(t, &result); + assert((intptr_t)result == -EPERM); + assert(yasl_loop_destroy(l) == 0); +} + +static void on_interrupt(int signo) +{ + (void)signo; +} + +struct interrupter { + pthread_t target; + int delay_ms; +}; + +static void *interrupt_thread(void *ud) +{ + struct interrupter *i = ud; + sleep_ms(i->delay_ms); + pthread_kill(i->target, SIGUSR2); + return NULL; +} + +/* An interrupted wait resumes with the time left, not the whole timeout. */ +static void test_interrupted_wait(void) +{ + struct sigaction sa = {.sa_handler = on_interrupt}; + sigemptyset(&sa.sa_mask); + struct sigaction old; + assert(sigaction(SIGUSR2, &sa, &old) == 0); + yasl_loop *l = yasl_loop_create(); + assert(l); + struct interrupter i = {.target = pthread_self(), .delay_ms = 60}; + pthread_t t; + uint64_t start = yasl_time_ns(); + assert(pthread_create(&t, NULL, interrupt_thread, &i) == 0); + assert(yasl_loop_step(l, 100) == 0); + uint64_t elapsed_ms = (yasl_time_ns() - start) / 1000000u; + pthread_join(t, NULL); + assert(elapsed_ms >= 90 && elapsed_ms < 140); + assert(yasl_loop_destroy(l) == 0); + sigaction(SIGUSR2, &old, NULL); +} + int main(void) { signal(SIGALRM, on_alarm); @@ -243,6 +376,9 @@ int main(void) test_post_order(); test_cross_thread(); test_signals(); + test_fds(); + test_reentry_and_quit(); + test_interrupted_wait(); printf("Loop tests passed.\n"); return 0; } diff --git a/tests/common/test_surface_integration.c b/tests/common/test_surface_integration.c index f33e35b..7862bfc 100644 --- a/tests/common/test_surface_integration.c +++ b/tests/common/test_surface_integration.c @@ -34,6 +34,102 @@ static void *wrong_thread(void *data) assert(yasl_surface_get_native(data, &native) == -EPERM && !native.wl_display && !native.x11_display); return NULL; } +struct callback_state { + yasl_loop *loop; + yasl_surface *window; + int refreshes; + int wait_result, shutdown_result; + yasl_surface *made; + int made_destroy; +}; +static void on_event(void *userdata, const yasl_event *e) +{ + struct callback_state *st = userdata; + if (e->type == YASL_EVENT_REFRESH && e->surface == st->window) { + st->refreshes++; + int w = 0, h = 0; + uint32_t *pixels = yasl_cpu_pixels(e->surface, &w, &h); + assert(pixels && w > 0 && h > 0); + pixels[0] = 0xff336699u; + assert(yasl_cpu_present(e->surface) == 0); + } + yasl_event inner; + st->wait_result = yasl_wait_event(&inner, 0); + st->shutdown_result = yasl_shutdown(); +} +/* A loop callback runs with the display idle, so surface creation and its + * roundtrips are allowed there. */ +static uint64_t create_in_timer(void *userdata, uint64_t id, uint64_t delay_ns) +{ + (void)id; (void)delay_ns; + struct callback_state *st = userdata; + st->made = yasl_surface_create(&(struct yasl_surface_options){ + .title = "made by a timer", .width = 64, .height = 64}); + assert(st->made); + st->made_destroy = yasl_surface_destroy(st->made); + return 0; +} +static uint64_t quit_in_timer(void *userdata, uint64_t id, uint64_t delay_ns) +{ + (void)id; (void)delay_ns; + yasl_loop_quit(userdata); + return 0; +} +static void step_until(struct callback_state *st, const int *counter, int want) +{ + uint64_t deadline = yasl_time_ns() + 5000000000ull; + while (*counter < want && yasl_time_ns() < deadline) + assert(yasl_loop_step(st->loop, 50) >= 0); + assert(*counter >= want); +} +static void test_callback_mode(enum yasl_backend backend) +{ + struct callback_state st = {.loop = yasl_loop_create()}; + assert(st.loop); + const struct yasl_options options = { + .backend = backend, .loop = st.loop, .on_event = on_event, .userdata = &st}; + assert(yasl_init(&options) == 0); + assert(yasl_init(&options) == -EALREADY); + assert(yasl_loop_destroy(st.loop) == -EBUSY); + yasl_event e; + assert(yasl_wait_event(&e, 0) == -EBUSY); + st.window = yasl_surface_create(&(struct yasl_surface_options){ + .title = "callback mode", .width = 96, .height = 64}); + assert(st.window); + + /* Events reach on_event while the application only steps its loop. */ + step_until(&st, &st.refreshes, 1); + assert(st.wait_result == -EBUSY && st.shutdown_result == -EBUSY); + + /* Frame requests coalesce into one REFRESH, sent once the window can + * show a frame. */ + int before = st.refreshes; + assert(yasl_surface_request_frame(st.window) == 0); + assert(yasl_surface_request_frame(st.window) == 0); + step_until(&st, &st.refreshes, before + 1); + for (int i = 0; i < 4; i++) + assert(yasl_loop_step(st.loop, 25) >= 0); + assert(st.refreshes == before + 1); + + /* An idle loop waits instead of spinning. */ + int steps = 0; + uint64_t end = yasl_time_ns() + 300000000ull; + while (yasl_time_ns() < end) { + assert(yasl_loop_step(st.loop, 100) >= 0); + steps++; + } + assert(steps < 30); + + assert(yasl_loop_add_timer(st.loop, 0, create_in_timer, &st)); + while (!st.made) + assert(yasl_loop_step(st.loop, 50) >= 0); + assert(st.made_destroy == 0); + + assert(yasl_loop_add_timer(st.loop, YASL_PRIVATE_MS_TO_NS(10), quit_in_timer, st.loop)); + assert(yasl_loop_run(st.loop) == 0); + assert(yasl_shutdown() == 0); + assert(yasl_loop_destroy(st.loop) == 0); +} static void push(yasl_surface *s, struct yasl_private_event event) { event.surface = s; @@ -68,8 +164,11 @@ int main(int argc, char **argv) enum yasl_backend backend = !strcmp(argv[1], "wayland") ? YASL_BACKEND_WAYLAND : !strcmp(argv[1], "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA; - assert(yasl_init(backend, "org.yasl.surface-test") == 0); - assert(yasl_init(backend, NULL) == -EALREADY); + const struct yasl_options options = {.backend = backend, .app_id = "org.yasl.surface-test"}; + assert(yasl_init(&options) == 0); + assert(yasl_init(&options) == -EALREADY); + /* Other options would need another display. */ + assert(yasl_init(&(struct yasl_options){.backend = backend}) == -EBUSY); assert(yasl_get_backend() == backend); unsigned features = yasl_get_features(); yasl_surface *s = yasl_surface_create( @@ -329,16 +428,17 @@ int main(int argc, char **argv) assert(yasl_surface_destroy(s) == 0); if (popup) assert(!listed(display, popup_id)); - assert(yasl_init(backend, NULL) == -EALREADY); + assert(yasl_init(&options) == -EALREADY); s = yasl_surface_create(&(struct yasl_surface_options){.title = "shared backend remains", .width = 64, .height = 64}); assert(s); yasl_shutdown(); - assert(yasl_init(backend, NULL) == 0); + assert(yasl_init(&(struct yasl_options){.backend = backend}) == 0); s = yasl_surface_create( &(struct yasl_surface_options){.title = "reinitialized", .width = 64, .height = 64}); assert(s); yasl_shutdown(); - puts("public surface lifecycle, pixels, ownership, input, wake and refresh passed"); + test_callback_mode(backend); + puts("public surface lifecycle, pixels, ownership, input, wake, refresh and callback mode passed"); return 0; } diff --git a/tests/common/test_timers.c b/tests/common/test_timers.c index 2c3542f..3a35819 100644 --- a/tests/common/test_timers.c +++ b/tests/common/test_timers.c @@ -3,15 +3,17 @@ #include "common/types.h" #include +#include #include #include +#include struct test_ctx { int count; uint64_t last_time; yasl_private_timer_id id; yasl_private_timer_id other_id; - struct yasl_private_loop *loop; + struct yasl_loop *loop; struct yasl_surface *surface; bool done; }; @@ -21,11 +23,32 @@ static uint64_t oneshot_cb(void *ud, yasl_private_timer_id id, uint64_t interval { struct test_ctx *ctx = ud; assert(id == ctx->id); - assert(interval_ns > 0); + (void)interval_ns; ctx->count++; return 0; /* Disarm */ } +static uint64_t count_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) +{ + (void)id; (void)interval_ns; + (*(int *)ud)++; + return 0; +} + +/* Adds enough timers to move the timer array, then repeats itself. */ +static uint64_t grow_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) +{ + struct test_ctx *ctx = ud; + assert(id == ctx->id); + if (ctx->count++ == 0) { + for (int i = 0; i < 64; i++) + assert(yasl_loop_add_timer(ctx->loop, YASL_PRIVATE_MS_TO_NS(60000), count_cb, NULL)); + return YASL_PRIVATE_MS_TO_NS(2); + } + assert(interval_ns == YASL_PRIVATE_MS_TO_NS(2)); + return 0; +} + /* 2. Repeating timer: ticks N times, then returns 0. */ static uint64_t repeat_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { @@ -73,7 +96,7 @@ static uint64_t parent_timer_cb(void *ud, yasl_private_timer_id id, uint64_t int struct test_ctx *ctx = ud; assert(id == ctx->id); ctx->count += 1; - ctx->other_id = yasl_private_loop_add_timer(ctx->loop, 1000000ull, child_timer_cb, ctx); + ctx->other_id = yasl_loop_add_timer(ctx->loop, 1000000ull, child_timer_cb, ctx); assert(ctx->other_id > 0); return 0; } @@ -91,7 +114,7 @@ static uint64_t killer_timer_cb(void *ud, yasl_private_timer_id id, uint64_t int (void)id; (void)interval_ns; struct test_ctx *ctx = ud; ctx->count++; - assert(yasl_private_loop_del_timer(ctx->loop, ctx->other_id)); + assert(yasl_loop_del_timer(ctx->loop, ctx->other_id)); return 0; } @@ -111,90 +134,109 @@ static uint64_t ms_timer_cb(void *ud, yasl_private_timer_id id, uint64_t interva static void test_loop_timers(void) { - struct yasl_private_loop *l = yasl_private_loop_create(); + struct yasl_loop *l = yasl_loop_create(); assert(l); /* Invalid arguments */ - assert(yasl_private_loop_add_timer(NULL, 1000, oneshot_cb, NULL) == 0); - assert(yasl_private_loop_add_timer(l, 0, oneshot_cb, NULL) == 0); - assert(yasl_private_loop_add_timer(l, 1000, NULL, NULL) == 0); - assert(!yasl_private_loop_del_timer(NULL, 1)); - assert(!yasl_private_loop_del_timer(l, 0)); - assert(!yasl_private_loop_del_timer(l, 99999)); + assert(yasl_loop_add_timer(NULL, 1000, oneshot_cb, NULL) == 0); + assert(yasl_loop_add_timer(l, 1000, NULL, NULL) == 0); + assert(!yasl_loop_del_timer(NULL, 1)); + assert(!yasl_loop_del_timer(l, 0)); + assert(!yasl_loop_del_timer(l, 99999)); assert(yasl_private_loop_next_timeout_ms(l) == -1); /* Test 1: Single-shot */ struct test_ctx ctx1 = { .loop = l }; - ctx1.id = yasl_private_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx1); + ctx1.id = yasl_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx1); assert(ctx1.id > 0); assert(yasl_private_loop_next_timeout_ms(l) >= 0); while (ctx1.count == 0) { - int handled = yasl_private_loop_step(l, 50); + int handled = yasl_loop_step(l, 50); assert(handled >= 0); } assert(ctx1.count == 1); /* Should be disarmed and removed */ - assert(!yasl_private_loop_del_timer(l, ctx1.id)); + assert(!yasl_loop_del_timer(l, ctx1.id)); assert(yasl_private_loop_next_timeout_ms(l) == -1); /* Test 2: Repeating */ struct test_ctx ctx2 = { .loop = l }; - ctx2.id = yasl_private_loop_add_timer(l, 2000000ull, repeat_cb, &ctx2); + ctx2.id = yasl_loop_add_timer(l, 2000000ull, repeat_cb, &ctx2); assert(ctx2.id > 0); while (!ctx2.done) { - yasl_private_loop_step(l, 50); + yasl_loop_step(l, 50); } assert(ctx2.count == 5); - assert(!yasl_private_loop_del_timer(l, ctx2.id)); + assert(!yasl_loop_del_timer(l, ctx2.id)); /* Test 3: Reschedule dynamically */ struct test_ctx ctx3 = { .loop = l }; - ctx3.id = yasl_private_loop_add_timer(l, 2000000ull, reschedule_cb, &ctx3); + ctx3.id = yasl_loop_add_timer(l, 2000000ull, reschedule_cb, &ctx3); assert(ctx3.id > 0); while (!ctx3.done) { - yasl_private_loop_step(l, 50); + yasl_loop_step(l, 50); } assert(ctx3.count == 2); /* Test 4: Timer cancellation before firing */ struct test_ctx ctx4 = { .loop = l }; - yasl_private_timer_id cancel_id = yasl_private_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx4); + yasl_private_timer_id cancel_id = yasl_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx4); assert(cancel_id > 0); - assert(yasl_private_loop_del_timer(l, cancel_id)); - assert(!yasl_private_loop_del_timer(l, cancel_id)); + assert(yasl_loop_del_timer(l, cancel_id)); + assert(!yasl_loop_del_timer(l, cancel_id)); /* Step for 10ms; timer should not fire */ - yasl_private_loop_step(l, 10); + yasl_loop_step(l, 10); assert(ctx4.count == 0); /* Test 5: Child timer added inside callback */ struct test_ctx ctx5 = { .loop = l }; - ctx5.id = yasl_private_loop_add_timer(l, 2000000ull, parent_timer_cb, &ctx5); + ctx5.id = yasl_loop_add_timer(l, 2000000ull, parent_timer_cb, &ctx5); while (ctx5.count < 11) { - yasl_private_loop_step(l, 50); + yasl_loop_step(l, 50); } assert(ctx5.count == 11); /* Test 6: Killer timer deletes victim timer before victim fires */ struct test_ctx ctx6 = { .loop = l }; - ctx6.id = yasl_private_loop_add_timer(l, 1000000ull, killer_timer_cb, &ctx6); - ctx6.other_id = yasl_private_loop_add_timer(l, 10000000ull, victim_timer_cb, &ctx6); + ctx6.id = yasl_loop_add_timer(l, 1000000ull, killer_timer_cb, &ctx6); + ctx6.other_id = yasl_loop_add_timer(l, 10000000ull, victim_timer_cb, &ctx6); while (ctx6.count == 0) { - yasl_private_loop_step(l, 50); + yasl_loop_step(l, 50); } assert(ctx6.count == 1); - yasl_private_loop_step(l, 20); + yasl_loop_step(l, 20); + + /* Test 8: Zero delay runs in the next step without waiting, and IDs are + * never handed out again. */ + int zero_runs = 0; + yasl_private_timer_id zero = yasl_loop_add_timer(l, 0, count_cb, &zero_runs); + assert(zero > 0 && yasl_private_loop_next_timeout_ms(l) == 0); + assert(yasl_loop_step(l, -1) == 1 && zero_runs == 1); + yasl_private_timer_id next = yasl_loop_add_timer(l, 0, count_cb, &zero_runs); + assert(next > zero); + assert(!yasl_loop_del_timer(l, zero) && yasl_loop_del_timer(l, next)); + + /* Test 9: A callback that grows the timer array still reschedules itself. */ + struct yasl_loop *grown = yasl_loop_create(); + assert(grown); + struct test_ctx ctx9 = { .loop = grown }; + ctx9.id = yasl_loop_add_timer(grown, 0, grow_cb, &ctx9); + while (ctx9.count < 2) + assert(yasl_loop_step(grown, 50) >= 0); + assert(!yasl_private_loop_timer_armed(grown, ctx9.id)); + assert(yasl_loop_destroy(grown) == 0); /* Test 7: Millisecond conversion */ struct test_ctx ctx7 = { .loop = l }; - ctx7.id = yasl_private_loop_add_timer(l, YASL_PRIVATE_MS_TO_NS(5), ms_timer_cb, &ctx7); + ctx7.id = yasl_loop_add_timer(l, YASL_PRIVATE_MS_TO_NS(5), ms_timer_cb, &ctx7); assert(ctx7.id > 0); while (!ctx7.done) { - yasl_private_loop_step(l, 50); + yasl_loop_step(l, 50); } assert(ctx7.count == 3); - yasl_private_loop_destroy(l); + yasl_loop_destroy(l); } /* Surface timer tests */ @@ -220,14 +262,14 @@ static uint64_t surface_suicide_cb(void *ud, yasl_private_timer_id id, uint64_t static void test_surface_timers(void) { - struct yasl_private_loop *loop = yasl_private_loop_create(); + struct yasl_loop *loop = yasl_loop_create(); assert(loop); struct yasl_private_display_spec spec = { .app_id = "org.yasl.timer_test" }; struct yasl_private_display *d = yasl_private_display_open(loop, &spec); if (!d) { /* Headless without display server */ - yasl_private_loop_destroy(loop); + yasl_loop_destroy(loop); return; } @@ -243,10 +285,10 @@ static void test_surface_timers(void) /* Application timers use the display's loop. */ struct test_ctx dctx = {0}; - dctx.id = yasl_private_loop_add_timer(yasl_private_display_loop(d), 2000000ull, oneshot_cb, &dctx); + dctx.id = yasl_loop_add_timer(yasl_private_display_loop(d), 2000000ull, oneshot_cb, &dctx); assert(dctx.id > 0); while (dctx.count == 0) { - yasl_private_display_dispatch(d, 50); + yasl_loop_step(loop, 50); } assert(dctx.count == 1); @@ -255,12 +297,12 @@ static void test_surface_timers(void) sctx.id = yasl_private_surface_add_timer(s, 2000000ull, surface_timer_repeat_cb, &sctx); assert(sctx.id > 0); while (sctx.count < 3) { - yasl_private_display_dispatch(d, 50); + yasl_loop_step(loop, 50); } assert(sctx.count >= 3); assert(yasl_private_surface_del_timer(s, sctx.id)); int count_before = sctx.count; - yasl_private_display_dispatch(d, 10); + yasl_loop_step(loop, 10); assert(sctx.count == count_before); /* A fired one-shot surface timer is retired, as a loop timer is. Many @@ -270,7 +312,7 @@ static void test_surface_timers(void) octx.id = yasl_private_surface_add_timer(s, 1000000ull, oneshot_cb, &octx); assert(octx.id > 0); while (octx.count == 0) - yasl_private_display_dispatch(d, 50); + yasl_loop_step(loop, 50); assert(!yasl_private_surface_del_timer(s, octx.id)); } @@ -280,7 +322,7 @@ static void test_surface_timers(void) assert(sctx2.id > 0); yasl_private_surface_destroy(s); /* Step display/loop; timer must not fire on dead surface */ - yasl_private_display_dispatch(d, 10); + yasl_loop_step(loop, 10); assert(sctx2.count == 0); /* Surface suicide from timer callback */ @@ -290,17 +332,19 @@ static void test_surface_timers(void) sctx3.id = yasl_private_surface_add_timer(s, 2000000ull, surface_suicide_cb, &sctx3); assert(sctx3.id > 0); while (sctx3.count == 0) { - yasl_private_display_dispatch(d, 50); + yasl_loop_step(loop, 50); } assert(sctx3.count == 1); assert(sctx3.surface == NULL); yasl_private_display_close(d); - yasl_private_loop_destroy(loop); + yasl_loop_destroy(loop); } int main(void) { + /* A wait that never ends fails here, not at the suite timeout. */ + alarm(10); test_loop_timers(); test_surface_timers(); printf("Timer tests passed successfully.\n"); diff --git a/tests/common/test_vulkan.c b/tests/common/test_vulkan.c index 4633d55..80f9cbe 100644 --- a/tests/common/test_vulkan.c +++ b/tests/common/test_vulkan.c @@ -41,7 +41,7 @@ static int run_test(enum yasl_backend backend) VkSemaphore acquired = VK_NULL_HANDLE, rendered = VK_NULL_HANDLE; VkCommandBuffer command = VK_NULL_HANDLE; - if (yasl_init(backend, NULL) < 0) { + if (yasl_init(&(struct yasl_options){.backend = backend}) < 0) { fprintf(stderr, "vulkan integration: cannot select requested backend %d (YASL error %d)\n", backend, errno); goto cleanup; diff --git a/tests/test_lifecycle.c b/tests/test_lifecycle.c index 41dafe6..7ba3f89 100644 --- a/tests/test_lifecycle.c +++ b/tests/test_lifecycle.c @@ -1,6 +1,11 @@ #include #include #include +static void on_event(void *userdata, const yasl_event *event) +{ + (void)userdata; + (void)event; +} int main(void) { assert(yasl_shutdown() == 0); @@ -8,7 +13,14 @@ int main(void) yasl_event event = {.type = YASL_EVENT_QUIT}; assert(yasl_wait_event(&event, 0) == -ENODEV && event.type == YASL_EVENT_NONE); assert(yasl_wait_event(NULL, 0) == -EINVAL); - assert(yasl_init((enum yasl_backend)999, NULL) == -EINVAL); + assert(yasl_init(&(struct yasl_options){.backend = (enum yasl_backend)999}) == -EINVAL); + /* The loop and its event callback come together or not at all. */ + yasl_loop *loop = yasl_loop_create(); + assert(loop); + assert(yasl_init(&(struct yasl_options){.loop = loop}) == -EINVAL); + assert(yasl_init(&(struct yasl_options){.on_event = on_event}) == -EINVAL); + assert(yasl_loop_destroy(loop) == 0); + assert(yasl_surface_request_frame(NULL) == -ENODEV); assert(yasl_get_backend() == YASL_BACKEND_AUTO && yasl_get_features() == 0); assert(!yasl_surface_create(NULL) && errno == EINVAL); struct yasl_surface_options options = {.width = 0, .height = 64}; diff --git a/tests/wayland/test_clipboard.c b/tests/wayland/test_clipboard.c index 0ee74b6..f1427ab 100644 --- a/tests/wayland/test_clipboard.c +++ b/tests/wayland/test_clipboard.c @@ -39,7 +39,7 @@ static void receive(const char *text, size_t len, bool uri_list, int main(void) { struct received received = {0}; - struct clipboard c = {.loop = yasl_private_loop_create(), .receive = receive, .ud = &received}; + struct clipboard c = {.loop = yasl_loop_create(), .receive = receive, .ud = &received}; received.clipboard = &c; assert(c.loop); const struct { const char *bytes; size_t len; bool valid; } cases[] = { @@ -62,7 +62,7 @@ int main(void) assert(write(fds[1], cases[i].bytes, cases[i].len) == (ssize_t)cases[i].len); close(fds[1]); for (int steps = 0; c.transfers && steps < 10; steps++) - assert(yasl_private_loop_step(c.loop, 0) >= 0); + assert(yasl_loop_step(c.loop, 0) >= 0); assert(!c.transfers); assert(!yasl_private_loop_timer_armed(c.loop, timeout_id)); assert(received.count == 1); @@ -100,11 +100,11 @@ int main(void) struct wl_surface *timeout_target = (struct wl_surface *)(uintptr_t)2; timed->drop_surface = timeout_target; yasl_private_timer_id timeout_id = timed->timeout; - assert(yasl_private_loop_del_timer(c.loop, timeout_id)); + assert(yasl_loop_del_timer(c.loop, timeout_id)); (void)transfer_timeout(timed, timeout_id, CLIPBOARD_TRANSFER_TIMEOUT_NS); close(timeout_fds[1]); assert(!c.transfers && received.count == 1); assert(received.request_id == 100 && received.status == YASL_PRIVATE_TRANSFER_FAILED); - yasl_private_loop_destroy(c.loop); + yasl_loop_destroy(c.loop); return 0; } diff --git a/tests/wayland/test_reconnect.c b/tests/wayland/test_reconnect.c index 891d98b..6bd6967 100644 --- a/tests/wayland/test_reconnect.c +++ b/tests/wayland/test_reconnect.c @@ -55,7 +55,7 @@ static VkSurfaceKHR vulkan_surface(void) int main(void) { - CHECK(yasl_init(YASL_BACKEND_WAYLAND, NULL) == 0, "selecting Wayland"); + CHECK(yasl_init(&(struct yasl_options){.backend = YASL_BACKEND_WAYLAND}) == 0, "selecting Wayland"); cpu = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect CPU", .width=160, .height=120}); CHECK(cpu, "creating CPU window"); gl = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect GL", .width=160, .height=120, .type=YASL_SURFACE_GL}); @@ -84,7 +84,8 @@ int main(void) puts("ready"); fflush(stdout); - expect(YASL_EVENT_DISPLAY_LOST, NULL, 10000); + yasl_event lost = expect(YASL_EVENT_DISPLAY_LOST, NULL, 10000); + CHECK(lost.action == YASL_LOST_RECONNECTING, "reporting a loss that reconnects"); struct yasl_native native; CHECK(yasl_surface_get_native(cpu, &native) == -ENOTCONN && !native.wl_display && !native.wl_surface, "rejecting native handles while offline"); diff --git a/tests/x11/test_input.c b/tests/x11/test_input.c index aa534ca..b2b79d7 100644 --- a/tests/x11/test_input.c +++ b/tests/x11/test_input.c @@ -62,7 +62,7 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface * 0, 0, 1, 1, 0, 0, 0); pending->target = s; pending->id = (uint64_t)i + 1; - pending->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, + pending->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, pending); assert(pending->timeout); pending->next = d->pastes; @@ -89,7 +89,7 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface * incr->window = XCreateSimpleWindow(d->native, DefaultRootWindow(d->native), 0, 0, 1, 1, 0, 0, 0); incr->target = s; incr->id = 9; - incr->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, incr); + incr->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, incr); assert(incr->timeout); incr->next = d->pastes; d->pastes = incr; @@ -193,7 +193,7 @@ static void check_request_completion(struct x11_display *d, struct x11_surface * assert(request_paste(&d->base, YASL_PRIVATE_CLIPBOARD, &s->base, 103)); struct paste_request *timed = d->pastes; assert(timed && timed->id == 103 && timed->timeout); - assert(yasl_private_loop_del_timer(d->loop, timed->timeout)); + assert(yasl_loop_del_timer(d->loop, timed->timeout)); timed->timeout = 0; paste_timeout(timed, 0, PASTE_TIMEOUT_NS); event = wait_paste(&d->base, 103); @@ -207,7 +207,7 @@ static void check_request_completion(struct x11_display *d, struct x11_surface * yasl_private_timer_id timeout = d->pastes->timeout; yasl_private_surface_destroy(cancel_target); assert(d->pastes && !d->pastes->target && d->pastes->id == 0); - assert(yasl_private_loop_del_timer(d->loop, timeout)); + assert(yasl_loop_del_timer(d->loop, timeout)); d->pastes->timeout = 0; XSelectionEvent late_reply = { .requestor = d->pastes->window, diff --git a/tests/x11/test_tablet_native.c b/tests/x11/test_tablet_native.c index bbe39f2..8d44232 100644 --- a/tests/x11/test_tablet_native.c +++ b/tests/x11/test_tablet_native.c @@ -140,7 +140,7 @@ int main(int argc, char **argv) { assert(argc == 3); /* pen socket, eraser socket */ struct canvas_state app = {0}; - assert(yasl_init(YASL_BACKEND_X11, NULL) == 0); + assert(yasl_init(&(struct yasl_options){.backend = YASL_BACKEND_X11}) == 0); yasl_surface *surface = yasl_surface_create(&(struct yasl_surface_options){.title="YASL native pressure test", .width=500, .height=400}); assert(surface && (yasl_get_features() & YASL_FEATURE_TABLET)); struct yasl_native native; -- cgit v1.2.3