From f7fa0930cb46046f995fa1ab927aef62466e995e Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 00:29:34 +0300 Subject: feat: add SDL3-style surface, display, and loop timer API for X11, Wayland, and Cocoa --- src/common/loop.c | 267 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 228 insertions(+), 39 deletions(-) (limited to 'src/common/loop.c') diff --git a/src/common/loop.c b/src/common/loop.c index ea9bee3..a1f2434 100644 --- a/src/common/loop.c +++ b/src/common/loop.c @@ -22,6 +22,20 @@ struct yasl_fd_source { short events; }; +struct yasl_timer_source { + union { + yasl_timer_cb ns; + yasl_timer_ms_cb ms; + void *raw; + } fn; + void *ud; + yasl_timer_id id; + uint64_t interval_ns; + uint64_t deadline_ns; + bool ms_mode; + bool deleted; +}; + struct yasl_loop { struct yasl_fd_source *fds; struct pollfd *pollfds; @@ -32,6 +46,12 @@ struct yasl_loop { int wake_rfd; int wake_wfd; int wake_watch; + + struct yasl_timer_source *timers; + int timer_count; + int timer_capacity; + uint32_t next_timer_id; + int timer_firing_depth; }; uint64_t yasl_loop_now_ns(void) @@ -67,6 +87,7 @@ struct yasl_loop *yasl_loop_create(void) } l->next_id = 1; + l->next_timer_id = 1; l->wake_rfd = -1; l->wake_wfd = -1; @@ -106,6 +127,7 @@ void yasl_loop_destroy(struct yasl_loop *l) close(l->wake_wfd); free(l->fds); free(l->pollfds); + free(l->timers); free(l); } @@ -177,65 +199,232 @@ void yasl_loop_kick(struct yasl_loop *l) } while (n < 0 && errno == EINTR); } +static void timer_cleanup_deleted(struct yasl_loop *l) +{ + if (!l || l->timer_firing_depth > 0) return; + int dest = 0; + for (int src = 0; src < l->timer_count; src++) { + if (!l->timers[src].deleted) { + if (dest != src) + l->timers[dest] = l->timers[src]; + dest++; + } + } + l->timer_count = dest; +} + +int yasl_loop_next_timeout_ms(const struct yasl_loop *l) +{ + if (!l || l->timer_count <= 0) return -1; + uint64_t now = yasl_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; + 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; +} + +yasl_timer_id yasl_loop_add_timer(struct yasl_loop *l, uint64_t interval_ns, + yasl_timer_cb cb, void *ud) +{ + if (!l || !cb || interval_ns == 0) return 0; + + if (l->timer_count >= l->timer_capacity) { + int new_cap = l->timer_capacity ? l->timer_capacity * 2 : 16; + struct yasl_timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); + if (!new_timers) 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_loop_now_ns(); + l->timers[l->timer_count++] = (struct yasl_timer_source){ + .fn.ns = cb, + .ud = ud, + .id = id, + .interval_ns = interval_ns, + .deadline_ns = now + interval_ns, + .ms_mode = false, + .deleted = false, + }; + + yasl_loop_kick(l); + return id; +} + +yasl_timer_id yasl_loop_add_timer_ms(struct yasl_loop *l, uint32_t interval_ms, + yasl_timer_ms_cb cb, void *ud) +{ + if (!l || !cb || interval_ms == 0) return 0; + + if (l->timer_count >= l->timer_capacity) { + int new_cap = l->timer_capacity ? l->timer_capacity * 2 : 16; + struct yasl_timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); + if (!new_timers) 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_loop_now_ns(); + uint64_t interval_ns = (uint64_t)interval_ms * 1000000ull; + l->timers[l->timer_count++] = (struct yasl_timer_source){ + .fn.ms = cb, + .ud = ud, + .id = id, + .interval_ns = interval_ns, + .deadline_ns = now + interval_ns, + .ms_mode = true, + .deleted = false, + }; + + yasl_loop_kick(l); + return id; +} + +bool yasl_loop_del_timer(struct yasl_loop *l, yasl_timer_id id) +{ + if (!l || id == 0) return false; + for (int i = 0; i < l->timer_count; i++) { + if (l->timers[i].id == id && !l->timers[i].deleted) { + l->timers[i].deleted = true; + if (l->timer_firing_depth == 0) { + timer_cleanup_deleted(l); + } + return true; + } + } + return false; +} + int yasl_loop_step(struct yasl_loop *l, int timeout_ms) { if (!l) return -1; + + int timer_to = yasl_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 (timeout_ms > 0) { + if (effective_timeout > 0) { struct timespec req = { - .tv_sec = timeout_ms / 1000, - .tv_nsec = (long)(timeout_ms % 1000) * 1000000L, + .tv_sec = effective_timeout / 1000, + .tv_nsec = (long)(effective_timeout % 1000) * 1000000L, }; nanosleep(&req, NULL); } - return 0; + } else { + int n; + do { + n = poll(l->pollfds, (nfds_t)l->count, effective_timeout); + } while (n < 0 && errno == EINTR); + + if (n < 0) return -1; } - int n; - do { - n = poll(l->pollfds, (nfds_t)l->count, timeout_ms); - } while (n < 0 && errno == EINTR); + int handled = 0; - if (n < 0) return -1; - if (n == 0) return 0; + if (l->count > 0) { + int count = l->count; + struct yasl_fd_source sstack[32]; + struct pollfd pstack[32]; + struct yasl_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; + } - int handled = 0; - int count = l->count; - - struct yasl_fd_source sstack[32]; - struct pollfd pstack[32]; - struct yasl_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; + 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; + // Verify that the source was not deleted by an earlier callback in this step + bool valid = false; + 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++; + } + } + } + + if (scopy != sstack) free(scopy); + if (pcopy != pstack) free(pcopy); } - 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; - // Verify that the source was not deleted by an earlier callback in this step - bool valid = false; - for (int j = 0; j < l->count; j++) { - if (l->fds[j].id == scopy[i].id) { - l->pollfds[j].revents = 0; - valid = true; + if (l->timer_count > 0) { + uint64_t now = yasl_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_timer_id tid = l->timers[i].id; + uint64_t interval = l->timers[i].interval_ns; + bool is_ms = l->timers[i].ms_mode; + void *ud = l->timers[i].ud; + struct yasl_timer_source timer_src = l->timers[i]; + + uint64_t next_interval = 0; + if (is_ms) { + uint32_t old_ms = (uint32_t)(interval / 1000000ull); + uint32_t res_ms = timer_src.fn.ms(ud, tid, old_ms); + next_interval = (uint64_t)res_ms * 1000000ull; + } else { + next_interval = timer_src.fn.ns(ud, tid, interval); + } + handled++; + + 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; } } - if (valid && scopy[i].fn) { - scopy[i].fn(scopy[i].fd, revents, scopy[i].ud); - handled++; - } + } + l->timer_firing_depth--; + if (l->timer_firing_depth == 0) { + timer_cleanup_deleted(l); } } - if (scopy != sstack) free(scopy); - if (pcopy != pstack) free(pcopy); return handled; } -- cgit v1.2.3