summaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 00:29:34 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 00:29:34 +0300
commitf7fa0930cb46046f995fa1ab927aef62466e995e (patch)
treef5ac4e1579811b5d69498a19d0bdfbf128a1d62b /src/common
parent624d1371304a5ae4b3429ad5e4267aac7cd03dfd (diff)
feat: add SDL3-style surface, display, and loop timer API for X11, Wayland, and Cocoa
Diffstat (limited to 'src/common')
-rw-r--r--src/common/backend.c106
-rw-r--r--src/common/backend.h6
-rw-r--r--src/common/loop.c267
3 files changed, 340 insertions, 39 deletions
diff --git a/src/common/backend.c b/src/common/backend.c
index 214854b..27ab297 100644
--- a/src/common/backend.c
+++ b/src/common/backend.c
@@ -181,6 +181,7 @@ void yasl_surface_destroy(struct yasl_surface *s)
{
if (!s || !s->display->ops->surface_destroy)
return;
+ yasl_surface_clear_timers(s);
s->display->ops->surface_destroy(s);
}
@@ -368,3 +369,108 @@ void *yasl_surface_get_native_handle(struct yasl_surface *s)
return 0;
return s->display->ops->surface_get_native_handle(s);
}
+
+void yasl_surface_clear_timers(struct yasl_surface *s)
+{
+ if (!s || !s->timers) return;
+ 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_loop_del_timer(loop, s->timers[i]);
+ }
+ }
+ free(s->timers);
+ s->timers = NULL;
+ s->timer_count = 0;
+ s->timer_capacity = 0;
+}
+
+yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_ns,
+ yasl_timer_cb cb, void *ud)
+{
+ if (!s || !s->display || !s->display->loop || !cb || interval_ns == 0)
+ return 0;
+
+ yasl_timer_id id = yasl_loop_add_timer(s->display->loop, interval_ns, cb, ud);
+ if (!id) return 0;
+
+ if (s->timer_count >= s->timer_capacity) {
+ int new_cap = s->timer_capacity ? s->timer_capacity * 2 : 4;
+ yasl_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt));
+ if (!nt) {
+ yasl_loop_del_timer(s->display->loop, id);
+ return 0;
+ }
+ s->timers = nt;
+ s->timer_capacity = new_cap;
+ }
+ s->timers[s->timer_count++] = id;
+ return id;
+}
+
+yasl_timer_id yasl_surface_add_timer_ms(struct yasl_surface *s, uint32_t interval_ms,
+ yasl_timer_ms_cb cb, void *ud)
+{
+ if (!s || !s->display || !s->display->loop || !cb || interval_ms == 0)
+ return 0;
+
+ yasl_timer_id id = yasl_loop_add_timer_ms(s->display->loop, interval_ms, cb, ud);
+ if (!id) return 0;
+
+ if (s->timer_count >= s->timer_capacity) {
+ int new_cap = s->timer_capacity ? s->timer_capacity * 2 : 4;
+ yasl_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt));
+ if (!nt) {
+ yasl_loop_del_timer(s->display->loop, id);
+ return 0;
+ }
+ s->timers = nt;
+ s->timer_capacity = new_cap;
+ }
+ s->timers[s->timer_count++] = id;
+ return id;
+}
+
+bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id)
+{
+ if (!s || id == 0) return false;
+ for (int i = 0; i < s->timer_count; i++) {
+ if (s->timers[i] == id) {
+ s->timers[i] = 0;
+ if (s->display && s->display->loop)
+ yasl_loop_del_timer(s->display->loop, id);
+ return true;
+ }
+ }
+ return false;
+}
+
+yasl_timer_id yasl_display_add_timer(struct yasl_display *d, uint64_t interval_ns,
+ yasl_timer_cb cb, void *ud)
+{
+ if (!d || !d->loop) return 0;
+ return yasl_loop_add_timer(d->loop, interval_ns, cb, ud);
+}
+
+yasl_timer_id yasl_display_add_timer_ms(struct yasl_display *d, uint32_t interval_ms,
+ yasl_timer_ms_cb cb, void *ud)
+{
+ if (!d || !d->loop) return 0;
+ return yasl_loop_add_timer_ms(d->loop, interval_ms, cb, ud);
+}
+
+bool yasl_display_del_timer(struct yasl_display *d, yasl_timer_id id)
+{
+ if (!d || !d->loop) return false;
+ return yasl_loop_del_timer(d->loop, id);
+}
+
+struct yasl_loop *yasl_display_loop(struct yasl_display *d)
+{
+ return d ? d->loop : NULL;
+}
+
+struct yasl_loop *yasl_surface_loop(struct yasl_surface *s)
+{
+ return (s && s->display) ? s->display->loop : NULL;
+}
diff --git a/src/common/backend.h b/src/common/backend.h
index c6691eb..b52a147 100644
--- a/src/common/backend.h
+++ b/src/common/backend.h
@@ -8,14 +8,20 @@ struct yasl_backend_ops;
struct yasl_display {
const struct yasl_backend_ops *ops;
struct yasl_display_cbs callbacks;
+ struct yasl_loop *loop;
};
struct yasl_surface {
struct yasl_display *display;
struct yasl_surface_cbs callbacks;
uint64_t frame_generation;
+ yasl_timer_id *timers;
+ int timer_count;
+ int timer_capacity;
};
+void yasl_surface_clear_timers(struct yasl_surface *s);
+
static inline uint64_t yasl_frame_begin(struct yasl_surface *s)
{
if (++s->frame_generation == 0) ++s->frame_generation;
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;
}