Atlas - SDL_timer.c

Home / ext / SDL / src / timer Lines: 1 | Size: 21052 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
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 30typedef 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 42typedef 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 50typedef 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 72static 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 80static 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 100static 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 209bool 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 238error: 239 SDL_SetInitialized(&data->init, true); 240 SDL_QuitTimers(); 241 return false; 242} 243 244void 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 293static bool SDL_CheckInitTimers(void) 294{ 295 return SDL_InitTimers(); 296} 297 298static 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 CHECK_PARAM(!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 361SDL_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 366SDL_TimerID SDL_AddTimerNS(Uint64 interval, SDL_NSTimerCallback callback, void *userdata) 367{ 368 return SDL_CreateTimer(interval, NULL, callback, userdata); 369} 370 371bool SDL_RemoveTimer(SDL_TimerID id) 372{ 373 SDL_TimerData *data = &SDL_timer_data; 374 SDL_TimerMap *prev, *entry; 375 bool canceled = false; 376 377 CHECK_PARAM(!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 415typedef 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 426typedef struct 427{ 428 SDL_TimerMap *timermap; 429} SDL_TimerData; 430 431static SDL_TimerData SDL_timer_data; 432 433static 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 448bool SDL_InitTimers(void) 449{ 450 return true; 451} 452 453void 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 465static 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 CHECK_PARAM(!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 495SDL_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 500SDL_TimerID SDL_AddTimerNS(Uint64 interval, SDL_NSTimerCallback callback, void *userdata) 501{ 502 return SDL_CreateTimer(interval, NULL, callback, userdata); 503} 504 505bool SDL_RemoveTimer(SDL_TimerID id) 506{ 507 SDL_TimerData *data = &SDL_timer_data; 508 SDL_TimerMap *prev, *entry; 509 510 CHECK_PARAM(!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 538static Uint64 tick_start; 539static Uint32 tick_numerator_ns; 540static Uint32 tick_denominator_ns; 541static Uint32 tick_numerator_ms; 542static 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 549static 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 568static 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 583void 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 614void 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 624Uint64 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 639Uint64 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 654void SDL_Delay(Uint32 ms) 655{ 656 SDL_SYS_DelayNS(SDL_MS_TO_NS(ms)); 657} 658 659void SDL_DelayNS(Uint64 ns) 660{ 661 SDL_SYS_DelayNS(ns); 662} 663 664void 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
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.