156 lines
3.6 KiB
C
156 lines
3.6 KiB
C
#include "timer.h"
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
#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;
|
|
} |