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