#include "SDL_internal.h"
#include "SDL_timer_c.h"
#include "../thread/SDL_systhread.h"
#if !defined(SDL_PLATFORM_EMSCRIPTEN) || !defined(SDL_THREADS_DISABLED)
typedef struct SDL_Timer
{
SDL_TimerID timerID;
SDL_TimerCallback callback_ms;
SDL_NSTimerCallback callback_ns;
void *userdata;
Uint64 interval;
Uint64 scheduled;
SDL_AtomicInt canceled;
struct SDL_Timer *next;
} SDL_Timer;
typedef struct SDL_TimerMap
{
SDL_TimerID timerID;
SDL_Timer *timer;
struct SDL_TimerMap *next;
} SDL_TimerMap;
typedef struct
{
SDL_InitState init;
SDL_Thread *thread;
SDL_TimerMap *timermap;
SDL_Mutex *timermap_lock;
char cache_pad[SDL_CACHELINE_SIZE];
SDL_SpinLock lock;
SDL_Semaphore *sem;
SDL_Timer *pending;
SDL_Timer *freelist;
SDL_AtomicInt active;
SDL_Timer *timers;
} SDL_TimerData;
static SDL_TimerData SDL_timer_data;
static void SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer)
{
SDL_Timer *prev, *curr;
prev = NULL;
for (curr = data->timers; curr; prev = curr, curr = curr->next) {
if (curr->scheduled > timer->scheduled) {
break;
}
}
if (prev) {
prev->next = timer;
} else {
data->timers = timer;
}
timer->next = curr;
}
static int SDLCALL SDL_TimerThread(void *_data)
{
SDL_TimerData *data = (SDL_TimerData *)_data;
SDL_Timer *pending;
SDL_Timer *current;
SDL_Timer *freelist_head = NULL;
SDL_Timer *freelist_tail = NULL;
Uint64 tick, now, interval, delay;
for (;;) {
SDL_LockSpinlock(&data->lock);
{
pending = data->pending;
data->pending = NULL;
if (freelist_head) {
freelist_tail->next = data->freelist;
data->freelist = freelist_head;
}
}
SDL_UnlockSpinlock(&data->lock);
while (pending) {
current = pending;
pending = pending->next;
SDL_AddTimerInternal(data, current);
}
freelist_head = NULL;
freelist_tail = NULL;
if (!SDL_GetAtomicInt(&data->active)) {
break;
}
delay = (Uint64)-1;
tick = SDL_GetTicksNS();
while (data->timers) {
current = data->timers;
if (tick < current->scheduled) {
delay = (current->scheduled - tick);
break;
}
data->timers = current->next;
if (SDL_GetAtomicInt(¤t->canceled)) {
interval = 0;
} else {
if (current->callback_ms) {
interval = SDL_MS_TO_NS(current->callback_ms(current->userdata, current->timerID, (Uint32)SDL_NS_TO_MS(current->interval)));
} else {
interval = current->callback_ns(current->userdata, current->timerID, current->interval);
}
}
if (interval > 0) {
current->interval = interval;
current->scheduled = tick + interval;
SDL_AddTimerInternal(data, current);
} else {
if (!freelist_head) {
freelist_head = current;
}
if (freelist_tail) {
freelist_tail->next = current;
}
freelist_tail = current;
SDL_SetAtomicInt(¤t->canceled, 1);
}
}
now = SDL_GetTicksNS();
interval = (now - tick);
if (interval > delay) {
delay = 0;
} else {
delay -= interval;
}
SDL_WaitSemaphoreTimeoutNS(data->sem, delay);
}
return 0;
}
bool SDL_InitTimers(void)
{
SDL_TimerData *data = &SDL_timer_data;
if (!SDL_ShouldInit(&data->init)) {
return true;
}
data->timermap_lock = SDL_CreateMutex();
if (!data->timermap_lock) {
goto error;
}
data->sem = SDL_CreateSemaphore(0);
if (!data->sem) {
goto error;
}
SDL_SetAtomicInt(&data->active, true);
data->thread = SDL_CreateThread(SDL_TimerThread, "SDLTimer", data);
if (!data->thread) {
goto error;
}
SDL_SetInitialized(&data->init, true);
return true;
error:
SDL_SetInitialized(&data->init, true);
SDL_QuitTimers();
return false;
}
void SDL_QuitTimers(void)
{
SDL_TimerData *data = &SDL_timer_data;
SDL_Timer *timer;
SDL_TimerMap *entry;
if (!SDL_ShouldQuit(&data->init)) {
return;
}
SDL_SetAtomicInt(&data->active, false);
if (data->thread) {
SDL_SignalSemaphore(data->sem);
SDL_WaitThread(data->thread, NULL);
data->thread = NULL;
}
if (data->sem) {
SDL_DestroySemaphore(data->sem);
data->sem = NULL;
}
while (data->timers) {
timer = data->timers;
data->timers = timer->next;
SDL_free(timer);
}
while (data->freelist) {
timer = data->freelist;
data->freelist = timer->next;
SDL_free(timer);
}
while (data->timermap) {
entry = data->timermap;
data->timermap = entry->next;
SDL_free(entry);
}
if (data->timermap_lock) {
SDL_DestroyMutex(data->timermap_lock);
data->timermap_lock = NULL;
}
SDL_SetInitialized(&data->init, false);
}
static bool SDL_CheckInitTimers(void)
{
return SDL_InitTimers();
}
static SDL_TimerID SDL_CreateTimer(Uint64 interval, SDL_TimerCallback callback_ms, SDL_NSTimerCallback callback_ns, void *userdata)
{
SDL_TimerData *data = &SDL_timer_data;
SDL_Timer *timer;
SDL_TimerMap *entry;
if (!callback_ms && !callback_ns) {
SDL_InvalidParamError("callback");
return 0;
}
if (!SDL_CheckInitTimers()) {
return 0;
}
SDL_LockSpinlock(&data->lock);
timer = data->freelist;
if (timer) {
data->freelist = timer->next;
}
SDL_UnlockSpinlock(&data->lock);
if (timer) {
SDL_RemoveTimer(timer->timerID);
} else {
timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
if (!timer) {
return 0;
}
}
timer->timerID = SDL_GetNextObjectID();
timer->callback_ms = callback_ms;
timer->callback_ns = callback_ns;
timer->userdata = userdata;
timer->interval = interval;
timer->scheduled = SDL_GetTicksNS() + timer->interval;
SDL_SetAtomicInt(&timer->canceled, 0);
entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
if (!entry) {
SDL_free(timer);
return 0;
}
entry->timer = timer;
entry->timerID = timer->timerID;
SDL_LockMutex(data->timermap_lock);
entry->next = data->timermap;
data->timermap = entry;
SDL_UnlockMutex(data->timermap_lock);
SDL_LockSpinlock(&data->lock);
timer->next = data->pending;
data->pending = timer;
SDL_UnlockSpinlock(&data->lock);
SDL_SignalSemaphore(data->sem);
return entry->timerID;
}
SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *userdata)
{
return SDL_CreateTimer(SDL_MS_TO_NS(interval), callback, NULL, userdata);
}
SDL_TimerID SDL_AddTimerNS(Uint64 interval, SDL_NSTimerCallback callback, void *userdata)
{
return SDL_CreateTimer(interval, NULL, callback, userdata);
}
bool SDL_RemoveTimer(SDL_TimerID id)
{
SDL_TimerData *data = &SDL_timer_data;
SDL_TimerMap *prev, *entry;
bool canceled = false;
if (!id) {
return SDL_InvalidParamError("id");
}
SDL_LockMutex(data->timermap_lock);
prev = NULL;
for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
if (entry->timerID == id) {
if (prev) {
prev->next = entry->next;
} else {
data->timermap = entry->next;
}
break;
}
}
SDL_UnlockMutex(data->timermap_lock);
if (entry) {
if (!SDL_GetAtomicInt(&entry->timer->canceled)) {
SDL_SetAtomicInt(&entry->timer->canceled, 1);
canceled = true;
}
SDL_free(entry);
}
if (canceled) {
return true;
} else {
return SDL_SetError("Timer not found");
}
}
#else
#include <emscripten/emscripten.h>
#include <emscripten/eventloop.h>
typedef struct SDL_TimerMap
{
SDL_TimerID timerID;
int timeoutID;
Uint64 interval;
SDL_TimerCallback callback_ms;
SDL_NSTimerCallback callback_ns;
void *userdata;
struct SDL_TimerMap *next;
} SDL_TimerMap;
typedef struct
{
SDL_TimerMap *timermap;
} SDL_TimerData;
static SDL_TimerData SDL_timer_data;
static void SDL_Emscripten_TimerHelper(void *userdata)
{
SDL_TimerMap *entry = (SDL_TimerMap *)userdata;
if (entry->callback_ms) {
entry->interval = SDL_MS_TO_NS(entry->callback_ms(entry->userdata, entry->timerID, (Uint32)SDL_NS_TO_MS(entry->interval)));
} else {
entry->interval = entry->callback_ns(entry->userdata, entry->timerID, entry->interval);
}
if (entry->interval > 0) {
entry->timeoutID = emscripten_set_timeout(&SDL_Emscripten_TimerHelper,
SDL_NS_TO_MS(entry->interval),
entry);
}
}
bool SDL_InitTimers(void)
{
return true;
}
void SDL_QuitTimers(void)
{
SDL_TimerData *data = &SDL_timer_data;
SDL_TimerMap *entry;
while (data->timermap) {
entry = data->timermap;
data->timermap = entry->next;
SDL_free(entry);
}
}
static SDL_TimerID SDL_CreateTimer(Uint64 interval, SDL_TimerCallback callback_ms, SDL_NSTimerCallback callback_ns, void *userdata)
{
SDL_TimerData *data = &SDL_timer_data;
SDL_TimerMap *entry;
if (!callback_ms && !callback_ns) {
SDL_InvalidParamError("callback");
return 0;
}
entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
if (!entry) {
return 0;
}
entry->timerID = SDL_GetNextObjectID();
entry->callback_ms = callback_ms;
entry->callback_ns = callback_ns;
entry->userdata = userdata;
entry->interval = interval;
entry->timeoutID = emscripten_set_timeout(&SDL_Emscripten_TimerHelper,
SDL_NS_TO_MS(entry->interval),
entry);
entry->next = data->timermap;
data->timermap = entry;
return entry->timerID;
}
SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *userdata)
{
return SDL_CreateTimer(SDL_MS_TO_NS(interval), callback, NULL, userdata);
}
SDL_TimerID SDL_AddTimerNS(Uint64 interval, SDL_NSTimerCallback callback, void *userdata)
{
return SDL_CreateTimer(interval, NULL, callback, userdata);
}
bool SDL_RemoveTimer(SDL_TimerID id)
{
SDL_TimerData *data = &SDL_timer_data;
SDL_TimerMap *prev, *entry;
if (!id) {
return SDL_InvalidParamError("id");
}
prev = NULL;
for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
if (entry->timerID == id) {
if (prev) {
prev->next = entry->next;
} else {
data->timermap = entry->next;
}
break;
}
}
if (entry) {
emscripten_clear_timeout(entry->timeoutID);
SDL_free(entry);
return true;
} else {
return SDL_SetError("Timer not found");
}
}
#endif
static Uint64 tick_start;
static Uint32 tick_numerator_ns;
static Uint32 tick_denominator_ns;
static Uint32 tick_numerator_ms;
static Uint32 tick_denominator_ms;
#if defined(SDL_TIMER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
#include <mmsystem.h>
#define HAVE_TIME_BEGIN_PERIOD
#endif
static void SDL_SetSystemTimerResolutionMS(int period)
{
#ifdef HAVE_TIME_BEGIN_PERIOD
static int timer_period = 0;
if (period != timer_period) {
if (timer_period) {
timeEndPeriod((UINT)timer_period);
}
timer_period = period;
if (timer_period) {
timeBeginPeriod((UINT)timer_period);
}
}
#endif
}
static void SDLCALL SDL_TimerResolutionChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
int period;
if (hint && *hint) {
period = SDL_atoi(hint);
} else {
period = 1;
}
if (period || oldValue != hint) {
SDL_SetSystemTimerResolutionMS(period);
}
}
void SDL_InitTicks(void)
{
Uint64 tick_freq;
Uint32 gcd;
if (tick_start) {
return;
}
SDL_AddHintCallback(SDL_HINT_TIMER_RESOLUTION,
SDL_TimerResolutionChanged, NULL);
tick_freq = SDL_GetPerformanceFrequency();
SDL_assert(tick_freq > 0 && tick_freq <= (Uint64)SDL_MAX_UINT32);
gcd = SDL_CalculateGCD(SDL_NS_PER_SECOND, (Uint32)tick_freq);
tick_numerator_ns = (SDL_NS_PER_SECOND / gcd);
tick_denominator_ns = (Uint32)(tick_freq / gcd);
gcd = SDL_CalculateGCD(SDL_MS_PER_SECOND, (Uint32)tick_freq);
tick_numerator_ms = (SDL_MS_PER_SECOND / gcd);
tick_denominator_ms = (Uint32)(tick_freq / gcd);
tick_start = SDL_GetPerformanceCounter();
if (!tick_start) {
--tick_start;
}
}
void SDL_QuitTicks(void)
{
SDL_RemoveHintCallback(SDL_HINT_TIMER_RESOLUTION,
SDL_TimerResolutionChanged, NULL);
SDL_SetSystemTimerResolutionMS(0);
tick_start = 0;
}
Uint64 SDL_GetTicksNS(void)
{
Uint64 starting_value, value;
if (!tick_start) {
SDL_InitTicks();
}
starting_value = (SDL_GetPerformanceCounter() - tick_start);
value = (starting_value * tick_numerator_ns);
SDL_assert(value >= starting_value);
value /= tick_denominator_ns;
return value;
}
Uint64 SDL_GetTicks(void)
{
Uint64 starting_value, value;
if (!tick_start) {
SDL_InitTicks();
}
starting_value = (SDL_GetPerformanceCounter() - tick_start);
value = (starting_value * tick_numerator_ms);
SDL_assert(value >= starting_value);
value /= tick_denominator_ms;
return value;
}
void SDL_Delay(Uint32 ms)
{
SDL_SYS_DelayNS(SDL_MS_TO_NS(ms));
}
void SDL_DelayNS(Uint64 ns)
{
SDL_SYS_DelayNS(ns);
}
void SDL_DelayPrecise(Uint64 ns)
{
Uint64 current_value = SDL_GetTicksNS();
const Uint64 target_value = current_value + ns;
const Uint64 SHORT_SLEEP_NS = 1 * SDL_NS_PER_MS;
Uint64 max_sleep_ns = SHORT_SLEEP_NS;
while (current_value + max_sleep_ns < target_value) {
SDL_SYS_DelayNS(SHORT_SLEEP_NS);
const Uint64 now = SDL_GetTicksNS();
const Uint64 next_sleep_ns = (now - current_value);
if (next_sleep_ns > max_sleep_ns) {
max_sleep_ns = next_sleep_ns;
}
current_value = now;
}
if (current_value < target_value && (target_value - current_value) > (max_sleep_ns - SHORT_SLEEP_NS)) {
const Uint64 delay_ns = (target_value - current_value) - (max_sleep_ns - SHORT_SLEEP_NS);
SDL_SYS_DelayNS(delay_ns);
current_value = SDL_GetTicksNS();
}
while (current_value + SHORT_SLEEP_NS < target_value) {
SDL_SYS_DelayNS(SHORT_SLEEP_NS);
current_value = SDL_GetTicksNS();
}
while (current_value < target_value) {
SDL_CPUPauseInstruction();
current_value = SDL_GetTicksNS();
}
}