#include "timer.h" #include #include #include "stb_ds.h" /* Global timer state */ static timer_t *timers = NULL; static Uint32 next_timer_id = 1; static SDL_Mutex *timer_mutex = NULL; static SDL_Condition *timer_cond = NULL; static SDL_Thread *timer_thread = NULL; static SDL_AtomicInt timer_running; static int timer_loop(void *data) { while (SDL_GetAtomicInt(&timer_running)) { SDL_LockMutex(timer_mutex); uint64_t to_ns = next_timeout_ns(); if (to_ns == UINT64_MAX) { /* No timers, wait indefinitely */ SDL_WaitCondition(timer_cond, timer_mutex); } else if (to_ns == 0) { /* Timer(s) already due, process immediately */ SDL_UnlockMutex(timer_mutex); process_due_timers(); continue; } else { /* Wait until next timer is due or a new timer is added */ Uint32 wait_ms = (Uint32)(to_ns / 1000000); if (wait_ms == 0) wait_ms = 1; /* Minimum 1ms wait */ SDL_WaitConditionTimeout(timer_cond, timer_mutex, wait_ms); } SDL_UnlockMutex(timer_mutex); /* Process any due timers */ process_due_timers(); } return 0; } void timer_init(void) { if (!timer_mutex) { timer_mutex = SDL_CreateMutex(); timer_cond = SDL_CreateCondition(); SDL_SetAtomicInt(&timer_running, 1); timer_thread = SDL_CreateThread(timer_loop, "timer thread", NULL); } } uint64_t get_time_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec; } Uint32 add_timer_ns(uint64_t delay_ns, TimerCallback callback, void *param) { timer_t t; SDL_LockMutex(timer_mutex); t.id = next_timer_id++; t.interval_ns = delay_ns; t.due_ns = get_time_ns() + delay_ns; t.callback = callback; t.param = param; arrput(timers, t); SDL_UnlockMutex(timer_mutex); SDL_SignalCondition(timer_cond); return t.id; } void remove_timer(Uint32 id) { SDL_LockMutex(timer_mutex); for (int i = 0; i < arrlen(timers); i++) { if (timers[i].id == id) { arrdel(timers, i); break; } } SDL_UnlockMutex(timer_mutex); } void process_due_timers(void) { uint64_t now = get_time_ns(); SDL_LockMutex(timer_mutex); for (int i = 0; i < arrlen(timers); i++) { if (timers[i].due_ns <= now) { timer_t t = timers[i]; arrdel(timers, i); SDL_UnlockMutex(timer_mutex); /* Convert interval_ns back to milliseconds for the callback */ Uint32 next_ms = t.callback(t.id, t.interval_ns, t.param); if (next_ms > 0) { uint64_t next_ns = (uint64_t)next_ms * 1000000ull; add_timer_ns(next_ns, t.callback, t.param); } /* restart scan because array may have shifted */ SDL_LockMutex(timer_mutex); i = -1; continue; } } SDL_UnlockMutex(timer_mutex); } uint64_t next_timeout_ns(void) { uint64_t min_due = UINT64_MAX; uint64_t now = get_time_ns(); SDL_LockMutex(timer_mutex); if (timers && arrlen(timers) > 0) { min_due = timers[0].due_ns; for (int i = 1; i < arrlen(timers); i++) { if (timers[i].due_ns < min_due) min_due = timers[i].due_ns; } } SDL_UnlockMutex(timer_mutex); if (min_due == UINT64_MAX) return UINT64_MAX; if (min_due <= now) return 0; return min_due - now; } void timer_quit(void) { if (timer_thread) { SDL_SetAtomicInt(&timer_running, 0); SDL_SignalCondition(timer_cond); SDL_WaitThread(timer_thread, NULL); timer_thread = NULL; } if (timer_cond) { SDL_DestroyCondition(timer_cond); timer_cond = NULL; } if (timer_mutex) { SDL_DestroyMutex(timer_mutex); timer_mutex = NULL; } arrfree(timers); timers = NULL; }