diff options
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 <poll.h>, for example POLLIN or POLLOUT. - * @param fn Callback receiving poll revents (including errors/hangup); return value is ignored. - * @param ud Borrowed userdata until watch removal or loop destruction. - * @return Positive watch ID, or -1 on failure. - * Delete the watch before closing fd. The callback must consume readiness or - * change/remove its watch to avoid repeated immediate wakeups. - */ -int yasl_private_loop_add_fd(struct yasl_private_loop *l, int fd, short events, - int (*fn)(int fd, short revents, void *ud), void *ud); -/** - * Remove a file watch without closing its descriptor. - * @param l Loop. - * @param id Watch ID returned by yasl_private_loop_add_fd(). - * @return Zero if removed, or -1 for an invalid/missing watch. - */ -int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id); -/** - * Replace a watch's poll event mask. - * @param l Loop. - * @param id Watch ID. - * @param events New <poll.h> mask; zero disables requested readiness (errors may still arrive). - * @return Zero if updated, or -1 for an invalid/missing watch. - */ -int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events); - -/* Timers use nanoseconds. Return the next interval to repeat/reschedule, or zero - * to remove the timer. A zero ID indicates failure. Loop timers live until they - * stop, are deleted, or the loop is destroyed. Surface timers are also canceled - * when their surface is destroyed. */ -/** Timer identity scoped to its loop. Zero is invalid; do not reuse a retired ID. */ -typedef uint32_t yasl_private_timer_id; +/** Timer identity scoped to its loop. Zero is invalid; IDs are never reissued. */ +typedef uint64_t yasl_private_timer_id; /** Timer callback. interval_ns is the last scheduled delay; return the next delay or zero to stop. */ typedef uint64_t (*yasl_private_timer_cb)(void *ud, yasl_private_timer_id id, uint64_t interval_ns); @@ -170,29 +24,60 @@ typedef uint64_t (*yasl_private_timer_cb)(void *ud, yasl_private_timer_id id, ui #define YASL_PRIVATE_MS_TO_NS(ms) ((uint64_t)(ms) * 1000000ull) /** - * Schedule a timer on a loop. - * @param l Loop. - * @param interval_ns Nonzero delay in monotonic nanoseconds before first firing. - * @param cb Required callback; returns a new nonzero interval to repeat, or zero to stop. - * @param ud Borrowed userdata until the timer stops, is deleted, or the loop is destroyed. - * @return Nonzero timer ID, or zero for invalid arguments or allocation failure. - * Repeating timers coalesce missed deadlines; do not use them to count elapsed - * wall time. The callback receives the last scheduled interval, not elapsed time. + * How a display drives the loop it runs on. Each step runs prepare, the wait, + * check, deliver, then descriptor callbacks and timers. + * prepare: dispatch what the backend already holds, draw due surfaces, flush + * requests, and arm the read. True when progress is possible now, so the + * wait must not block. + * wait: replaces poll(2) for platforms whose events do not arrive on a + * descriptor; NULL uses poll. The loop reads descriptor readiness after it. + * check: runs after every wait, failed or not, before any callback; it closes + * what prepare opened. + * deliver: optional; hands queued events to the application. + */ +struct yasl_private_loop_driver { + bool (*prepare)(void *ud); + int (*wait)(void *ud, int timeout_ms); + void (*check)(void *ud); + void (*deliver)(void *ud); + void *ud; +}; +/** Install a driver, or remove it with NULL. False with EBUSY when one is set. */ +bool yasl_private_loop_set_driver(yasl_loop *l, const struct yasl_private_loop_driver *driver); +/** True on the thread that created the loop. */ +bool yasl_private_loop_owned(const yasl_loop *l); +/** True while a step runs. */ +bool yasl_private_loop_stepping(const yasl_loop *l); +/** The revents the last wait reported for watch id, until its callback runs. */ +short yasl_private_loop_revents(const yasl_loop *l, int id); + +/** Wake a waiting loop from any thread or a signal handler. Kicks coalesce. */ +void yasl_private_loop_kick(yasl_loop *l); +/** Kick, but only from another thread: the owner's next step sees its own work. */ +void yasl_private_loop_wake(yasl_loop *l); +/** + * Run fn on the loop's owning thread during a later step. Any thread, not a + * signal handler. False for NULL arguments or allocation failure. Functions run + * in posting order; one posted while posts run waits for a later step. */ -yasl_private_timer_id yasl_private_loop_add_timer(struct yasl_private_loop *l, uint64_t interval_ns, - yasl_private_timer_cb cb, void *ud); +bool yasl_private_loop_post(yasl_loop *l, void (*fn)(void *ud), void *ud); +/** CLOCK_MONOTONIC nanoseconds. Any thread. */ +uint64_t yasl_private_loop_now_ns(void); + /** - * Cancel a loop timer, including from its own callback. - * @param l Loop. - * @param id Timer ID. - * @return true if an armed timer was removed, false if invalid or already retired. + * Get the loop used for a display's file watches and application timers. + * @param d Display, or NULL. + * @return Borrowed loop, or NULL. Do not destroy a display-owned loop yourself. */ -bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id); +yasl_loop *yasl_private_display_loop(struct yasl_private_display *d); + +/** Whether id is armed: added, not deleted, and not retired by its callback. */ +bool yasl_private_loop_timer_armed(const yasl_loop *l, yasl_private_timer_id id); /** * Schedule a timer canceled automatically on surface destruction. * @param s Surface whose display loop will run the timer. - * @param interval_ns Nonzero initial delay in nanoseconds. - * @param cb Required timer callback, with the same rules as yasl_private_loop_add_timer(). + * @param interval_ns Initial delay in nanoseconds. + * @param cb Required timer callback, with the same rules as yasl_loop_add_timer(). * @param ud Borrowed userdata until the timer ends or the surface is destroyed. * @return Nonzero timer ID, or zero on failure. * The surface does not own userdata. Cancel with yasl_private_surface_del_timer(). diff --git a/src/common/surface.c b/src/common/surface.c index aeca3d8..5c29a10 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -13,9 +13,12 @@ static struct yasl_private_display *display; static pthread_t owner; static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER; static atomic_bool wake_requested; -static bool pumping; static unsigned refresh_depth; static char *delivered_text; /* Text of the last returned event; freed by the next wait. */ +/* The options of the open display; on_event set means callback mode. */ +static struct yasl_options active; +static char *active_app_id; +static bool queue_failure_reported; bool yasl_public_fail(int error) { @@ -62,20 +65,74 @@ bool yasl_public_connected(yasl_surface *s) /* Runs a private call that reports failure through errno. */ #define STATUS(call) (errno = 0, yasl_public_status((call) || yasl_public_fail(errno))) -int yasl_init(enum yasl_backend backend, const char *app_id) +static bool refresh_ready(yasl_surface *s); +static int take_event(yasl_event *out); +/* An owed REFRESH is an event the queue does not hold. */ +static bool public_pending(struct yasl_private_display *d) +{ + for (yasl_surface *s = d->surfaces; s; s = s->event_next) + if (s->refresh_owed && refresh_ready(s)) + return true; + return false; +} +/* Callback mode: runs in each loop step, after the display is read and before + * descriptor callbacks. Bounded, so a handler that keeps making events cannot + * starve the loop's other sources; the rest go in the next step, which does + * not wait while they are pending. */ +static void deliver_events(struct yasl_private_display *d) { - if (display) - return yasl_public_owner() ? yasl_public_error(EALREADY) : -errno; - if ((unsigned)backend > YASL_BACKEND_COCOA) + if (yasl_private_events_error(d) && !queue_failure_reported) { + queue_failure_reported = true; + active.on_event(active.userdata, &(yasl_event){ + .type = YASL_EVENT_DISPLAY_LOST, .action = YASL_LOST_FINAL, .time_ns = yasl_time_ns()}); + } + for (int i = 0; i < 256; i++) { + yasl_event event; + if (take_event(&event) <= 0) + break; + active.on_event(active.userdata, &event); + free(delivered_text); + delivered_text = NULL; + } +} +static bool same_options(const struct yasl_options *o) +{ + const char *app_id = o->app_id ? o->app_id : ""; + return o->backend == active.backend && o->loop == active.loop && + o->on_event == active.on_event && o->userdata == active.userdata && + !strcmp(app_id, active_app_id ? active_app_id : ""); +} +int yasl_init(const struct yasl_options *options) +{ + struct yasl_options o = options ? *options : (struct yasl_options){0}; + if (display) { + if (!yasl_public_owner()) + return -errno; + return yasl_public_error(same_options(&o) ? EALREADY : EBUSY); + } + if ((unsigned)o.backend > YASL_BACKEND_COCOA || !o.loop != !o.on_event) return yasl_public_error(EINVAL); + if (o.loop && !yasl_private_loop_owned(o.loop)) + return yasl_public_error(EPERM); + char *app_id = o.app_id ? strdup(o.app_id) : NULL; + if (o.app_id && !app_id) + return yasl_public_error(ENOMEM); struct yasl_private_display_spec spec = { - .app_id = app_id, - .backend = (enum yasl_private_backend)backend, + .app_id = o.app_id, + .backend = (enum yasl_private_backend)o.backend, }; errno = 0; - struct yasl_private_display *d = yasl_private_display_open(NULL, &spec); - if (!d) + struct yasl_private_display *d = yasl_private_display_open(o.loop, &spec); + if (!d) { + free(app_id); return yasl_public_error(errno); + } + d->pending = public_pending; + d->deliver = o.on_event ? deliver_events : NULL; + active = o; + active.app_id = NULL; + active_app_id = app_id; + queue_failure_reported = false; pthread_mutex_lock(&lifecycle_mutex); owner = pthread_self(); display = d; @@ -84,7 +141,7 @@ int yasl_init(enum yasl_backend backend, const char *app_id) } bool yasl_public_initialize(void) { - return display ? yasl_public_owner() : yasl_init(YASL_BACKEND_AUTO, NULL) == 0; + return display ? yasl_public_owner() : yasl_init(NULL) == 0; } static void close_display(void) { @@ -98,7 +155,7 @@ int yasl_shutdown(void) { if (display && !yasl_public_owner()) return -errno; - if (pumping || refresh_depth) + if (refresh_depth || (display && yasl_private_loop_stepping(display->loop))) return yasl_public_error(EBUSY); if (display) { for (yasl_surface *s = display->surfaces; s; s = s->event_next) @@ -110,6 +167,9 @@ int yasl_shutdown(void) } free(delivered_text); delivered_text = NULL; + free(active_app_id); + active_app_id = NULL; + active = (struct yasl_options){0}; atomic_store(&wake_requested, false); #if YASL_HAS_VULKAN extern void yasl_private_vulkan_reset(void); @@ -260,7 +320,7 @@ yasl_surface *yasl_surface_create(const struct yasl_surface_options *o) { if (!o || (unsigned)o->type > YASL_SURFACE_NATIVE || !size_valid(o->width, o->height)) return yasl_public_fail(EINVAL), NULL; - if (pumping || refresh_depth) + if (refresh_depth) return yasl_public_fail(EBUSY), NULL; unsigned flags = (o->type == YASL_SURFACE_CPU ? YASL_PRIVATE_SURFACE_CPU : 0u) | (o->undecorated ? YASL_PRIVATE_SURFACE_UNDECORATED : 0u) | @@ -387,6 +447,12 @@ int yasl_surface_request_size(yasl_surface *s, int w, int h) return yasl_public_error(EINVAL); return STATUS(yasl_private_surface_set_size(s, (struct yasl_private_vector2i){w, h})); } +int yasl_surface_request_frame(yasl_surface *s) +{ + if (!yasl_public_connected(s)) + return -errno; + return STATUS(yasl_private_surface_request_shown_frame(s)); +} int yasl_surface_set_cursor(yasl_surface *s, enum yasl_cursor cursor) { if (!yasl_public_surface(s, false)) @@ -409,7 +475,8 @@ void yasl_private_surface_notify_refresh(yasl_surface *s) { if (!s || !s->display) return; - if (s->refresh && s->display->modal && pumping && !refresh_depth) { + /* modal holds only inside the system's own loop, within the wait. */ + if (s->refresh && s->display->modal && !refresh_depth) { ++refresh_depth; s->refresh(s); --refresh_depth; @@ -703,6 +770,7 @@ static bool convert_event(const struct yasl_private_event *p, yasl_event *e) return true; case YASL_PRIVATE_EVENT_DISPLAY_LOST: e->type = YASL_EVENT_DISPLAY_LOST; + e->action = display->reconnects ? YASL_LOST_RECONNECTING : YASL_LOST_FINAL; return true; case YASL_PRIVATE_EVENT_DISPLAY_RESTORED: e->type = YASL_EVENT_DISPLAY_RESTORED; @@ -869,11 +937,14 @@ static int pump(yasl_event *out, int timeout) int n = take_event(out); if (n) return n; - yasl_private_display_flush(display); - if ((n = take_event(out))) - return n; + int error = yasl_private_events_error(display); + if (error) + return yasl_public_error(error); if (atomic_exchange(&wake_requested, false)) return 0; + /* A loss that does not reconnect ends the waits once its event is out. */ + if (atomic_load(&display->offline) && !display->reconnects) + return yasl_public_error(ENOTCONN); int wait = timeout; if (timeout > 0) { uint64_t now = yasl_time_ns(); @@ -881,11 +952,11 @@ static int pump(yasl_event *out, int timeout) return 0; wait = (int)((deadline - now + 999999u) / 1000000u); } - if (yasl_private_display_dispatch(display, wait) < 0) - return yasl_public_error(errno); - yasl_private_display_flush(display); - if ((n = take_event(out)) || timeout == 0) - return n; + int stepped = yasl_loop_step(display->loop, wait); + if (stepped < 0) + return yasl_public_error(-stepped); + if (timeout == 0) + return take_event(out); } } int yasl_wait_event(yasl_event *out, int timeout) @@ -895,13 +966,11 @@ int yasl_wait_event(yasl_event *out, int timeout) *out = (yasl_event){.type = YASL_EVENT_NONE}; if (!yasl_public_owner()) return -errno; - if (pumping || refresh_depth) + if (active.on_event || refresh_depth || yasl_private_loop_stepping(display->loop)) return yasl_public_error(EBUSY); free(delivered_text); delivered_text = NULL; - pumping = true; int n = pump(out, timeout); - pumping = false; if (n < 0) *out = (yasl_event){.type = YASL_EVENT_NONE}; return n; @@ -1014,5 +1083,8 @@ int yasl_gl_present(yasl_surface *s) return -errno; if (!display->ops->gl_swap_buffers) return yasl_public_error(ENOTSUP); - return STATUS(display->ops->gl_swap_buffers(display, s)); + int status = STATUS(display->ops->gl_swap_buffers(display, s)); + if (status == 0) + atomic_store(&s->presented_ns, yasl_private_loop_now_ns()); + return status; } diff --git a/src/common/types.h b/src/common/types.h index 3fffd47..8d00d0b 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -4,8 +4,9 @@ * * All calls and callbacks use the display's opening thread (the main thread on * Cocoa), unless explicitly documented otherwise. Userdata is borrowed; callback - * tables and creation specs are copied. Do not recursively dispatch/flush or - * destroy a display/loop while dispatch or a callback is active. Destroy child + * tables and creation specs are copied. The display drives its loop: each + * yasl_loop_step dispatches it. Do not recursively step or flush, or destroy a + * display/loop while a step or a callback is active. Destroy child * popups before parents, surfaces before displays, displays before borrowed loops. * Surface destruction from its own callback is supported; stop using it immediately. * @@ -43,7 +44,7 @@ extern "C" { struct yasl_private_display; struct yasl_surface; -struct yasl_private_loop; +struct yasl_loop; /* Geometry */ /** A two-dimensional integer size, position, or offset; units depend on the caller. */ @@ -569,7 +570,7 @@ struct yasl_private_display_spec { * opening fails without a more specific platform error. Explicit choices never * fall back. Close with yasl_private_display_close() after destroying its surfaces. */ -struct yasl_private_display *yasl_private_display_open(struct yasl_private_loop *loop, +struct yasl_private_display *yasl_private_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec); /** * Read the selected backend. @@ -592,19 +593,6 @@ void yasl_private_display_close(struct yasl_private_display *d); * permission to perform an operation at any arbitrary moment. */ uint32_t yasl_private_display_caps(const struct yasl_private_display *d); -/* Process events, waiting at most timeout_ms: zero polls, negative waits. - * Pending input or redraw work returns immediately. Returns -1 on failure. */ -/** - * Receive events and run ready loop sources. - * @param d Display to dispatch. - * @param timeout_ms Maximum wait in milliseconds: zero polls, negative waits indefinitely. - * @return A nonnegative backend-specific work count on success, or -1 with errno - * set on failure (EINVAL for NULL, EIO if no specific backend error is available). - * Do not interpret the count as the number of queued input events. Pending input - * or runnable redraw work prevents waiting. Drain input and call - * yasl_private_display_flush() after updating application state. Do not dispatch recursively. - */ -int yasl_private_display_dispatch(struct yasl_private_display *d, int timeout_ms); /** * Draw scheduled surfaces and send pending display requests. * @param d Display, or NULL for no action. @@ -886,6 +874,9 @@ bool yasl_private_frame_present(struct yasl_private_frame *frame, const struct yasl_private_rect2i *rects, size_t count); /** Coalesce a redraw request; use on the display thread. NULL is ignored. */ void yasl_private_surface_request_frame(struct yasl_surface *s); +/** Owe s a REFRESH once the system shows its frame in flight, or at once when + * none is. False with errno when the fallback timer cannot start. */ +bool yasl_private_surface_request_shown_frame(struct yasl_surface *s); /** Set drawing policy for either loop style. Returns false with errno on failure. */ bool yasl_private_surface_set_pacing(struct yasl_surface *s, yasl_private_pacing_mode mode); diff --git a/src/platform/cocoa/api.h b/src/platform/cocoa/api.h index 006954c..0660ffd 100644 --- a/src/platform/cocoa/api.h +++ b/src/platform/cocoa/api.h @@ -6,10 +6,11 @@ struct cocoa_display; struct cocoa_surface; -struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); +struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec); void cocoa_display_close(struct cocoa_display *d); uint32_t cocoa_display_caps(const struct cocoa_display *d); -int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms); +bool cocoa_display_prepare(struct cocoa_display *d); +int cocoa_display_wait(struct cocoa_display *d, int timeout_ms); void cocoa_display_flush(struct cocoa_display *d); int cocoa_display_modifiers(const struct cocoa_display *d); bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *origin, const struct yasl_private_drag_image *image, const struct yasl_private_drag_listener *l, void *ud); diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m index a78c2ce..c5022dd 100644 --- a/src/platform/cocoa/window.m +++ b/src/platform/cocoa/window.m @@ -129,7 +129,7 @@ struct cocoa_display { int count; int capacity; char *app_id; - struct yasl_private_loop *loop; + struct yasl_loop *loop; id key_up_monitor; NSCursor *hidden_cursor; YaslAppDelegate *delegate; @@ -1413,10 +1413,18 @@ bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_private_fr return true; } +/* render_and_commit draws only when this holds; readiness uses it too, so a + * window that cannot draw never keeps the loop from waiting. */ +static bool draw_possible(const struct cocoa_surface *w) +{ + return w->base.presenter == YASL_PRIVATE_PRESENTER_CPU && !w->destroyed && !w->closed && + w->cbs->draw && !w->rendering && w->size.x > 0 && w->size.y > 0 && + !(w->base.state & YASL_PRIVATE_STATE_SUSPENDED); +} + static void render_and_commit(struct cocoa_surface *w) { - if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU || w->destroyed || w->closed || !w->cbs->draw || w->rendering || w->size.x <= 0 || w->size.y <= 0 || - (w->base.state & YASL_PRIVATE_STATE_SUSPENDED)) + if (!draw_possible(w)) return; size_t count = (size_t)w->size.x * (size_t)w->size.y; @@ -1482,7 +1490,7 @@ static void loop_ready(CFFileDescriptorRef fd, CFOptionFlags flags, void *info) /* A kqueue mirroring the loop's poll set, or -1. complete is false when some * descriptor could not be registered. */ -static int loop_kqueue(struct yasl_private_loop *loop, bool *complete) +static int loop_kqueue(struct yasl_loop *loop, bool *complete) { *complete = true; int kq = kqueue(); @@ -1511,30 +1519,30 @@ static bool is_loop_wake(NSEvent *ev) return ev.type == NSEventTypeApplicationDefined && ev.subtype == LOOP_WAKE_SUBTYPE; } -int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) +/* Before the wait: draw due windows. True when a window can draw now. */ +bool cocoa_display_prepare(struct cocoa_display *d) { assert([NSThread isMainThread]); - if (!d || d->pumping) - return -1; + cocoa_display_flush(d); for (int i = 0; i < d->count; i++) { struct cocoa_surface *w = d->windows[i]; - if (w->input.count || w->input.reset_pending || - (w->want_draw && (!w->link || w->frame_due || w->unpaced))) { - timeout_ms = 0; - break; - } + if (w->want_draw && (!w->link || w->frame_due || w->unpaced) && draw_possible(w)) + return true; } + return false; +} + +/* The loop's wait. The loop has already shortened timeout_ms to its next + * timer, and reads descriptor readiness after this returns. */ +int cocoa_display_wait(struct cocoa_display *d, int timeout_ms) +{ + assert([NSThread isMainThread]); + if (d->pumping) + return -EBUSY; d->pumping = true; d->depth++; int count = 0; @try { - if (d->loop) { - int timer_to = yasl_private_loop_next_timeout_ms(d->loop); - if (timer_to >= 0 && (timeout_ms < 0 || timer_to < timeout_ms)) { - timeout_ms = timer_to; - } - } - /* AppKit waits on Mach ports, not on loop descriptors. While waiting, * a run loop source on a kqueue of the loop's descriptors posts a wake * event, so kicks, posts, signals, and watches end the wait. */ @@ -1592,10 +1600,6 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) dequeue:YES]; } } - - if (d->loop) { - yasl_private_loop_step(d->loop, 0); - } } @finally { d->depth--; d->pumping = false; @@ -1632,8 +1636,7 @@ void cocoa_surface_schedule_redraw(struct cocoa_surface *w) w->frame_due = true; w->link.paused = NO; } - if (darwin_ctx.loop) - yasl_private_loop_kick(darwin_ctx.loop); + yasl_private_loop_wake(darwin_ctx.loop); } static void app_init(const char *app_name) @@ -1688,7 +1691,7 @@ static void place_window(NSWindow *window) darwin_ctx.cascade = [window cascadeTopLeftFromPoint:darwin_ctx.cascade]; } -struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { if (![NSThread isMainThread]) { errno = EPERM; @@ -1701,7 +1704,7 @@ struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const s darwin_ctx.base.native_handle = (__bridge void *)[NSApplication sharedApplication]; darwin_ctx.open = true; if (!loop) { - darwin_ctx.loop = yasl_private_loop_create(); + darwin_ctx.loop = yasl_loop_create(); darwin_ctx.owns_loop = true; } else { darwin_ctx.loop = loop; @@ -1726,7 +1729,7 @@ void cocoa_display_close(struct cocoa_display *d) d->key_up_monitor = nil; } if (d->owns_loop && d->loop) { - yasl_private_loop_destroy(d->loop); + yasl_loop_destroy(d->loop); d->loop = NULL; d->base.loop = NULL; } @@ -2538,7 +2541,7 @@ struct yasl_private_timing cocoa_surface_timing(const struct cocoa_surface *w) return timing; } -static struct yasl_private_display *impl_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +static struct yasl_private_display *impl_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct cocoa_display *result = cocoa_display_open(loop, spec); return result ? &result->base : NULL; @@ -2554,9 +2557,19 @@ static uint32_t impl_display_caps(const struct yasl_private_display *d) return cocoa_display_caps((const struct cocoa_display *)d); } -static int impl_display_dispatch_timeout(struct yasl_private_display *d, int timeout_ms) +static bool impl_display_prepare(struct yasl_private_display *d) +{ + return cocoa_display_prepare((struct cocoa_display *)d); +} + +static int impl_display_wait(struct yasl_private_display *d, int timeout_ms) +{ + return cocoa_display_wait((struct cocoa_display *)d, timeout_ms); +} + +static void impl_display_check(struct yasl_private_display *d) { - return cocoa_display_dispatch_timeout((struct cocoa_display *)d, timeout_ms); + (void)d; } static void impl_display_flush(struct yasl_private_display *d) @@ -2787,7 +2800,9 @@ const struct yasl_private_backend_ops yasl_private_cocoa_backend = { .display_open = impl_display_open, .display_close = impl_display_close, .display_caps = impl_display_caps, - .display_dispatch_timeout = impl_display_dispatch_timeout, + .display_prepare = impl_display_prepare, + .display_wait = impl_display_wait, + .display_check = impl_display_check, .display_flush = impl_display_flush, .display_modifiers = impl_display_modifiers, .display_double_click_ms = impl_display_double_click_ms, diff --git a/src/platform/posix/loop.c b/src/platform/posix/loop.c index 49132f8..6624575 100644 --- a/src/platform/posix/loop.c +++ b/src/platform/posix/loop.c @@ -6,7 +6,9 @@ #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <poll.h> +#include <pthread.h> #include <signal.h> #include <stdatomic.h> #include <stdlib.h> @@ -16,33 +18,33 @@ #define YASL_PRIVATE_LOOP_INIT_CAP 32 -typedef int (*yasl_private_loop_fd_fn)(int fd, short revents, void *ud); +typedef void (*fd_fn)(void *ud, int fd, short revents); -struct yasl_private_fd_source { - yasl_private_loop_fd_fn fn; +struct fd_source { + fd_fn fn; void *ud; int id; int fd; short events; }; -struct yasl_private_timer_source { +struct timer_source { yasl_private_timer_cb fn; void *ud; uint64_t interval_ns; uint64_t deadline_ns; - yasl_private_timer_id id; + uint64_t id; bool deleted; }; -struct yasl_private_post { +struct post { void (*fn)(void *ud); void *ud; - struct yasl_private_post *next; + struct post *next; }; /* Signals this loop watches; the previous actions are restored on unwatch. */ -struct yasl_private_signal_watch { +struct signal_watch { void (*cb)(void *ud, int signo); void *ud; int count; @@ -50,28 +52,34 @@ struct yasl_private_signal_watch { struct sigaction old[NSIG]; }; -struct yasl_private_loop { - struct yasl_private_fd_source *fds; +struct yasl_loop { + struct fd_source *fds; struct pollfd *pollfds; int count; int capacity; int next_id; - bool running; + pthread_t owner; + /* Set by yasl_loop_quit; run clears it when it returns. */ + bool quit; + bool stepping; int wake_rfd; int wake_wfd; int wake_watch; - struct yasl_private_timer_source *timers; + struct timer_source *timers; int timer_count; int timer_capacity; - uint32_t next_timer_id; + uint64_t next_timer_id; int timer_firing_depth; yasl_private_mutex post_mutex; - struct yasl_private_post *post_head; - struct yasl_private_post *post_tail; + struct post *post_head; + struct post *post_tail; - struct yasl_private_signal_watch *signals; + struct signal_watch *signals; + + struct yasl_private_loop_driver driver; + bool has_driver; }; /* Handlers run on any thread, so they only touch lock-free atomics: they mark @@ -79,7 +87,7 @@ struct yasl_private_loop { * stored as fd + 1 so that the zero initializer means no pipe. */ static atomic_int signal_wake_fd[NSIG]; static atomic_bool signal_pending[NSIG]; -static struct yasl_private_loop *signal_owner[NSIG]; +static struct yasl_loop *signal_owner[NSIG]; uint64_t yasl_private_loop_now_ns(void) { @@ -88,25 +96,25 @@ uint64_t yasl_private_loop_now_ns(void) return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; } -static void run_posts(struct yasl_private_loop *l) +static void run_posts(struct yasl_loop *l) { yasl_private_mutex_lock(&l->post_mutex); - struct yasl_private_post *p = l->post_head; + struct post *p = l->post_head; l->post_head = l->post_tail = NULL; yasl_private_mutex_unlock(&l->post_mutex); while (p) { - struct yasl_private_post *next = p->next; + struct post *next = p->next; p->fn(p->ud); free(p); p = next; } } -static void run_signals(struct yasl_private_loop *l) +static void run_signals(struct yasl_loop *l) { /* A callback may unwatch, so re-check the watch before each delivery. */ for (int signo = 1; signo < NSIG; signo++) { - struct yasl_private_signal_watch *w = l->signals; + struct signal_watch *w = l->signals; if (!w) return; if (signal_owner[signo] != l || !atomic_exchange(&signal_pending[signo], false)) continue; @@ -114,148 +122,143 @@ static void run_signals(struct yasl_private_loop *l) } } -static int loop_wake_cb(int fd, short revents, void *ud) +static void loop_wake_cb(void *ud, int fd, short revents) { (void)revents; char buf[128]; ssize_t n; while ((n = read(fd, buf, sizeof(buf))) > 0 || (n < 0 && errno == EINTR)) {} - struct yasl_private_loop *l = ud; + struct yasl_loop *l = ud; run_posts(l); run_signals(l); - return 0; } -struct yasl_private_loop *yasl_private_loop_create(void) +static bool open_wake_pipe(int p[2]) { - struct yasl_private_loop *l = calloc(1, sizeof(*l)); - if (!l) return NULL; +#if defined(__linux__) && defined(O_CLOEXEC) + return pipe2(p, O_CLOEXEC | O_NONBLOCK) == 0; +#else + if (pipe(p) != 0) return false; + for (int i = 0; i < 2; i++) { + int flags = fcntl(p[i], F_GETFL, 0); + if (flags < 0 || fcntl(p[i], F_SETFL, flags | O_NONBLOCK) < 0 || + fcntl(p[i], F_SETFD, FD_CLOEXEC) < 0) { + int err = errno; + close(p[0]); + close(p[1]); + errno = err; + return false; + } + } + return true; +#endif +} +yasl_loop *yasl_loop_create(void) +{ + struct yasl_loop *l = calloc(1, sizeof(*l)); + if (!l) return NULL; l->capacity = YASL_PRIVATE_LOOP_INIT_CAP; l->fds = malloc((size_t)l->capacity * sizeof(*l->fds)); l->pollfds = malloc((size_t)l->capacity * sizeof(*l->pollfds)); - if (!l->fds || !l->pollfds) { - free(l->fds); - free(l->pollfds); - free(l); - return NULL; + int p[2]; + int err = 0; + if (!l->fds || !l->pollfds) + err = ENOMEM; + else if ((err = yasl_private_mutex_init(&l->post_mutex)) == 0 && !open_wake_pipe(p)) { + err = errno; + yasl_private_mutex_destroy(&l->post_mutex); } - - if (yasl_private_mutex_init(&l->post_mutex) != 0) { + if (err) { free(l->fds); free(l->pollfds); free(l); + errno = err; return NULL; } - + l->owner = pthread_self(); l->next_id = 1; l->next_timer_id = 1; - l->wake_rfd = -1; - l->wake_wfd = -1; - - int p[2]; -#if defined(__linux__) && defined(O_CLOEXEC) - if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == 0) { - l->wake_rfd = p[0]; - l->wake_wfd = p[1]; - } -#else - if (pipe(p) == 0) { - int f0 = fcntl(p[0], F_GETFL, 0); - int f1 = fcntl(p[1], F_GETFL, 0); - if (f0 >= 0) fcntl(p[0], F_SETFL, f0 | O_NONBLOCK); - if (f1 >= 0) fcntl(p[1], F_SETFL, f1 | O_NONBLOCK); - fcntl(p[0], F_SETFD, FD_CLOEXEC); - fcntl(p[1], F_SETFD, FD_CLOEXEC); - l->wake_rfd = p[0]; - l->wake_wfd = p[1]; - } -#endif - - if (l->wake_rfd >= 0) { - l->wake_watch = yasl_private_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); - } + l->wake_rfd = p[0]; + l->wake_wfd = p[1]; + /* The first watch fits the initial capacity, so it cannot fail. */ + l->wake_watch = yasl_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); return l; } -void yasl_private_loop_destroy(struct yasl_private_loop *l) +int yasl_loop_destroy(yasl_loop *l) { - if (!l) return; - yasl_private_loop_unwatch_signals(l); - for (struct yasl_private_post *p = l->post_head; p;) { - struct yasl_private_post *next = p->next; + if (!l) return 0; + if (l->stepping || l->has_driver) return -EBUSY; + yasl_loop_unwatch_signals(l); + for (struct post *p = l->post_head; p;) { + struct post *next = p->next; free(p); p = next; } yasl_private_mutex_destroy(&l->post_mutex); - if (l->wake_watch > 0) - yasl_private_loop_del_fd(l, l->wake_watch); - if (l->wake_rfd >= 0) - close(l->wake_rfd); - if (l->wake_wfd >= 0) - close(l->wake_wfd); + close(l->wake_rfd); + close(l->wake_wfd); free(l->fds); free(l->pollfds); free(l->timers); free(l); + return 0; } -int yasl_private_loop_add_fd(struct yasl_private_loop *l, int fd, short events, - int (*fn)(int fd, short revents, void *ud), void *ud) +int yasl_loop_add_fd(yasl_loop *l, int fd, short events, + void (*fn)(void *ud, int fd, short revents), void *ud) { - if (!l || fd < 0) - return -1; - + if (!l || fd < 0 || !fn) return -EINVAL; if (l->count >= l->capacity) { - int new_cap = l->capacity ? l->capacity * 2 : YASL_PRIVATE_LOOP_INIT_CAP; - struct yasl_private_fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); - if (!new_fds) return -1; + int new_cap = l->capacity * 2; + struct fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); + if (!new_fds) return -ENOMEM; l->fds = new_fds; - struct pollfd *new_pollfds = realloc(l->pollfds, (size_t)new_cap * sizeof(*new_pollfds)); - if (!new_pollfds) return -1; + if (!new_pollfds) return -ENOMEM; l->pollfds = new_pollfds; l->capacity = new_cap; } - int id = l->next_id++; if (l->next_id <= 0) l->next_id = 1; - - l->fds[l->count] = (struct yasl_private_fd_source){ - .fn = fn, .ud = ud, .id = id, .fd = fd, .events = events, - }; - l->pollfds[l->count] = (struct pollfd){ - .fd = fd, .events = events, .revents = 0, - }; + l->fds[l->count] = (struct fd_source){.fn = fn, .ud = ud, .id = id, .fd = fd, .events = events}; + l->pollfds[l->count] = (struct pollfd){.fd = fd, .events = events}; l->count++; return id; } -int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id) +static int fd_index(const struct yasl_loop *l, int id) { - if (!l || id <= 0) return -1; - for (int i = 0; i < l->count; i++) { - if (l->fds[i].id == id) { - l->count--; - l->fds[i] = l->fds[l->count]; - l->pollfds[i] = l->pollfds[l->count]; - return 0; - } - } + for (int i = 0; l && id > 0 && i < l->count; i++) + if (l->fds[i].id == id) + return i; return -1; } -int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events) +int yasl_loop_del_fd(yasl_loop *l, int id) { - if (!l || id <= 0) return -1; - for (int i = 0; i < l->count; i++) { - if (l->fds[i].id == id) { - l->fds[i].events = events; - l->pollfds[i].events = events; - return 0; - } - } - return -1; + int i = fd_index(l, id); + if (i < 0) return -ENOENT; + l->count--; + l->fds[i] = l->fds[l->count]; + l->pollfds[i] = l->pollfds[l->count]; + return 0; +} + +int yasl_loop_set_fd_events(yasl_loop *l, int id, short events) +{ + int i = fd_index(l, id); + if (i < 0) return -ENOENT; + l->fds[i].events = events; + l->pollfds[i].events = events; + return 0; +} + +short yasl_private_loop_revents(const yasl_loop *l, int id) +{ + int i = fd_index(l, id); + return i < 0 ? 0 : l->pollfds[i].revents; } static void wake_write(int fd) @@ -270,18 +273,23 @@ static void wake_write(int fd) errno = saved; } -void yasl_private_loop_kick(struct yasl_private_loop *l) +void yasl_private_loop_kick(yasl_loop *l) { - if (!l || l->wake_wfd < 0) return; - wake_write(l->wake_wfd); + if (l) wake_write(l->wake_wfd); } -bool yasl_private_loop_post(struct yasl_private_loop *l, void (*fn)(void *ud), void *ud) +void yasl_private_loop_wake(yasl_loop *l) { - if (!l || !fn || l->wake_wfd < 0) return false; - struct yasl_private_post *p = malloc(sizeof(*p)); + if (l && !pthread_equal(l->owner, pthread_self())) + wake_write(l->wake_wfd); +} + +bool yasl_private_loop_post(yasl_loop *l, void (*fn)(void *ud), void *ud) +{ + if (!l || !fn) return false; + struct post *p = malloc(sizeof(*p)); if (!p) return false; - *p = (struct yasl_private_post){.fn = fn, .ud = ud}; + *p = (struct post){.fn = fn, .ud = ud}; yasl_private_mutex_lock(&l->post_mutex); if (l->post_tail) l->post_tail->next = p; @@ -305,85 +313,49 @@ static bool signal_valid(int signo) return signo > 0 && signo < NSIG && signo != SIGKILL && signo != SIGSTOP; } -bool yasl_private_loop_watch_signals(struct yasl_private_loop *l, const int *signals, int n, - void (*cb)(void *ud, int signo), void *ud) +int yasl_loop_watch_signals(yasl_loop *l, const int *signals, int n, + void (*cb)(void *ud, int signo), void *ud) { - if (!l || !signals || n <= 0 || !cb || l->wake_wfd < 0) { - errno = EINVAL; - return false; - } + if (!l || !signals || n <= 0 || !cb) return -EINVAL; + if (l->signals) return -EBUSY; bool wanted[NSIG] = {0}; for (int i = 0; i < n; i++) { - if (!signal_valid(signals[i])) { - errno = EINVAL; - return false; - } - if (signal_owner[signals[i]] && signal_owner[signals[i]] != l) { - errno = EBUSY; - return false; - } + if (!signal_valid(signals[i])) return -EINVAL; + if (signal_owner[signals[i]]) return -EBUSY; wanted[signals[i]] = true; } - struct yasl_private_signal_watch *w = calloc(1, sizeof(*w)); - if (!w) return false; + struct signal_watch *w = calloc(1, sizeof(*w)); + if (!w) return -ENOMEM; w->cb = cb; w->ud = ud; - - /* Keep the old watch's saved actions for signals that stay watched, so - * that unwatch restores what the application had before any watch. */ - struct yasl_private_signal_watch *old = l->signals; struct sigaction sa = {.sa_handler = signal_handler, .sa_flags = SA_RESTART}; sigemptyset(&sa.sa_mask); for (int signo = 1; signo < NSIG; signo++) { if (!wanted[signo]) continue; - int prev = -1; - for (int i = 0; old && i < old->count; i++) - if (old->signals[i] == signo) prev = i; - if (prev >= 0) { - w->old[w->count] = old->old[prev]; - } else { - atomic_store(&signal_pending[signo], false); - atomic_store(&signal_wake_fd[signo], l->wake_wfd + 1); - if (sigaction(signo, &sa, &w->old[w->count]) != 0) { - int err = errno; - atomic_store(&signal_wake_fd[signo], 0); - for (int i = 0; i < w->count; i++) { - int done = w->signals[i]; - bool kept = false; - for (int j = 0; old && j < old->count; j++) - if (old->signals[j] == done) kept = true; - if (kept) continue; - sigaction(done, &w->old[i], NULL); - atomic_store(&signal_wake_fd[done], 0); - } - free(w); - errno = err; - return false; + atomic_store(&signal_pending[signo], false); + atomic_store(&signal_wake_fd[signo], l->wake_wfd + 1); + if (sigaction(signo, &sa, &w->old[w->count]) != 0) { + int err = errno; + atomic_store(&signal_wake_fd[signo], 0); + for (int i = 0; i < w->count; i++) { + sigaction(w->signals[i], &w->old[i], NULL); + atomic_store(&signal_wake_fd[w->signals[i]], 0); } + free(w); + return -err; } w->signals[w->count++] = signo; } - - /* Restore signals the old watch had that the new one drops. */ - for (int i = 0; old && i < old->count; i++) { - int signo = old->signals[i]; - if (wanted[signo]) continue; - sigaction(signo, &old->old[i], NULL); - atomic_store(&signal_wake_fd[signo], 0); - atomic_store(&signal_pending[signo], false); - signal_owner[signo] = NULL; - } for (int i = 0; i < w->count; i++) signal_owner[w->signals[i]] = l; - free(old); l->signals = w; - return true; + return 0; } -void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l) +void yasl_loop_unwatch_signals(yasl_loop *l) { if (!l || !l->signals) return; - struct yasl_private_signal_watch *w = l->signals; + struct signal_watch *w = l->signals; l->signals = NULL; for (int i = 0; i < w->count; i++) { int signo = w->signals[i]; @@ -395,19 +367,19 @@ void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l) free(w); } -static void timer_cleanup_deleted(struct yasl_private_loop *l) +static void timer_cleanup_deleted(struct yasl_loop *l) { - if (!l || l->timer_firing_depth > 0) return; + if (l->timer_firing_depth > 0) return; int dest = 0; for (int src = 0; src < l->timer_count; src++) { - struct yasl_private_timer_source t = l->timers[src]; + struct timer_source t = l->timers[src]; l->timers[dest] = t; dest += !t.deleted; } l->timer_count = dest; } -bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_private_timer_id id) +bool yasl_private_loop_timer_armed(const yasl_loop *l, yasl_private_timer_id id) { if (!l || id == 0) return false; for (int i = 0; i < l->timer_count; i++) @@ -416,7 +388,7 @@ bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_priva return false; } -int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct pollfd **fds) +int yasl_private_loop_poll_set(const yasl_loop *l, const struct pollfd **fds) { if (!l) { *fds = NULL; @@ -426,215 +398,213 @@ int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct p return l->count; } -int yasl_private_loop_next_timeout_ms(const struct yasl_private_loop *l) +int yasl_private_loop_next_timeout_ms(const yasl_loop *l) { - if (!l || l->timer_count <= 0) return -1; + if (!l) return -1; uint64_t now = yasl_private_loop_now_ns(); uint64_t min_deadline = UINT64_MAX; - bool has_active = false; - for (int i = 0; i < l->timer_count; i++) { - if (!l->timers[i].deleted) { - has_active = true; - if (l->timers[i].deadline_ns < min_deadline) - min_deadline = l->timers[i].deadline_ns; - } - } - if (!has_active) return -1; + for (int i = 0; i < l->timer_count; i++) + if (!l->timers[i].deleted && l->timers[i].deadline_ns < min_deadline) + min_deadline = l->timers[i].deadline_ns; + if (min_deadline == UINT64_MAX) return -1; if (min_deadline <= now) return 0; - uint64_t diff = min_deadline - now; - uint64_t ms = (diff + 999999ull) / 1000000ull; - return (ms > (uint64_t)INT_MAX) ? INT_MAX : (int)ms; + uint64_t ms = (min_deadline - now + 999999ull) / 1000000ull; + return ms > (uint64_t)INT_MAX ? INT_MAX : (int)ms; } -yasl_private_timer_id yasl_private_loop_add_timer(struct yasl_private_loop *l, uint64_t interval_ns, - yasl_private_timer_cb cb, void *ud) +uint64_t yasl_loop_add_timer(yasl_loop *l, uint64_t interval_ns, + yasl_private_timer_cb cb, void *ud) { - if (!l || !cb || interval_ns == 0) return 0; - + if (!l || !cb) { + errno = EINVAL; + return 0; + } if (l->timer_count >= l->timer_capacity) { int new_cap = l->timer_capacity ? l->timer_capacity * 2 : 16; - struct yasl_private_timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); - if (!new_timers) return 0; + struct timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); + if (!new_timers) { + errno = ENOMEM; + return 0; + } l->timers = new_timers; l->timer_capacity = new_cap; } - - uint32_t id = l->next_timer_id++; - if (l->next_timer_id == 0) l->next_timer_id = 1; - - uint64_t now = yasl_private_loop_now_ns(); - l->timers[l->timer_count++] = (struct yasl_private_timer_source){ + /* 64 bits never wrap in practice, so an ID never names a newer timer. */ + uint64_t id = l->next_timer_id++; + l->timers[l->timer_count++] = (struct timer_source){ .fn = cb, .ud = ud, .id = id, .interval_ns = interval_ns, - .deadline_ns = now + interval_ns, - .deleted = false, + .deadline_ns = yasl_private_loop_now_ns() + interval_ns, }; - - yasl_private_loop_kick(l); + /* Owner-thread only, and the next wait computes its timeout from the + * timers, so no wake is needed. */ return id; } -bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id) +bool yasl_loop_del_timer(yasl_loop *l, uint64_t id) { if (!l || id == 0) return false; for (int i = l->timer_count - 1; i >= 0; i--) { if (l->timers[i].id == id && !l->timers[i].deleted) { l->timers[i].deleted = true; - if (l->timer_firing_depth == 0) { - if (i == l->timer_count - 1) { - while (l->timer_count > 0 && l->timers[l->timer_count - 1].deleted) - l->timer_count--; - } else { - timer_cleanup_deleted(l); - } - } + timer_cleanup_deleted(l); return true; } } return false; } -int yasl_private_loop_step(struct yasl_private_loop *l, int timeout_ms) +bool yasl_private_loop_set_driver(yasl_loop *l, const struct yasl_private_loop_driver *driver) { - if (!l) return -1; - - int timer_to = yasl_private_loop_next_timeout_ms(l); - int effective_timeout = timeout_ms; - if (timer_to >= 0) { - if (effective_timeout < 0 || timer_to < effective_timeout) - effective_timeout = timer_to; - } - - if (l->count <= 0) { - if (effective_timeout > 0) { - struct timespec req = { - .tv_sec = effective_timeout / 1000, - .tv_nsec = (long)(effective_timeout % 1000) * 1000000L, - }; - nanosleep(&req, NULL); - } - } else { - int n; - do { - n = poll(l->pollfds, (nfds_t)l->count, effective_timeout); - } while (n < 0 && errno == EINTR); - - if (n < 0) return -1; + if (!l) return false; + if (driver && l->has_driver) { + errno = EBUSY; + return false; } + l->driver = driver ? *driver : (struct yasl_private_loop_driver){0}; + l->has_driver = driver != NULL; + return true; +} - int handled = 0; +bool yasl_private_loop_owned(const yasl_loop *l) +{ + return l && pthread_equal(l->owner, pthread_self()); +} - if (l->count > 0) { - int count = l->count; - struct yasl_private_fd_source sstack[32]; - struct pollfd pstack[32]; - struct yasl_private_fd_source *scopy = (count <= 32) ? sstack : malloc((size_t)count * sizeof(*scopy)); - struct pollfd *pcopy = (count <= 32) ? pstack : malloc((size_t)count * sizeof(*pcopy)); - if (!scopy || !pcopy) { - if (scopy && scopy != sstack) free(scopy); - if (pcopy && pcopy != pstack) free(pcopy); - return -1; - } +bool yasl_private_loop_stepping(const yasl_loop *l) +{ + return l && l->stepping; +} - memcpy(scopy, l->fds, (size_t)count * sizeof(*scopy)); - memcpy(pcopy, l->pollfds, (size_t)count * sizeof(*pcopy)); - - for (int i = 0; i < count; i++) { - if (pcopy[i].revents) { - short revents = pcopy[i].revents; - // Fast-path: check if source is still at index i before linear scan - bool valid = false; - if (i < l->count && l->fds[i].id == scopy[i].id) { - l->pollfds[i].revents = 0; - valid = true; - } else { - for (int j = 0; j < l->count; j++) { - if (l->fds[j].id == scopy[i].id) { - l->pollfds[j].revents = 0; - valid = true; - break; - } - } - } - if (valid && scopy[i].fn) { - scopy[i].fn(scopy[i].fd, revents, scopy[i].ud); - handled++; - } - } +/* poll with an EINTR retry that keeps the deadline. */ +static int poll_all(struct yasl_loop *l, int timeout_ms) +{ + uint64_t deadline = timeout_ms > 0 ? yasl_private_loop_now_ns() + YASL_PRIVATE_MS_TO_NS(timeout_ms) : 0; + for (;;) { + if (poll(l->pollfds, (nfds_t)l->count, timeout_ms) >= 0) return 0; + if (errno != EINTR) return -errno; + if (timeout_ms > 0) { + uint64_t now = yasl_private_loop_now_ns(); + timeout_ms = now >= deadline ? 0 : (int)((deadline - now + 999999ull) / 1000000ull); } + } +} +static int dispatch_fds(struct yasl_loop *l) +{ + int handled = 0; + int count = l->count; + struct fd_source sstack[32]; + struct pollfd pstack[32]; + struct fd_source *scopy = count <= 32 ? sstack : malloc((size_t)count * sizeof(*scopy)); + struct pollfd *pcopy = count <= 32 ? pstack : malloc((size_t)count * sizeof(*pcopy)); + if (!scopy || !pcopy) { if (scopy != sstack) free(scopy); if (pcopy != pstack) free(pcopy); + return -ENOMEM; + } + memcpy(scopy, l->fds, (size_t)count * sizeof(*scopy)); + memcpy(pcopy, l->pollfds, (size_t)count * sizeof(*pcopy)); + for (int i = 0; i < count; i++) { + if (!pcopy[i].revents) continue; + /* A callback may have removed or moved this watch. */ + int j = i < l->count && l->fds[i].id == scopy[i].id ? i : fd_index(l, scopy[i].id); + if (j < 0) continue; + l->pollfds[j].revents = 0; + scopy[i].fn(scopy[i].ud, scopy[i].fd, pcopy[i].revents); + handled++; } + if (scopy != sstack) free(scopy); + if (pcopy != pstack) free(pcopy); + return handled; +} - if (l->timer_count > 0) { - uint64_t now = yasl_private_loop_now_ns(); - l->timer_firing_depth++; - int initial_count = l->timer_count; - for (int i = 0; i < initial_count; i++) { - if (i >= l->timer_count) break; - if (l->timers[i].deleted || l->timers[i].deadline_ns > now) - continue; - - yasl_private_timer_id tid = l->timers[i].id; - uint64_t interval = l->timers[i].interval_ns; - void *ud = l->timers[i].ud; - struct yasl_private_timer_source timer_src = l->timers[i]; - - uint64_t next_interval = timer_src.fn(ud, tid, interval); - handled++; - - // Fast-path: timer is usually still at index i unless an earlier timer was deleted - if (i < l->timer_count && l->timers[i].id == tid) { - if (next_interval == 0 || l->timers[i].deleted) { - l->timers[i].deleted = true; - } else { - l->timers[i].interval_ns = next_interval; - if (l->timers[i].deadline_ns + next_interval > now) - l->timers[i].deadline_ns += next_interval; - else - l->timers[i].deadline_ns = now + next_interval; - } - } else { - for (int j = 0; j < l->timer_count; j++) { - if (l->timers[j].id == tid) { - if (next_interval == 0 || l->timers[j].deleted) { - l->timers[j].deleted = true; - } else { - l->timers[j].interval_ns = next_interval; - if (l->timers[j].deadline_ns + next_interval > now) - l->timers[j].deadline_ns += next_interval; - else - l->timers[j].deadline_ns = now + next_interval; - } - break; - } - } - } - } - l->timer_firing_depth--; - if (l->timer_firing_depth == 0) { - timer_cleanup_deleted(l); - } +static void reschedule(struct timer_source *t, uint64_t next, uint64_t now) +{ + if (next == 0 || t->deleted) { + t->deleted = true; + return; } + t->interval_ns = next; + t->deadline_ns = t->deadline_ns + next > now ? t->deadline_ns + next : now + next; +} +static int fire_timers(struct yasl_loop *l) +{ + int handled = 0; + uint64_t now = yasl_private_loop_now_ns(); + l->timer_firing_depth++; + /* Timers added by a callback wait for the next step. */ + int initial_count = l->timer_count; + for (int i = 0; i < initial_count && i < l->timer_count; i++) { + if (l->timers[i].deleted || l->timers[i].deadline_ns > now) + continue; + struct timer_source t = l->timers[i]; + uint64_t next = t.fn(t.ud, t.id, t.interval_ns); + handled++; + /* Compaction waits for depth zero, so index i still holds this timer. */ + reschedule(&l->timers[i], next, now); + } + l->timer_firing_depth--; + timer_cleanup_deleted(l); return handled; } -int yasl_private_loop_run(struct yasl_private_loop *l) +static int step(struct yasl_loop *l, int timeout_ms) { - if (!l) return -1; - l->running = true; - while (l->running) { - if (yasl_private_loop_step(l, -1) < 0) - return -1; + /* A driver cannot change during a step: its display cannot close then. */ + const struct yasl_private_loop_driver *d = l->has_driver ? &l->driver : NULL; + if (d && d->prepare(d->ud)) + timeout_ms = 0; + int timer_to = yasl_private_loop_next_timeout_ms(l); + if (timer_to >= 0 && (timeout_ms < 0 || timer_to < timeout_ms)) + timeout_ms = timer_to; + int r; + if (d && d->wait) { + r = d->wait(d->ud, timeout_ms); + /* The platform wait does not fill revents; read them now. */ + if (r >= 0) r = poll_all(l, 0); + } else { + r = poll_all(l, timeout_ms); } - return 0; + /* check closes what prepare opened, whatever the wait returned. */ + if (d) d->check(d->ud); + if (r < 0) return r; + if (d && d->deliver) d->deliver(d->ud); + int fds = dispatch_fds(l); + if (fds < 0) return fds; + return fds + fire_timers(l); +} + +int yasl_loop_step(yasl_loop *l, int timeout_ms) +{ + if (!l) return -EINVAL; + if (!pthread_equal(l->owner, pthread_self())) return -EPERM; + if (l->stepping) return -EBUSY; + l->stepping = true; + int r = step(l, timeout_ms); + l->stepping = false; + return r; +} + +int yasl_loop_run(yasl_loop *l) +{ + if (!l) return -EINVAL; + if (!pthread_equal(l->owner, pthread_self())) return -EPERM; + /* A nested run must not consume the outer run's quit. */ + if (l->stepping) return -EBUSY; + int r = 0; + while (!l->quit) { + r = yasl_loop_step(l, -1); + if (r < 0) break; + } + l->quit = false; + return r < 0 ? r : 0; } -void yasl_private_loop_quit(struct yasl_private_loop *l) +void yasl_loop_quit(yasl_loop *l) { - if (l) l->running = false; + if (l) l->quit = true; } diff --git a/src/platform/wayland/api.h b/src/platform/wayland/api.h index 69f61b1..1fb6d5c 100644 --- a/src/platform/wayland/api.h +++ b/src/platform/wayland/api.h @@ -6,10 +6,11 @@ struct wayland_display; struct wayland_surface; -struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); +struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec); void wayland_display_close(struct wayland_display *d); uint32_t wayland_display_caps(const struct wayland_display *d); -int wayland_display_dispatch_timeout(struct wayland_display *d, int timeout_ms); +bool wayland_display_prepare(struct wayland_display *d); +void wayland_display_check(struct wayland_display *d); void wayland_display_flush(struct wayland_display *d); int wayland_display_modifiers(const struct wayland_display *d); bool wayland_display_copy_text(struct wayland_display *d, enum yasl_private_selection sel, const char *text); diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index 3ff696c..f6207ed 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -2,7 +2,7 @@ #include "platform/freedesktop/dbus.h" #include <errno.h> -static struct yasl_private_display *impl_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +static struct yasl_private_display *impl_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct wayland_display *result = wayland_display_open(loop, spec); return result ? &result->base : NULL; @@ -18,9 +18,19 @@ static uint32_t impl_display_caps(const struct yasl_private_display *d) return wayland_display_caps((const struct wayland_display *)d); } -static int impl_display_dispatch_timeout(struct yasl_private_display *d, int timeout_ms) +static bool impl_display_prepare(struct yasl_private_display *d) { - return wayland_display_dispatch_timeout((struct wayland_display *)d, timeout_ms); + return wayland_display_prepare((struct wayland_display *)d); +} + +static void impl_display_check(struct yasl_private_display *d) +{ + wayland_display_check((struct wayland_display *)d); +} + +static bool impl_surface_frame_in_flight(const struct yasl_surface *s) +{ + return ((const struct wayland_surface *)s)->frame_cb != NULL; } static void impl_display_flush(struct yasl_private_display *d) @@ -497,7 +507,9 @@ const struct yasl_private_backend_ops yasl_private_wayland_backend = { .display_open = impl_display_open, .display_close = impl_display_close, .display_caps = impl_display_caps, - .display_dispatch_timeout = impl_display_dispatch_timeout, + .display_prepare = impl_display_prepare, + .display_check = impl_display_check, + .surface_frame_in_flight = impl_surface_frame_in_flight, .display_flush = impl_display_flush, .display_modifiers = impl_display_modifiers, .display_double_click_ms = impl_display_double_click_ms, diff --git a/src/platform/wayland/buffer.c b/src/platform/wayland/buffer.c index 7ff4bd0..4f42b60 100644 --- a/src/platform/wayland/buffer.c +++ b/src/platform/wayland/buffer.c @@ -335,6 +335,7 @@ static void frame_done(void *data, struct wl_callback *cb, uint32_t ms) w->clock.source = YASL_PRIVATE_TIMING_FRAME_CALLBACK; } + yasl_private_surface_notify_frame_shown(&w->base); if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) yasl_private_surface_notify_refresh(&w->base); else if (w->want_draw) @@ -550,6 +551,13 @@ bool wayland_surface_read_pixels(struct wayland_surface *w, uint32_t *out, size_ return yasl_private_copy_frame(b->pixels, (struct yasl_private_vector2i){b->width, b->height}, out, cap, size); } +bool surface_draw_ready(const struct wayland_surface *w) +{ + return w->want_draw && w->configured && !w->closed && + w->base.presenter == YASL_PRIVATE_PRESENTER_CPU && + buffer_pick_free_index(w->buffers, w->cur) >= 0; +} + void surface_draw_frame(struct wayland_surface *w) { if (w->closed || !w->configured || w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) diff --git a/src/platform/wayland/clipboard.c b/src/platform/wayland/clipboard.c index 9a75953..9c019a1 100644 --- a/src/platform/wayland/clipboard.c +++ b/src/platform/wayland/clipboard.c @@ -48,7 +48,7 @@ struct clipboard { struct offer *drag_offer; struct primary_offer *primary_selection; struct primary_offer *primary_pending_offers; - struct yasl_private_loop *loop; + struct yasl_loop *loop; struct transfer *transfers; char *text; char *primary_text; @@ -106,9 +106,9 @@ static void transfer_end(struct transfer *t, bool complete) struct transfer **p = &owner->transfers; while (*p && *p != t) p = &(*p)->next; if (*p) *p = t->next; - yasl_private_loop_del_fd(owner->loop, t->id); + yasl_loop_del_fd(owner->loop, t->id); if (t->timeout) - yasl_private_loop_del_timer(owner->loop, t->timeout); + yasl_loop_del_timer(owner->loop, t->timeout); close(t->fd); if (t->drop) { if (complete && wl_data_offer_get_version(t->drop->proxy) >= @@ -133,10 +133,10 @@ static void transfer_end(struct transfer *t, bool complete) free(t); } -static int transfer_io(int fd, short events, void *ud) +static void transfer_io(void *ud, int fd, short events) { struct transfer *t = ud; - if (events & (POLLERR | POLLNVAL)) { transfer_end(t, false); return 0; } + if (events & (POLLERR | POLLNVAL)) { transfer_end(t, false); return; } if (t->sending) { ssize_t n = write(fd, t->text + t->offset, t->len - t->offset); if (n > 0) t->offset += (size_t)n; @@ -146,9 +146,9 @@ static int transfer_io(int fd, short events, void *ud) if (t->capacity - t->len < 4096) { size_t capacity = t->capacity ? t->capacity * 2 : 8192; if (capacity > CLIPBOARD_TEXT_MAX) capacity = CLIPBOARD_TEXT_MAX; - if (capacity <= t->len) { transfer_end(t, false); return 0; } + if (capacity <= t->len) { transfer_end(t, false); return; } char *text = realloc(t->text, capacity); - if (!text) { transfer_end(t, false); return 0; } + if (!text) { transfer_end(t, false); return; } t->text = text; t->capacity = capacity; } @@ -160,7 +160,6 @@ static int transfer_io(int fd, short events, void *ud) transfer_end(t, valid); } else if (errno != EAGAIN && errno != EINTR) transfer_end(t, false); } - return 0; } static struct transfer *start_transfer_text(struct clipboard *c, int fd, bool sending, @@ -176,12 +175,12 @@ static struct transfer *start_transfer_text(struct clipboard *c, int fd, bool se if (!t->text) { close(fd); free(t); return NULL; } t->len = strlen(t->text); } - t->id = yasl_private_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t); - if (t->id < 0) { close(fd); free(t->text); free(t); return NULL; } - t->timeout = yasl_private_loop_add_timer(c->loop, CLIPBOARD_TRANSFER_TIMEOUT_NS, + t->id = yasl_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t); + if (t->id <= 0) { close(fd); free(t->text); free(t); return NULL; } + t->timeout = yasl_loop_add_timer(c->loop, CLIPBOARD_TRANSFER_TIMEOUT_NS, transfer_timeout, t); if (!t->timeout) { - yasl_private_loop_del_fd(c->loop, t->id); + yasl_loop_del_fd(c->loop, t->id); close(fd); free(t->text); free(t); @@ -522,7 +521,7 @@ struct wl_data_device *clipboard_data_device(const struct clipboard *c) } struct clipboard *clipboard_create(struct wl_display *display, struct wl_data_device_manager *manager, - struct wl_seat *seat, struct yasl_private_loop *loop, + struct wl_seat *seat, struct yasl_loop *loop, clipboard_receive_fn receive, void *ud) { if (!manager || !seat) return NULL; diff --git a/src/platform/wayland/clipboard.h b/src/platform/wayland/clipboard.h index 7ab1719..738d32e 100644 --- a/src/platform/wayland/clipboard.h +++ b/src/platform/wayland/clipboard.h @@ -19,7 +19,7 @@ typedef void (*clipboard_receive_fn)(const char *, size_t, bool uri_list, enum yasl_private_transfer_status, void *); struct clipboard *clipboard_create(struct wl_display *, struct wl_data_device_manager *, - struct wl_seat *, struct yasl_private_loop *, + struct wl_seat *, struct yasl_loop *, clipboard_receive_fn, void *); void clipboard_destroy(struct clipboard *); bool clipboard_set_seat(struct clipboard *, struct wl_seat *); diff --git a/src/platform/wayland/internal.h b/src/platform/wayland/internal.h index d5b7de1..a580e71 100644 --- a/src/platform/wayland/internal.h +++ b/src/platform/wayland/internal.h @@ -249,7 +249,7 @@ struct wayland_display { struct wp_fifo_manager_v1 *fifo_mgr; struct wp_commit_timing_manager_v1 *commit_timing_mgr; struct xkb_context *xkb_ctx; - struct yasl_private_loop *loop; + struct yasl_loop *loop; char *app_id; struct xdg_toplevel_drag_manager_v1 *toplevel_drag_mgr; struct conn_drag drag; @@ -405,6 +405,8 @@ void conn_destroy_outputs(struct wayland_display *c); void surface_follow_outputs(struct wayland_surface *w); uint64_t conn_pres_to_mono(const struct wayland_display *c, uint64_t ns); uint64_t conn_mono_to_pres(const struct wayland_display *c, uint64_t ns); +/* True when surface_draw_frame would draw now. */ +bool surface_draw_ready(const struct wayland_surface *w); void surface_draw_frame(struct wayland_surface *w); void wayland_surface_arm_frame(struct wayland_surface *w); void surface_fail(struct wayland_surface *w); diff --git a/src/platform/wayland/keyboard.c b/src/platform/wayland/keyboard.c index cf6d56c..294f553 100644 --- a/src/platform/wayland/keyboard.c +++ b/src/platform/wayland/keyboard.c @@ -336,23 +336,22 @@ static void keyboard_key(void *data, struct wl_keyboard *keyboard, } } -static int keyboard_repeat_ready(int fd, short revents, void *data) +static void keyboard_repeat_ready(void *data, int fd, short revents) { struct seat *s = data; uint64_t count; - if (!(revents & POLLIN) || read(fd, &count, sizeof(count)) != sizeof(count)) return 0; + if (!(revents & POLLIN) || read(fd, &count, sizeof(count)) != sizeof(count)) return; struct wayland_surface *w = s->keyboard_focus; if (!w) { keyboard_repeat_stop(s); - return 0; + return; } if (count > 32) count = 32; while (count-- && s->repeat_held && s->repeat_rate > 0) { if (!keyboard_dispatch_key(s, w, s->repeat_key, YASL_KEY_REPEAT)) keyboard_repeat_stop(s); } - return 0; } static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, @@ -391,7 +390,7 @@ int keyboard_repeat_init(struct seat *s) if (s->repeat_fd >= 0) return 0; s->repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (s->repeat_fd < 0) return -1; - s->repeat_watch = yasl_private_loop_add_fd(s->conn->loop, s->repeat_fd, POLLIN, + s->repeat_watch = yasl_loop_add_fd(s->conn->loop, s->repeat_fd, POLLIN, keyboard_repeat_ready, s); if (s->repeat_watch <= 0) { s->repeat_watch = 0; diff --git a/src/platform/wayland/pointer.c b/src/platform/wayland/pointer.c index 571bb12..6cf2710 100644 --- a/src/platform/wayland/pointer.c +++ b/src/platform/wayland/pointer.c @@ -1552,7 +1552,7 @@ void seat_destroy(struct seat *s) } seat_drop_touch(s); clipboard_destroy(s->clipboard); - if (s->repeat_watch > 0) yasl_private_loop_del_fd(c->loop, s->repeat_watch); + if (s->repeat_watch > 0) yasl_loop_del_fd(c->loop, s->repeat_watch); if (s->repeat_fd >= 0) close(s->repeat_fd); if (s->cursor_viewport) wp_viewport_destroy(s->cursor_viewport); if (s->cursor_surface) wl_surface_destroy(s->cursor_surface); diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c index cc0c715..bc37c10 100644 --- a/src/platform/wayland/surface.c +++ b/src/platform/wayland/surface.c @@ -49,7 +49,7 @@ static void display_lose(struct wayland_display *c) report_display_error(c); display_cancel_read(c); if (c->loop && c->watch > 0) - yasl_private_loop_del_fd(c->loop, c->watch); + yasl_loop_del_fd(c->loop, c->watch); c->watch = 0; c->depth++; conn_drag_end(c); @@ -62,7 +62,7 @@ static void display_lose(struct wayland_display *c) yasl_private_display_notify_event(&c->base, YASL_PRIVATE_EVENT_DISPLAY_LOST); c->reconnect_delay_ns = YASL_PRIVATE_MS_TO_NS(100); if (c->loop && !c->reconnect_timer) - c->reconnect_timer = yasl_private_loop_add_timer(c->loop, c->reconnect_delay_ns, reconnect_tick, c); + c->reconnect_timer = yasl_loop_add_timer(c->loop, c->reconnect_delay_ns, reconnect_tick, c); } void surface_fail(struct wayland_surface *w) @@ -73,43 +73,18 @@ void surface_fail(struct wayland_surface *w) yasl_private_surface_notify_close(&w->base); } -static int display_ready(int fd, short revents, void *ud) +/* The display fd is read in wayland_display_check, before any callback. */ +static void display_ready(void *ud, int fd, short revents) { - (void)fd; - struct wayland_display *c = ud; - if (c->lost) - return 0; - c->depth++; - if (c->reading) { - c->reading = false; - if (!(revents & POLLIN)) - wl_display_cancel_read(c->wl); - else if (wl_display_read_events(c->wl) < 0) - revents |= POLLERR; - } - if (revents & POLLOUT) { - if (wl_display_flush(c->wl) >= 0) { - if (c->loop && c->watch > 0) - yasl_private_loop_set_fd_events(c->loop, c->watch, POLLIN); - } else if (errno != EAGAIN) { - display_lose(c); - } - } - if (wl_display_dispatch_pending(c->wl) < 0 || - (revents & (POLLERR | POLLHUP | POLLNVAL))) - display_lose(c); - display_leave(c); - return 0; + (void)ud; (void)fd; (void)revents; } -static bool display_arm(struct wayland_display *c) +static void display_arm(struct wayland_display *c) { - bool had_pending = false; while (!c->reading && wl_display_prepare_read(c->wl) != 0) { - had_pending = true; if (wl_display_dispatch_pending(c->wl) < 0) { display_lose(c); - return false; + return; } } c->reading = true; @@ -118,74 +93,65 @@ static bool display_arm(struct wayland_display *c) if (errno != EAGAIN) { display_cancel_read(c); display_lose(c); - return false; + return; } events |= POLLOUT; } - if (c->loop && c->watch > 0) - yasl_private_loop_set_fd_events(c->loop, c->watch, events); - return had_pending; + yasl_loop_set_fd_events(c->loop, c->watch, events); } -static inline bool display_has_immediate_work(const struct wayland_display *c) +/* A draw that cannot proceed now, for want of a buffer or a configure, is not + * work: the release or configure that unblocks it schedules it again. */ +static bool display_has_immediate_work(const struct wayland_display *c) { - if (unlikely(!c || c->lost)) return false; - for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next) { - bool ready_draw = w->want_draw & (w->frame_cb == NULL) & w->configured; - bool has_input = (w->input.count > 0 || w->input.reset_pending); - if (unlikely((ready_draw | has_input) & !w->closed)) + for (const struct wayland_surface *w = c->surfaces; w; w = w->conn_next) + if (surface_draw_ready(w)) return true; - } return false; } -int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms) +/* Before the wait: dispatch what libwayland already holds, draw due surfaces, + * and arm the read. While lost, only the reconnect timer runs. */ +bool wayland_display_prepare(struct wayland_display *c) { - if (!c) - return -1; - + if (c->lost) + return false; c->depth++; - if (c->lost) { - /* Reconnect attempts run as a loop timer. */ - int n = c->loop ? yasl_private_loop_step(c->loop, timeout_ms) : 0; - display_leave(c); - return n; - } - /* Losing the connection is reported as DISPLAY_LOST, not as a failed wait. */ - if (wl_display_dispatch_pending(c->wl) < 0) { + if (wl_display_dispatch_pending(c->wl) < 0) display_lose(c); - display_leave(c); - return 0; - } - - display_arm(c); - if (c->lost) { - display_leave(c); - return 0; - } - - if (timeout_ms != 0 && display_has_immediate_work(c)) { - timeout_ms = 0; - } - - int n = 0; - if (c->loop) { - n = yasl_private_loop_step(c->loop, timeout_ms); - } + if (!c->lost) + wayland_display_flush(c); + if (!c->lost) + display_arm(c); + bool ready = !c->lost && display_has_immediate_work(c); + display_leave(c); + return ready; +} - if (c->reading) { - display_cancel_read(c); +/* After the wait, before any callback: finish the read prepare armed, so + * callbacks may do roundtrips, and dispatch what it brought. */ +void wayland_display_check(struct wayland_display *c) +{ + if (!c->reading) + return; + c->depth++; + short revents = yasl_private_loop_revents(c->loop, c->watch); + c->reading = false; + if (!(revents & POLLIN)) + wl_display_cancel_read(c->wl); + else if (wl_display_read_events(c->wl) < 0) + revents |= POLLERR; + if (revents & POLLOUT) { + if (wl_display_flush(c->wl) >= 0) + yasl_loop_set_fd_events(c->loop, c->watch, POLLIN); + else if (errno != EAGAIN) + display_lose(c); } - - if (c->wl && wl_display_dispatch_pending(c->wl) < 0) { + if (!c->lost && (wl_display_dispatch_pending(c->wl) < 0 || + (revents & (POLLERR | POLLHUP | POLLNVAL)))) display_lose(c); - display_leave(c); - return 0; - } - display_leave(c); - return n; } void wayland_display_flush(struct wayland_display *c) @@ -212,7 +178,7 @@ static void connection_release(struct wayland_display *c) for (struct wayland_surface *w = c->surfaces; w; w = w->conn_next) surface_release_native(w); conn_drag_end(c); - if (c->loop && c->watch > 0) yasl_private_loop_del_fd(c->loop, c->watch); + if (c->loop && c->watch > 0) yasl_loop_del_fd(c->loop, c->watch); c->watch = 0; if (c->wl) display_cancel_read(c); while (c->seats) @@ -271,7 +237,7 @@ static bool connection_open(struct wayland_display *c) return false; surface_init_cursor(c); if (c->loop) { - c->watch = yasl_private_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c); + c->watch = yasl_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c); if (c->watch <= 0) { c->watch = 0; return false; } } return display_input_init(c) == 0; @@ -329,10 +295,10 @@ static uint64_t reconnect_tick(void *ud, yasl_private_timer_id id, uint64_t inte static void display_free(struct wayland_display *c) { connection_release(c); - if (c->loop && c->reconnect_timer) yasl_private_loop_del_timer(c->loop, c->reconnect_timer); + if (c->loop && c->reconnect_timer) yasl_loop_del_timer(c->loop, c->reconnect_timer); if (c->xkb_ctx) xkb_context_unref(c->xkb_ctx); if (c->owns_loop && c->loop) { - yasl_private_loop_destroy(c->loop); + yasl_loop_destroy(c->loop); c->loop = NULL; c->base.loop = NULL; } @@ -340,14 +306,15 @@ static void display_free(struct wayland_display *c) free(c); } -struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) +struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct wayland_display *c = calloc(1, sizeof(*c)); if (!c) return NULL; c->owner = pthread_self(); c->base.ops = &yasl_private_wayland_backend; + c->base.reconnects = true; if (!loop) { - c->loop = yasl_private_loop_create(); + c->loop = yasl_loop_create(); c->owns_loop = true; } else { c->loop = loop; @@ -605,8 +572,8 @@ void wayland_surface_schedule_redraw(struct wayland_surface *w) return; } w->want_draw = true; - if (w->conn && w->conn->loop) - yasl_private_loop_kick(w->conn->loop); + if (w->conn) + yasl_private_loop_wake(w->conn->loop); } struct yasl_private_geometry wayland_surface_geometry(const struct wayland_surface *w) diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c index 630eb91..2751c91 100644 --- a/src/platform/x11/window.c +++ b/src/platform/x11/window.c @@ -143,7 +143,7 @@ struct x11_display { struct x11_display *next_open; Display *native; int screen; - struct yasl_private_loop *loop; + struct yasl_loop *loop; int watch; struct x11_surface *surfaces, *dead; int depth; @@ -354,7 +354,8 @@ static int yasl_private_x11_error_handler(Display *native, XErrorEvent *event) } /* Marks the display lost once and queues DISPLAY_LOST. The poll watch is removed - * by ready(), on the thread that owns the loop, because this can run on a GL thread. */ + * by prepare() or check(), on the thread that owns the loop, because this can run + * on a GL thread. */ static void display_lost(struct x11_display *d) { if (d->lost) return; @@ -1025,7 +1026,7 @@ static void cancel_pastes(struct x11_display *d, struct x11_surface *target) } else if (!target || r->target == target) { /* An INCR transfer in progress is abandoned; its owner times out. */ *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); if (!d->lost) XDestroyWindow(d->native, r->window); free(r->buf); free(r); @@ -1058,8 +1059,8 @@ static uint64_t paste_timeout(void *ud, yasl_private_timer_id id, uint64_t inter static void paste_rearm(struct paste_request *r) { struct x11_display *d = r->display; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); - r->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); + r->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); } /* Unlink a paste request at *link and report its outcome to the surface, if alive. */ @@ -1068,7 +1069,7 @@ static void paste_complete(struct x11_display *d, struct paste_request **link, { struct paste_request *r = *link; *link = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); if (r->target && !r->target->destroyed) yasl_private_surface_complete_paste(&r->target->base, r->id, status, text, len); @@ -2027,7 +2028,7 @@ static void incr_send_retire(struct x11_display *d, struct incr_send *st, bool c *p = st->next; break; } - if (cancel_timer && st->timeout) yasl_private_loop_del_timer(d->loop, st->timeout); + if (cancel_timer && st->timeout) yasl_loop_del_timer(d->loop, st->timeout); free(st->data); free(st); } @@ -2045,8 +2046,8 @@ static uint64_t incr_send_timeout(void *ud, yasl_private_timer_id id, uint64_t i static void incr_send_arm(struct incr_send *st) { struct x11_display *d = st->display; - if (st->timeout) yasl_private_loop_del_timer(d->loop, st->timeout); - st->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); + if (st->timeout) yasl_loop_del_timer(d->loop, st->timeout); + st->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); } /* Start an INCR send: publish the total size, then wait for the requestor to @@ -2062,7 +2063,7 @@ static bool incr_send_start(struct x11_display *d, Window requestor, Atom proper st->target = target; st->data = data; st->length = length; - st->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); + st->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); if (!st->timeout) { free(st); return false; @@ -2325,14 +2326,14 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) /* Surface destruction detached this accepted request. The reply means * the owner has finished writing the initial property; retire safely. */ *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); free(r); return; } if (event->property == None) { *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); enum yasl_private_transfer_status status = XGetSelectionOwner(d->native, r->selection) == None ? YASL_PRIVATE_TRANSFER_UNAVAILABLE : YASL_PRIVATE_TRANSFER_FAILED; @@ -2361,7 +2362,7 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) return; } *p = r->next; - if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); Atom actual = None; int format = 0; unsigned long count = 0, left = 0; @@ -2938,56 +2939,49 @@ static void flush(struct yasl_private_display *base) leave(d); } -static int ready(int fd, short events, void *ud) +/* The connection is read in check(), before any callback. */ +static void ready(void *ud, int fd, short events) +{ + (void)ud; (void)fd; (void)events; +} + +/* A hung-up socket stays readable; stop watching it so the loop does not spin. */ +static void drop_watch(struct x11_display *d) +{ + if (d->watch > 0) yasl_loop_del_fd(d->loop, d->watch); + d->watch = 0; +} + +/* Before the wait: dispatch what Xlib already holds, draw due surfaces, and send + * requests. Loss is final, so a lost display only drops its watch. */ +static bool prepare(struct yasl_private_display *base) { - (void)fd; - struct yasl_private_display *base = ud; struct x11_display *d = (struct x11_display *)base; - if (events & (POLLERR | POLLHUP)) display_lost(d); - if (d->lost) { - /* A hung-up socket stays readable; stop watching it so the loop does not spin. */ - if (d->watch > 0) yasl_private_loop_del_fd(d->loop, d->watch); - d->watch = 0; - return 0; - } - if (events & POLLIN) { + if (!d->lost) { dispatch(base); flush(base); } - return 0; + if (d->lost) { + drop_watch(d); + return false; + } + /* Replies awaited while drawing can read events into Xlib's queue. */ + return XEventsQueued(d->native, QueuedAlready) > 0; } -static int dispatch_timeout(struct yasl_private_display *base, int timeout_ms) +static void check(struct yasl_private_display *base) { struct x11_display *d = (struct x11_display *)base; - if (unlikely(!d || !d->native)) { errno = ENODEV; return -1; } - /* The DISPLAY_LOST event is delivered by the wait that found the loss. */ - if (d->lost) { errno = ENOTCONN; return -1; } - dispatch(base); - if (d->lost) return 0; - for (struct x11_surface *s = d->surfaces; s; s = s->next) { - if (s->want_draw || s->input.count || s->input.reset_pending) { - timeout_ms = 0; - break; - } - } - if (d->loop) { - XFlush(d->native); - if (d->lost) return 0; - if (XEventsQueued(d->native, QueuedAlready) > 0) - timeout_ms = 0; - if (!XPending(d->native) && timeout_ms != 0) { - yasl_private_loop_step(d->loop, timeout_ms); - dispatch(base); - } else { - yasl_private_loop_step(d->loop, 0); - dispatch(base); - } + short revents = d->watch > 0 ? yasl_private_loop_revents(d->loop, d->watch) : 0; + if (revents & (POLLERR | POLLHUP)) display_lost(d); + if (d->lost) { + drop_watch(d); + return; } - return 0; + if (revents & POLLIN) dispatch(base); } -static struct yasl_private_display *display_open(struct yasl_private_loop *loop, +static struct yasl_private_display *display_open(struct yasl_loop *loop, const struct yasl_private_display_spec *spec) { struct x11_display *d = calloc(1, sizeof(*d)); @@ -3046,7 +3040,7 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, XISelectEvents(d->native, RootWindow(d->native, d->screen), &mask, 1); } if (!loop) { - d->loop = yasl_private_loop_create(); + d->loop = yasl_loop_create(); d->owns_loop = true; } else { d->loop = loop; @@ -3055,7 +3049,7 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, d->app_id = strdup(spec && spec->app_id ? spec->app_id : "YASL"); d->base.app_id = d->app_id; if (!d->loop || !d->app_id) { - if (d->owns_loop) yasl_private_loop_destroy(d->loop); + if (d->owns_loop) yasl_loop_destroy(d->loop); free(d->app_id); XCloseDisplay(d->native); display_unregister(d); @@ -3095,14 +3089,14 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, ATOM(uri_list, "text/uri-list"); #undef ATOM if (d->loop) { - d->watch = yasl_private_loop_add_fd(d->loop, ConnectionNumber(d->native), POLLIN, ready, &d->base); + d->watch = yasl_loop_add_fd(d->loop, ConnectionNumber(d->native), POLLIN, ready, &d->base); if (d->watch < 0) { if (d->im) XCloseIM(d->im); XDestroyWindow(d->native, d->selection_window); XCloseDisplay(d->native); display_unregister(d); free(d->app_id); - if (d->owns_loop) yasl_private_loop_destroy(d->loop); + if (d->owns_loop) yasl_loop_destroy(d->loop); free(d); return NULL; } @@ -3118,9 +3112,9 @@ static void display_close(struct yasl_private_display *base) cancel_pastes(d, NULL); incr_cancel_all(d); d->pending_drop.active = false; - if (d->loop && d->watch > 0) yasl_private_loop_del_fd(d->loop, d->watch); + if (d->loop && d->watch > 0) yasl_loop_del_fd(d->loop, d->watch); if (d->owns_loop && d->loop) { - yasl_private_loop_destroy(d->loop); + yasl_loop_destroy(d->loop); d->loop = NULL; d->base.loop = NULL; } @@ -3208,7 +3202,7 @@ static bool request_paste(struct yasl_private_display *base, enum yasl_private_s r->selection = selection; r->id = id; errno = 0; - r->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, + r->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); if (!r->timeout) { XDestroyWindow(d->native, r->window); @@ -3236,8 +3230,8 @@ static void schedule_redraw(struct yasl_surface *base) if (!s->destroyed) { s->want_draw = true; struct x11_display *d = (struct x11_display *)base->display; - if (d && d->loop) - yasl_private_loop_kick(d->loop); + if (d) + yasl_private_loop_wake(d->loop); } } @@ -3668,7 +3662,8 @@ const struct yasl_private_backend_ops yasl_private_x11_backend = { .display_open = display_open, .display_close = display_close, .display_caps = caps, - .display_dispatch_timeout = dispatch_timeout, + .display_prepare = prepare, + .display_check = check, .display_flush = flush, .display_modifiers = mods, .display_copy_text = copy_text, 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 <assert.h> #include <errno.h> +#include <poll.h> #include <pthread.h> +#include <stdint.h> #include <signal.h> #include <stdio.h> #include <string.h> @@ -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 <assert.h> +#include <signal.h> #include <stdio.h> #include <string.h> +#include <unistd.h> 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 <yasl/yasl.h> #include <assert.h> #include <string.h> +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; |
