Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/timer/SDL_timer.c
9903 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
4
5
This software is provided 'as-is', without any express or implied
6
warranty. In no event will the authors be held liable for any damages
7
arising from the use of this software.
8
9
Permission is granted to anyone to use this software for any purpose,
10
including commercial applications, and to alter it and redistribute it
11
freely, subject to the following restrictions:
12
13
1. The origin of this software must not be misrepresented; you must not
14
claim that you wrote the original software. If you use this software
15
in a product, an acknowledgment in the product documentation would be
16
appreciated but is not required.
17
2. Altered source versions must be plainly marked as such, and must not be
18
misrepresented as being the original software.
19
3. This notice may not be removed or altered from any source distribution.
20
*/
21
#include "SDL_internal.h"
22
23
#include "SDL_timer_c.h"
24
#include "../thread/SDL_systhread.h"
25
26
// #define DEBUG_TIMERS
27
28
#if !defined(SDL_PLATFORM_EMSCRIPTEN) || !defined(SDL_THREADS_DISABLED)
29
30
typedef struct SDL_Timer
31
{
32
SDL_TimerID timerID;
33
SDL_TimerCallback callback_ms;
34
SDL_NSTimerCallback callback_ns;
35
void *userdata;
36
Uint64 interval;
37
Uint64 scheduled;
38
SDL_AtomicInt canceled;
39
struct SDL_Timer *next;
40
} SDL_Timer;
41
42
typedef struct SDL_TimerMap
43
{
44
SDL_TimerID timerID;
45
SDL_Timer *timer;
46
struct SDL_TimerMap *next;
47
} SDL_TimerMap;
48
49
// The timers are kept in a sorted list
50
typedef struct
51
{
52
// Data used by the main thread
53
SDL_InitState init;
54
SDL_Thread *thread;
55
SDL_TimerMap *timermap;
56
SDL_Mutex *timermap_lock;
57
58
// Padding to separate cache lines between threads
59
char cache_pad[SDL_CACHELINE_SIZE];
60
61
// Data used to communicate with the timer thread
62
SDL_SpinLock lock;
63
SDL_Semaphore *sem;
64
SDL_Timer *pending;
65
SDL_Timer *freelist;
66
SDL_AtomicInt active;
67
68
// List of timers - this is only touched by the timer thread
69
SDL_Timer *timers;
70
} SDL_TimerData;
71
72
static SDL_TimerData SDL_timer_data;
73
74
/* The idea here is that any thread might add a timer, but a single
75
* thread manages the active timer queue, sorted by scheduling time.
76
*
77
* Timers are removed by simply setting a canceled flag
78
*/
79
80
static void SDL_AddTimerInternal(SDL_TimerData *data, SDL_Timer *timer)
81
{
82
SDL_Timer *prev, *curr;
83
84
prev = NULL;
85
for (curr = data->timers; curr; prev = curr, curr = curr->next) {
86
if (curr->scheduled > timer->scheduled) {
87
break;
88
}
89
}
90
91
// Insert the timer here!
92
if (prev) {
93
prev->next = timer;
94
} else {
95
data->timers = timer;
96
}
97
timer->next = curr;
98
}
99
100
static int SDLCALL SDL_TimerThread(void *_data)
101
{
102
SDL_TimerData *data = (SDL_TimerData *)_data;
103
SDL_Timer *pending;
104
SDL_Timer *current;
105
SDL_Timer *freelist_head = NULL;
106
SDL_Timer *freelist_tail = NULL;
107
Uint64 tick, now, interval, delay;
108
109
/* Threaded timer loop:
110
* 1. Queue timers added by other threads
111
* 2. Handle any timers that should dispatch this cycle
112
* 3. Wait until next dispatch time or new timer arrives
113
*/
114
for (;;) {
115
// Pending and freelist maintenance
116
SDL_LockSpinlock(&data->lock);
117
{
118
// Get any timers ready to be queued
119
pending = data->pending;
120
data->pending = NULL;
121
122
// Make any unused timer structures available
123
if (freelist_head) {
124
freelist_tail->next = data->freelist;
125
data->freelist = freelist_head;
126
}
127
}
128
SDL_UnlockSpinlock(&data->lock);
129
130
// Sort the pending timers into our list
131
while (pending) {
132
current = pending;
133
pending = pending->next;
134
SDL_AddTimerInternal(data, current);
135
}
136
freelist_head = NULL;
137
freelist_tail = NULL;
138
139
// Check to see if we're still running, after maintenance
140
if (!SDL_GetAtomicInt(&data->active)) {
141
break;
142
}
143
144
// Initial delay if there are no timers
145
delay = (Uint64)-1;
146
147
tick = SDL_GetTicksNS();
148
149
// Process all the pending timers for this tick
150
while (data->timers) {
151
current = data->timers;
152
153
if (tick < current->scheduled) {
154
// Scheduled for the future, wait a bit
155
delay = (current->scheduled - tick);
156
break;
157
}
158
159
// We're going to do something with this timer
160
data->timers = current->next;
161
162
if (SDL_GetAtomicInt(&current->canceled)) {
163
interval = 0;
164
} else {
165
if (current->callback_ms) {
166
interval = SDL_MS_TO_NS(current->callback_ms(current->userdata, current->timerID, (Uint32)SDL_NS_TO_MS(current->interval)));
167
} else {
168
interval = current->callback_ns(current->userdata, current->timerID, current->interval);
169
}
170
}
171
172
if (interval > 0) {
173
// Reschedule this timer
174
current->interval = interval;
175
current->scheduled = tick + interval;
176
SDL_AddTimerInternal(data, current);
177
} else {
178
if (!freelist_head) {
179
freelist_head = current;
180
}
181
if (freelist_tail) {
182
freelist_tail->next = current;
183
}
184
freelist_tail = current;
185
186
SDL_SetAtomicInt(&current->canceled, 1);
187
}
188
}
189
190
// Adjust the delay based on processing time
191
now = SDL_GetTicksNS();
192
interval = (now - tick);
193
if (interval > delay) {
194
delay = 0;
195
} else {
196
delay -= interval;
197
}
198
199
/* Note that each time a timer is added, this will return
200
immediately, but we process the timers added all at once.
201
That's okay, it just means we run through the loop a few
202
extra times.
203
*/
204
SDL_WaitSemaphoreTimeoutNS(data->sem, delay);
205
}
206
return 0;
207
}
208
209
bool SDL_InitTimers(void)
210
{
211
SDL_TimerData *data = &SDL_timer_data;
212
213
if (!SDL_ShouldInit(&data->init)) {
214
return true;
215
}
216
217
data->timermap_lock = SDL_CreateMutex();
218
if (!data->timermap_lock) {
219
goto error;
220
}
221
222
data->sem = SDL_CreateSemaphore(0);
223
if (!data->sem) {
224
goto error;
225
}
226
227
SDL_SetAtomicInt(&data->active, true);
228
229
// Timer threads use a callback into the app, so we can't set a limited stack size here.
230
data->thread = SDL_CreateThread(SDL_TimerThread, "SDLTimer", data);
231
if (!data->thread) {
232
goto error;
233
}
234
235
SDL_SetInitialized(&data->init, true);
236
return true;
237
238
error:
239
SDL_SetInitialized(&data->init, true);
240
SDL_QuitTimers();
241
return false;
242
}
243
244
void SDL_QuitTimers(void)
245
{
246
SDL_TimerData *data = &SDL_timer_data;
247
SDL_Timer *timer;
248
SDL_TimerMap *entry;
249
250
if (!SDL_ShouldQuit(&data->init)) {
251
return;
252
}
253
254
SDL_SetAtomicInt(&data->active, false);
255
256
// Shutdown the timer thread
257
if (data->thread) {
258
SDL_SignalSemaphore(data->sem);
259
SDL_WaitThread(data->thread, NULL);
260
data->thread = NULL;
261
}
262
263
if (data->sem) {
264
SDL_DestroySemaphore(data->sem);
265
data->sem = NULL;
266
}
267
268
// Clean up the timer entries
269
while (data->timers) {
270
timer = data->timers;
271
data->timers = timer->next;
272
SDL_free(timer);
273
}
274
while (data->freelist) {
275
timer = data->freelist;
276
data->freelist = timer->next;
277
SDL_free(timer);
278
}
279
while (data->timermap) {
280
entry = data->timermap;
281
data->timermap = entry->next;
282
SDL_free(entry);
283
}
284
285
if (data->timermap_lock) {
286
SDL_DestroyMutex(data->timermap_lock);
287
data->timermap_lock = NULL;
288
}
289
290
SDL_SetInitialized(&data->init, false);
291
}
292
293
static bool SDL_CheckInitTimers(void)
294
{
295
return SDL_InitTimers();
296
}
297
298
static SDL_TimerID SDL_CreateTimer(Uint64 interval, SDL_TimerCallback callback_ms, SDL_NSTimerCallback callback_ns, void *userdata)
299
{
300
SDL_TimerData *data = &SDL_timer_data;
301
SDL_Timer *timer;
302
SDL_TimerMap *entry;
303
304
if (!callback_ms && !callback_ns) {
305
SDL_InvalidParamError("callback");
306
return 0;
307
}
308
309
if (!SDL_CheckInitTimers()) {
310
return 0;
311
}
312
313
SDL_LockSpinlock(&data->lock);
314
timer = data->freelist;
315
if (timer) {
316
data->freelist = timer->next;
317
}
318
SDL_UnlockSpinlock(&data->lock);
319
320
if (timer) {
321
SDL_RemoveTimer(timer->timerID);
322
} else {
323
timer = (SDL_Timer *)SDL_malloc(sizeof(*timer));
324
if (!timer) {
325
return 0;
326
}
327
}
328
timer->timerID = SDL_GetNextObjectID();
329
timer->callback_ms = callback_ms;
330
timer->callback_ns = callback_ns;
331
timer->userdata = userdata;
332
timer->interval = interval;
333
timer->scheduled = SDL_GetTicksNS() + timer->interval;
334
SDL_SetAtomicInt(&timer->canceled, 0);
335
336
entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
337
if (!entry) {
338
SDL_free(timer);
339
return 0;
340
}
341
entry->timer = timer;
342
entry->timerID = timer->timerID;
343
344
SDL_LockMutex(data->timermap_lock);
345
entry->next = data->timermap;
346
data->timermap = entry;
347
SDL_UnlockMutex(data->timermap_lock);
348
349
// Add the timer to the pending list for the timer thread
350
SDL_LockSpinlock(&data->lock);
351
timer->next = data->pending;
352
data->pending = timer;
353
SDL_UnlockSpinlock(&data->lock);
354
355
// Wake up the timer thread if necessary
356
SDL_SignalSemaphore(data->sem);
357
358
return entry->timerID;
359
}
360
361
SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *userdata)
362
{
363
return SDL_CreateTimer(SDL_MS_TO_NS(interval), callback, NULL, userdata);
364
}
365
366
SDL_TimerID SDL_AddTimerNS(Uint64 interval, SDL_NSTimerCallback callback, void *userdata)
367
{
368
return SDL_CreateTimer(interval, NULL, callback, userdata);
369
}
370
371
bool SDL_RemoveTimer(SDL_TimerID id)
372
{
373
SDL_TimerData *data = &SDL_timer_data;
374
SDL_TimerMap *prev, *entry;
375
bool canceled = false;
376
377
if (!id) {
378
return SDL_InvalidParamError("id");
379
}
380
381
// Find the timer
382
SDL_LockMutex(data->timermap_lock);
383
prev = NULL;
384
for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
385
if (entry->timerID == id) {
386
if (prev) {
387
prev->next = entry->next;
388
} else {
389
data->timermap = entry->next;
390
}
391
break;
392
}
393
}
394
SDL_UnlockMutex(data->timermap_lock);
395
396
if (entry) {
397
if (!SDL_GetAtomicInt(&entry->timer->canceled)) {
398
SDL_SetAtomicInt(&entry->timer->canceled, 1);
399
canceled = true;
400
}
401
SDL_free(entry);
402
}
403
if (canceled) {
404
return true;
405
} else {
406
return SDL_SetError("Timer not found");
407
}
408
}
409
410
#else
411
412
#include <emscripten/emscripten.h>
413
#include <emscripten/eventloop.h>
414
415
typedef struct SDL_TimerMap
416
{
417
SDL_TimerID timerID;
418
int timeoutID;
419
Uint64 interval;
420
SDL_TimerCallback callback_ms;
421
SDL_NSTimerCallback callback_ns;
422
void *userdata;
423
struct SDL_TimerMap *next;
424
} SDL_TimerMap;
425
426
typedef struct
427
{
428
SDL_TimerMap *timermap;
429
} SDL_TimerData;
430
431
static SDL_TimerData SDL_timer_data;
432
433
static void SDL_Emscripten_TimerHelper(void *userdata)
434
{
435
SDL_TimerMap *entry = (SDL_TimerMap *)userdata;
436
if (entry->callback_ms) {
437
entry->interval = SDL_MS_TO_NS(entry->callback_ms(entry->userdata, entry->timerID, (Uint32)SDL_NS_TO_MS(entry->interval)));
438
} else {
439
entry->interval = entry->callback_ns(entry->userdata, entry->timerID, entry->interval);
440
}
441
if (entry->interval > 0) {
442
entry->timeoutID = emscripten_set_timeout(&SDL_Emscripten_TimerHelper,
443
SDL_NS_TO_MS(entry->interval),
444
entry);
445
}
446
}
447
448
bool SDL_InitTimers(void)
449
{
450
return true;
451
}
452
453
void SDL_QuitTimers(void)
454
{
455
SDL_TimerData *data = &SDL_timer_data;
456
SDL_TimerMap *entry;
457
458
while (data->timermap) {
459
entry = data->timermap;
460
data->timermap = entry->next;
461
SDL_free(entry);
462
}
463
}
464
465
static SDL_TimerID SDL_CreateTimer(Uint64 interval, SDL_TimerCallback callback_ms, SDL_NSTimerCallback callback_ns, void *userdata)
466
{
467
SDL_TimerData *data = &SDL_timer_data;
468
SDL_TimerMap *entry;
469
470
if (!callback_ms && !callback_ns) {
471
SDL_InvalidParamError("callback");
472
return 0;
473
}
474
475
entry = (SDL_TimerMap *)SDL_malloc(sizeof(*entry));
476
if (!entry) {
477
return 0;
478
}
479
entry->timerID = SDL_GetNextObjectID();
480
entry->callback_ms = callback_ms;
481
entry->callback_ns = callback_ns;
482
entry->userdata = userdata;
483
entry->interval = interval;
484
485
entry->timeoutID = emscripten_set_timeout(&SDL_Emscripten_TimerHelper,
486
SDL_NS_TO_MS(entry->interval),
487
entry);
488
489
entry->next = data->timermap;
490
data->timermap = entry;
491
492
return entry->timerID;
493
}
494
495
SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_TimerCallback callback, void *userdata)
496
{
497
return SDL_CreateTimer(SDL_MS_TO_NS(interval), callback, NULL, userdata);
498
}
499
500
SDL_TimerID SDL_AddTimerNS(Uint64 interval, SDL_NSTimerCallback callback, void *userdata)
501
{
502
return SDL_CreateTimer(interval, NULL, callback, userdata);
503
}
504
505
bool SDL_RemoveTimer(SDL_TimerID id)
506
{
507
SDL_TimerData *data = &SDL_timer_data;
508
SDL_TimerMap *prev, *entry;
509
510
if (!id) {
511
return SDL_InvalidParamError("id");
512
}
513
514
// Find the timer
515
prev = NULL;
516
for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
517
if (entry->timerID == id) {
518
if (prev) {
519
prev->next = entry->next;
520
} else {
521
data->timermap = entry->next;
522
}
523
break;
524
}
525
}
526
527
if (entry) {
528
emscripten_clear_timeout(entry->timeoutID);
529
SDL_free(entry);
530
return true;
531
} else {
532
return SDL_SetError("Timer not found");
533
}
534
}
535
536
#endif // !SDL_PLATFORM_EMSCRIPTEN || !SDL_THREADS_DISABLED
537
538
static Uint64 tick_start;
539
static Uint32 tick_numerator_ns;
540
static Uint32 tick_denominator_ns;
541
static Uint32 tick_numerator_ms;
542
static Uint32 tick_denominator_ms;
543
544
#if defined(SDL_TIMER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
545
#include <mmsystem.h>
546
#define HAVE_TIME_BEGIN_PERIOD
547
#endif
548
549
static void SDL_SetSystemTimerResolutionMS(int period)
550
{
551
#ifdef HAVE_TIME_BEGIN_PERIOD
552
static int timer_period = 0;
553
554
if (period != timer_period) {
555
if (timer_period) {
556
timeEndPeriod((UINT)timer_period);
557
}
558
559
timer_period = period;
560
561
if (timer_period) {
562
timeBeginPeriod((UINT)timer_period);
563
}
564
}
565
#endif // HAVE_TIME_BEGIN_PERIOD
566
}
567
568
static void SDLCALL SDL_TimerResolutionChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
569
{
570
int period;
571
572
// Unless the hint says otherwise, let's have good sleep precision
573
if (hint && *hint) {
574
period = SDL_atoi(hint);
575
} else {
576
period = 1;
577
}
578
if (period || oldValue != hint) {
579
SDL_SetSystemTimerResolutionMS(period);
580
}
581
}
582
583
void SDL_InitTicks(void)
584
{
585
Uint64 tick_freq;
586
Uint32 gcd;
587
588
if (tick_start) {
589
return;
590
}
591
592
/* If we didn't set a precision, set it high. This affects lots of things
593
on Windows besides the SDL timers, like audio callbacks, etc. */
594
SDL_AddHintCallback(SDL_HINT_TIMER_RESOLUTION,
595
SDL_TimerResolutionChanged, NULL);
596
597
tick_freq = SDL_GetPerformanceFrequency();
598
SDL_assert(tick_freq > 0 && tick_freq <= (Uint64)SDL_MAX_UINT32);
599
600
gcd = SDL_CalculateGCD(SDL_NS_PER_SECOND, (Uint32)tick_freq);
601
tick_numerator_ns = (SDL_NS_PER_SECOND / gcd);
602
tick_denominator_ns = (Uint32)(tick_freq / gcd);
603
604
gcd = SDL_CalculateGCD(SDL_MS_PER_SECOND, (Uint32)tick_freq);
605
tick_numerator_ms = (SDL_MS_PER_SECOND / gcd);
606
tick_denominator_ms = (Uint32)(tick_freq / gcd);
607
608
tick_start = SDL_GetPerformanceCounter();
609
if (!tick_start) {
610
--tick_start;
611
}
612
}
613
614
void SDL_QuitTicks(void)
615
{
616
SDL_RemoveHintCallback(SDL_HINT_TIMER_RESOLUTION,
617
SDL_TimerResolutionChanged, NULL);
618
619
SDL_SetSystemTimerResolutionMS(0); // always release our timer resolution request.
620
621
tick_start = 0;
622
}
623
624
Uint64 SDL_GetTicksNS(void)
625
{
626
Uint64 starting_value, value;
627
628
if (!tick_start) {
629
SDL_InitTicks();
630
}
631
632
starting_value = (SDL_GetPerformanceCounter() - tick_start);
633
value = (starting_value * tick_numerator_ns);
634
SDL_assert(value >= starting_value);
635
value /= tick_denominator_ns;
636
return value;
637
}
638
639
Uint64 SDL_GetTicks(void)
640
{
641
Uint64 starting_value, value;
642
643
if (!tick_start) {
644
SDL_InitTicks();
645
}
646
647
starting_value = (SDL_GetPerformanceCounter() - tick_start);
648
value = (starting_value * tick_numerator_ms);
649
SDL_assert(value >= starting_value);
650
value /= tick_denominator_ms;
651
return value;
652
}
653
654
void SDL_Delay(Uint32 ms)
655
{
656
SDL_SYS_DelayNS(SDL_MS_TO_NS(ms));
657
}
658
659
void SDL_DelayNS(Uint64 ns)
660
{
661
SDL_SYS_DelayNS(ns);
662
}
663
664
void SDL_DelayPrecise(Uint64 ns)
665
{
666
Uint64 current_value = SDL_GetTicksNS();
667
const Uint64 target_value = current_value + ns;
668
669
// Sleep for a short number of cycles when real sleeps are desired.
670
// We'll use 1 ms, it's the minimum guaranteed to produce real sleeps across
671
// all platforms.
672
const Uint64 SHORT_SLEEP_NS = 1 * SDL_NS_PER_MS;
673
674
// Try to sleep short of target_value. If for some crazy reason
675
// a particular platform sleeps for less than 1 ms when 1 ms was requested,
676
// that's fine, the code below can cope with that, but in practice no
677
// platforms behave that way.
678
Uint64 max_sleep_ns = SHORT_SLEEP_NS;
679
while (current_value + max_sleep_ns < target_value) {
680
// Sleep for a short time
681
SDL_SYS_DelayNS(SHORT_SLEEP_NS);
682
683
const Uint64 now = SDL_GetTicksNS();
684
const Uint64 next_sleep_ns = (now - current_value);
685
if (next_sleep_ns > max_sleep_ns) {
686
max_sleep_ns = next_sleep_ns;
687
}
688
current_value = now;
689
}
690
691
// Do a shorter sleep of the remaining time here, less the max overshoot in
692
// the first loop. Due to maintaining max_sleep_ns as
693
// greater-than-or-equal-to-1 ms, we can always subtract off 1 ms to get
694
// the duration overshot beyond a 1 ms sleep request; if the system never
695
// overshot, great, it's zero duration. By choosing the max overshoot
696
// amount, we're likely to not overshoot here. If the sleep here ends up
697
// functioning like SDL_DelayNS(0) internally, that's fine, we just don't
698
// get to do a more-precise-than-1 ms-resolution sleep to undershoot by a
699
// small amount on the current system, but SDL_DelayNS(0) does at least
700
// introduce a small, yielding delay on many platforms, better than an
701
// unyielding busyloop.
702
//
703
// Note that we'll always do at least one sleep in this function, so the
704
// minimum resolution will be that of SDL_SYS_DelayNS()
705
if (current_value < target_value && (target_value - current_value) > (max_sleep_ns - SHORT_SLEEP_NS)) {
706
const Uint64 delay_ns = (target_value - current_value) - (max_sleep_ns - SHORT_SLEEP_NS);
707
SDL_SYS_DelayNS(delay_ns);
708
current_value = SDL_GetTicksNS();
709
}
710
711
// We've likely undershot target_value at this point by a pretty small
712
// amount, but maybe not. The footgun case if not handled here is where
713
// we've undershot by a large amount, like several ms, but still smaller
714
// than the amount max_sleep_ns overshot by; in such a situation, the above
715
// shorter-sleep block didn't do any delay, the if-block wasn't entered.
716
// Also, maybe the shorter-sleep undershot by several ms, so we still don't
717
// want to spin a lot then. In such a case, we accept the possibility of
718
// overshooting to not spin much, or if overshot here, not at all, keeping
719
// CPU/power usage down in any case. Due to scheduler sloppiness, it's
720
// entirely possible to end up undershooting/overshooting here by much less
721
// than 1 ms even if the current system's sleep function is only 1
722
// ms-resolution, as SDL_GetTicksNS() generally is better resolution than 1
723
// ms on the systems SDL supports.
724
while (current_value + SHORT_SLEEP_NS < target_value) {
725
SDL_SYS_DelayNS(SHORT_SLEEP_NS);
726
current_value = SDL_GetTicksNS();
727
}
728
729
// Spin for any remaining time
730
while (current_value < target_value) {
731
SDL_CPUPauseInstruction();
732
current_value = SDL_GetTicksNS();
733
}
734
}
735
736