From 99599d923602c6d6444a316898066ff30b2c0bdd Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Mon, 27 May 2024 06:30:37 -0700 Subject: [PATCH] Added SDL_AddTimerNS() --- include/SDL3/SDL_timer.h | 64 ++++++++++++++++++++++++++++++- src/dynapi/SDL_dynapi.sym | 1 + src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + src/timer/SDL_timer.c | 57 ++++++++++++++++++++------- test/testtimer.c | 52 ++++++++++++++++++++----- 6 files changed, 152 insertions(+), 24 deletions(-) diff --git a/include/SDL3/SDL_timer.h b/include/SDL3/SDL_timer.h index 200a7fba0..9bac551a5 100644 --- a/include/SDL3/SDL_timer.h +++ b/include/SDL3/SDL_timer.h @@ -132,7 +132,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_DelayNS(Uint64 ns); typedef Uint32 SDL_TimerID; /** - * Function prototype for the timer callback function. + * Function prototype for the millisecond timer callback function. * * The callback function is passed the current timer interval and returns the * next timer interval, in milliseconds. If the returned value is the same as @@ -187,10 +187,72 @@ typedef Uint32 (SDLCALL *SDL_TimerCallback)(void *userdata, SDL_TimerID timerID, * * \since This function is available since SDL 3.0.0. * + * \sa SDL_AddTimerNS * \sa SDL_RemoveTimer */ extern SDL_DECLSPEC SDL_TimerID SDLCALL SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *userdata); +/** + * Function prototype for the nanosecond timer callback function. + * + * The callback function is passed the current timer interval and returns the + * next timer interval, in nanoseconds. If the returned value is the same as + * the one passed in, the periodic alarm continues, otherwise a new alarm is + * scheduled. If the callback returns 0, the periodic alarm is cancelled. + * + * \param userdata an arbitrary pointer provided by the app through SDL_AddTimer, for its own use. + * \param timerID the current timer being processed + * \param interval the current callback time interval. + * \returns the new callback time interval, or 0 to disable further runs of + * the callback. + * + * \threadsafety SDL may call this callback at any time from a background + * thread; the application is responsible for locking resources + * the callback touches that need to be protected. + * + * \since This datatype is available since SDL 3.0.0. + * + * \sa SDL_AddTimerNS + */ +typedef Uint64 (SDLCALL *SDL_NSTimerCallback)(void *userdata, SDL_TimerID timerID, Uint64 interval); + +/** + * Call a callback function at a future time. + * + * If you use this function, you must pass `SDL_INIT_TIMER` to SDL_Init(). + * + * The callback function is passed the current timer interval and the user + * supplied parameter from the SDL_AddTimerNS() call and should return the next + * timer interval. If the value returned from the callback is 0, the timer is + * canceled. + * + * The callback is run on a separate thread. + * + * Timers take into account the amount of time it took to execute the + * callback. For example, if the callback took 250 ns to execute and returned + * 1000 (ns), the timer would only wait another 750 ns before its next + * iteration. + * + * Timing may be inexact due to OS scheduling. Be sure to note the current + * time with SDL_GetTicksNS() or SDL_GetPerformanceCounter() in case your + * callback needs to adjust for variances. + * + * \param interval the timer delay, in nanoseconds, passed to `callback` + * \param callback the SDL_TimerCallback function to call when the specified + * `interval` elapses + * \param userdata a pointer that is passed to `callback` + * \returns a timer ID or 0 if an error occurs; call SDL_GetError() for more + * information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_AddTimer + * \sa SDL_RemoveTimer + */ +extern SDL_DECLSPEC SDL_TimerID SDLCALL SDL_AddTimerNS(Uint64 interval, SDL_NSTimerCallback callback, void *userdata); + /** * Remove a timer created with SDL_AddTimer(). * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 744cd0eb7..f67090060 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -9,6 +9,7 @@ SDL3_0.0.0 { SDL_AddGamepadMappingsFromIO; SDL_AddHintCallback; SDL_AddTimer; + SDL_AddTimerNS; SDL_AddVulkanRenderSemaphores; SDL_AllocateEventMemory; SDL_AndroidBackButton; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 2e5558159..629a12d8d 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -34,6 +34,7 @@ #define SDL_AddGamepadMappingsFromIO SDL_AddGamepadMappingsFromIO_REAL #define SDL_AddHintCallback SDL_AddHintCallback_REAL #define SDL_AddTimer SDL_AddTimer_REAL +#define SDL_AddTimerNS SDL_AddTimerNS_REAL #define SDL_AddVulkanRenderSemaphores SDL_AddVulkanRenderSemaphores_REAL #define SDL_AllocateEventMemory SDL_AllocateEventMemory_REAL #define SDL_AndroidBackButton SDL_AndroidBackButton_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index a2e016c00..1147fe02f 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -54,6 +54,7 @@ SDL_DYNAPI_PROC(int,SDL_AddGamepadMappingsFromFile,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_AddGamepadMappingsFromIO,(SDL_IOStream *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_AddHintCallback,(const char *a, SDL_HintCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_TimerID,SDL_AddTimer,(Uint32 a, SDL_TimerCallback b, void *c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_TimerID,SDL_AddTimerNS,(Uint64 a, SDL_NSTimerCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_AddVulkanRenderSemaphores,(SDL_Renderer *a, Uint32 b, Sint64 c, Sint64 d),(a,b,c,d),return) SDL_DYNAPI_PROC(void*,SDL_AllocateEventMemory,(size_t a),(a),return) SDL_DYNAPI_PROC(void,SDL_AndroidBackButton,(void),(),) diff --git a/src/timer/SDL_timer.c b/src/timer/SDL_timer.c index 9043a2052..e6e509498 100644 --- a/src/timer/SDL_timer.c +++ b/src/timer/SDL_timer.c @@ -30,7 +30,8 @@ typedef struct SDL_Timer { SDL_TimerID timerID; - SDL_TimerCallback callback; + SDL_TimerCallback callback_ms; + SDL_NSTimerCallback callback_ns; void *userdata; Uint64 interval; Uint64 scheduled; @@ -160,8 +161,11 @@ static int SDLCALL SDL_TimerThread(void *_data) if (SDL_AtomicGet(¤t->canceled)) { interval = 0; } else { - /* FIXME: We could potentially support sub-millisecond timers now */ - interval = SDL_MS_TO_NS(current->callback(current->userdata, current->timerID, (Uint32)SDL_NS_TO_MS(current->interval))); + 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) { @@ -269,7 +273,7 @@ void SDL_QuitTimers(void) } } -SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *userdata) +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; @@ -298,9 +302,10 @@ SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *user } } timer->timerID = SDL_GetNextObjectID(); - timer->callback = callback; + timer->callback_ms = callback_ms; + timer->callback_ns = callback_ns; timer->userdata = userdata; - timer->interval = SDL_MS_TO_NS(interval); + timer->interval = interval; timer->scheduled = SDL_GetTicksNS() + timer->interval; SDL_AtomicSet(&timer->canceled, 0); @@ -329,6 +334,16 @@ SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *user 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); +} + int SDL_RemoveTimer(SDL_TimerID id) { SDL_TimerData *data = &SDL_timer_data; @@ -373,8 +388,9 @@ typedef struct SDL_TimerMap { SDL_TimerID timerID; int timeoutID; - Uint32 interval; - SDL_TimerCallback callback; + Uint64 interval; + SDL_TimerCallback callback_ms; + SDL_NSTimerCallback callback_ns; void *userdata; struct SDL_TimerMap *next; } SDL_TimerMap; @@ -389,10 +405,14 @@ static SDL_TimerData SDL_timer_data; static void SDL_Emscripten_TimerHelper(void *userdata) { SDL_TimerMap *entry = (SDL_TimerMap *)userdata; - entry->interval = entry->callback(entry->userdata, entry->timerID, entry->interval); + 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, - entry->interval, + SDL_NS_TO_MS(entry->interval), entry); } } @@ -414,7 +434,7 @@ void SDL_QuitTimers(void) } } -SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *userdata) +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; @@ -424,12 +444,13 @@ SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *user return 0; } entry->timerID = SDL_GetNextObjectID(); - entry->callback = callback; + entry->callback_ms = callback_ms; + entry->callback_ns = callback_ns; entry->userdata = userdata; entry->interval = interval; entry->timeoutID = emscripten_set_timeout(&SDL_Emscripten_TimerHelper, - entry->interval, + SDL_NS_TO_MS(entry->interval), entry); entry->next = data->timermap; @@ -438,6 +459,16 @@ SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *user 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); +} + int SDL_RemoveTimer(SDL_TimerID id) { SDL_TimerData *data = &SDL_timer_data; diff --git a/test/testtimer.c b/test/testtimer.c index 63456f7fc..de3884292 100644 --- a/test/testtimer.c +++ b/test/testtimer.c @@ -56,6 +56,13 @@ ticktock(void *param, SDL_TimerID timerID, Uint32 interval) return interval; } +static Uint64 SDLCALL +ticktockNS(void *param, SDL_TimerID timerID, Uint64 interval) +{ + ++ticks; + return interval; +} + static Uint32 SDLCALL callback(void *param, SDL_TimerID timerID, Uint32 interval) { @@ -136,25 +143,49 @@ int main(int argc, char *argv[]) } } - /* Start the timer */ + /* Start the millisecond timer */ if (desired < 0) { desired = DEFAULT_RESOLUTION; } + ticks = 0; t1 = SDL_AddTimer(desired, ticktock, NULL); - /* Wait 10 seconds */ - SDL_Log("Waiting 10 seconds\n"); - SDL_Delay(10 * 1000); + /* Wait 1 seconds */ + SDL_Log("Waiting 1 seconds for millisecond timer\n"); + SDL_Delay(1 * 1000); /* Stop the timer */ SDL_RemoveTimer(t1); /* Print the results */ if (ticks) { - SDL_Log("Timer resolution: desired = %d ms, actual = %f ms\n", + SDL_Log("Millisecond timer resolution: desired = %d ms, actual = %f ms\n", desired, (double)(10 * 1000) / ticks); } + /* Wait for the results to be seen */ + SDL_Delay(1 * 1000); + + /* Start the nanosecond timer */ + ticks = 0; + t1 = SDL_AddTimerNS(desired, ticktockNS, NULL); + + /* Wait 1 seconds */ + SDL_Log("Waiting 1 seconds for nanosecond timer\n"); + SDL_Delay(1 * 1000); + + /* Stop the timer */ + SDL_RemoveTimer(t1); + + /* Print the results */ + if (ticks) { + SDL_Log("Nanosecond timer resolution: desired = %d ns, actual = %f ns\n", + desired, (double)(10 * 1000000) / ticks); + } + + /* Wait for the results to be seen */ + SDL_Delay(1 * 1000); + /* Test multiple timers */ SDL_Log("Testing multiple timers...\n"); t1 = SDL_AddTimer(100, callback, (void *)1); @@ -170,18 +201,19 @@ int main(int argc, char *argv[]) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Could not create timer 3: %s\n", SDL_GetError()); } - /* Wait 10 seconds */ - SDL_Log("Waiting 10 seconds\n"); - SDL_Delay(10 * 1000); + /* Wait 3 seconds */ + SDL_Log("Waiting 3 seconds\n"); + SDL_Delay(3 * 1000); - SDL_Log("Removing timer 1 and waiting 5 more seconds\n"); + SDL_Log("Removing timer 1 and waiting 3 more seconds\n"); SDL_RemoveTimer(t1); - SDL_Delay(5 * 1000); + SDL_Delay(3 * 1000); SDL_RemoveTimer(t2); SDL_RemoveTimer(t3); + ticks = 0; start_perf = SDL_GetPerformanceCounter(); for (i = 0; i < 1000000; ++i) { ticktock(NULL, 0, 0);