Atlas - SDL_thread.c

Home / ext / SDL / src / thread Lines: 1 | Size: 18291 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 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// System independent thread management routines for SDL 24 25#include "SDL_thread_c.h" 26#include "SDL_systhread.h" 27#include "../SDL_error_c.h" 28 29// The storage is local to the thread, but the IDs are global for the process 30 31static SDL_AtomicInt SDL_tls_allocated; 32static SDL_AtomicInt SDL_tls_id; 33 34void SDL_InitTLSData(void) 35{ 36 SDL_SYS_InitTLSData(); 37} 38 39void *SDL_GetTLS(SDL_TLSID *id) 40{ 41 SDL_TLSData *storage; 42 int storage_index; 43 44 CHECK_PARAM(id == NULL) { 45 SDL_InvalidParamError("id"); 46 return NULL; 47 } 48 49 storage_index = SDL_GetAtomicInt(id) - 1; 50 storage = SDL_SYS_GetTLSData(); 51 if (!storage || storage_index < 0 || storage_index >= storage->limit) { 52 return NULL; 53 } 54 return storage->array[storage_index].data; 55} 56 57bool SDL_SetTLS(SDL_TLSID *id, const void *value, SDL_TLSDestructorCallback destructor) 58{ 59 SDL_TLSData *storage; 60 int storage_index; 61 62 CHECK_PARAM(id == NULL) { 63 return SDL_InvalidParamError("id"); 64 } 65 66 /* Make sure TLS is initialized. 67 * There's a race condition here if you are calling this from non-SDL threads 68 * and haven't called SDL_Init() on your main thread, but such is life. 69 */ 70 SDL_InitTLSData(); 71 72 // Get the storage index associated with the ID in a thread-safe way 73 storage_index = SDL_GetAtomicInt(id) - 1; 74 if (storage_index < 0) { 75 int new_id = (SDL_AtomicIncRef(&SDL_tls_id) + 1); 76 77 SDL_CompareAndSwapAtomicInt(id, 0, new_id); 78 79 /* If there was a race condition we'll have wasted an ID, but every thread 80 * will have the same storage index for this id. 81 */ 82 storage_index = SDL_GetAtomicInt(id) - 1; 83 } else { 84 // Make sure we don't allocate an ID clobbering this one 85 int tls_id = SDL_GetAtomicInt(&SDL_tls_id); 86 while (storage_index >= tls_id) { 87 if (SDL_CompareAndSwapAtomicInt(&SDL_tls_id, tls_id, storage_index + 1)) { 88 break; 89 } 90 tls_id = SDL_GetAtomicInt(&SDL_tls_id); 91 } 92 } 93 94 // Get the storage for the current thread 95 storage = SDL_SYS_GetTLSData(); 96 if (!storage || storage_index >= storage->limit) { 97 unsigned int i, oldlimit, newlimit; 98 SDL_TLSData *new_storage; 99 100 oldlimit = storage ? storage->limit : 0; 101 newlimit = (storage_index + TLS_ALLOC_CHUNKSIZE); 102 new_storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage) + newlimit * sizeof(storage->array[0])); 103 if (!new_storage) { 104 return false; 105 } 106 storage = new_storage; 107 storage->limit = newlimit; 108 for (i = oldlimit; i < newlimit; ++i) { 109 storage->array[i].data = NULL; 110 storage->array[i].destructor = NULL; 111 } 112 if (!SDL_SYS_SetTLSData(storage)) { 113 SDL_free(storage); 114 return false; 115 } 116 SDL_AtomicIncRef(&SDL_tls_allocated); 117 } 118 119 storage->array[storage_index].data = SDL_const_cast(void *, value); 120 storage->array[storage_index].destructor = destructor; 121 return true; 122} 123 124void SDL_CleanupTLS(void) 125{ 126 SDL_TLSData *storage; 127 128 // Cleanup the storage for the current thread 129 storage = SDL_SYS_GetTLSData(); 130 if (storage) { 131 int i; 132 for (i = 0; i < storage->limit; ++i) { 133 if (storage->array[i].destructor) { 134 storage->array[i].destructor(storage->array[i].data); 135 } 136 } 137 SDL_SYS_SetTLSData(NULL); 138 SDL_free(storage); 139 (void)SDL_AtomicDecRef(&SDL_tls_allocated); 140 } 141} 142 143void SDL_QuitTLSData(void) 144{ 145 SDL_CleanupTLS(); 146 147 if (SDL_GetAtomicInt(&SDL_tls_allocated) == 0) { 148 SDL_SYS_QuitTLSData(); 149 } else { 150 // Some thread hasn't called SDL_CleanupTLS() 151 } 152} 153 154/* This is a generic implementation of thread-local storage which doesn't 155 require additional OS support. 156 157 It is not especially efficient and doesn't clean up thread-local storage 158 as threads exit. If there is a real OS that doesn't support thread-local 159 storage this implementation should be improved to be production quality. 160*/ 161 162typedef struct SDL_TLSEntry 163{ 164 SDL_ThreadID thread; 165 SDL_TLSData *storage; 166 struct SDL_TLSEntry *next; 167} SDL_TLSEntry; 168 169static SDL_Mutex *SDL_generic_TLS_mutex; 170static SDL_TLSEntry *SDL_generic_TLS; 171 172void SDL_Generic_InitTLSData(void) 173{ 174 if (!SDL_generic_TLS_mutex) { 175 SDL_generic_TLS_mutex = SDL_CreateMutex(); 176 } 177} 178 179SDL_TLSData *SDL_Generic_GetTLSData(void) 180{ 181 SDL_ThreadID thread = SDL_GetCurrentThreadID(); 182 SDL_TLSEntry *entry; 183 SDL_TLSData *storage = NULL; 184 185 SDL_LockMutex(SDL_generic_TLS_mutex); 186 for (entry = SDL_generic_TLS; entry; entry = entry->next) { 187 if (entry->thread == thread) { 188 storage = entry->storage; 189 break; 190 } 191 } 192 SDL_UnlockMutex(SDL_generic_TLS_mutex); 193 194 return storage; 195} 196 197bool SDL_Generic_SetTLSData(SDL_TLSData *data) 198{ 199 SDL_ThreadID thread = SDL_GetCurrentThreadID(); 200 SDL_TLSEntry *prev, *entry; 201 bool result = true; 202 203 SDL_LockMutex(SDL_generic_TLS_mutex); 204 prev = NULL; 205 for (entry = SDL_generic_TLS; entry; entry = entry->next) { 206 if (entry->thread == thread) { 207 if (data) { 208 entry->storage = data; 209 } else { 210 if (prev) { 211 prev->next = entry->next; 212 } else { 213 SDL_generic_TLS = entry->next; 214 } 215 SDL_free(entry); 216 } 217 break; 218 } 219 prev = entry; 220 } 221 if (!entry && data) { 222 entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry)); 223 if (entry) { 224 entry->thread = thread; 225 entry->storage = data; 226 entry->next = SDL_generic_TLS; 227 SDL_generic_TLS = entry; 228 } else { 229 result = false; 230 } 231 } 232 SDL_UnlockMutex(SDL_generic_TLS_mutex); 233 234 return result; 235} 236 237void SDL_Generic_QuitTLSData(void) 238{ 239 SDL_TLSEntry *entry; 240 241 // This should have been cleaned up by the time we get here 242 SDL_assert(!SDL_generic_TLS); 243 if (SDL_generic_TLS) { 244 SDL_LockMutex(SDL_generic_TLS_mutex); 245 for (entry = SDL_generic_TLS; entry; ) { 246 SDL_TLSEntry *next = entry->next; 247 SDL_free(entry->storage); 248 SDL_free(entry); 249 entry = next; 250 } 251 SDL_generic_TLS = NULL; 252 SDL_UnlockMutex(SDL_generic_TLS_mutex); 253 } 254 255 if (SDL_generic_TLS_mutex) { 256 SDL_DestroyMutex(SDL_generic_TLS_mutex); 257 SDL_generic_TLS_mutex = NULL; 258 } 259} 260 261// Non-thread-safe global error variable 262static SDL_error *SDL_GetStaticErrBuf(void) 263{ 264 static SDL_error SDL_global_error; 265 static char SDL_global_error_str1[128]; 266 static char SDL_global_error_str2[128]; 267 SDL_global_error.info[0].str = SDL_global_error_str1; 268 SDL_global_error.info[0].len = sizeof(SDL_global_error_str1); 269 SDL_global_error.info[1].str = SDL_global_error_str2; 270 SDL_global_error.info[1].len = sizeof(SDL_global_error_str2); 271 return &SDL_global_error; 272} 273 274#ifndef SDL_THREADS_DISABLED 275static void SDLCALL SDL_FreeErrBuf(void *data) 276{ 277 SDL_error *errbuf = (SDL_error *)data; 278 if (errbuf->info[0].str) { 279 errbuf->free_func(errbuf->info[0].str); 280 } 281 if (errbuf->info[1].str) { 282 errbuf->free_func(errbuf->info[1].str); 283 } 284 errbuf->free_func(errbuf); 285} 286#endif 287 288// Routine to get the thread-specific error variable 289SDL_error *SDL_GetErrBuf(bool create) 290{ 291#ifdef SDL_THREADS_DISABLED 292 return SDL_GetStaticErrBuf(); 293#else 294 static SDL_TLSID tls_errbuf; 295 SDL_error *errbuf; 296 297 errbuf = (SDL_error *)SDL_GetTLS(&tls_errbuf); 298 if (!errbuf) { 299 if (!create) { 300 return NULL; 301 } 302 303 /* Get the original memory functions for this allocation because the lifetime 304 * of the error buffer may span calls to SDL_SetMemoryFunctions() by the app 305 */ 306 SDL_realloc_func realloc_func; 307 SDL_free_func free_func; 308 SDL_GetOriginalMemoryFunctions(NULL, NULL, &realloc_func, &free_func); 309 310 errbuf = (SDL_error *)realloc_func(NULL, sizeof(*errbuf)); 311 if (!errbuf) { 312 return SDL_GetStaticErrBuf(); 313 } 314 SDL_zerop(errbuf); 315 errbuf->realloc_func = realloc_func; 316 errbuf->free_func = free_func; 317 SDL_SetTLS(&tls_errbuf, errbuf, SDL_FreeErrBuf); 318 } 319 return errbuf; 320#endif // SDL_THREADS_DISABLED 321} 322 323static bool ThreadValid(SDL_Thread *thread) 324{ 325 return SDL_ObjectValid(thread, SDL_OBJECT_TYPE_THREAD); 326} 327 328void SDL_RunThread(SDL_Thread *thread) 329{ 330 void *userdata = thread->userdata; 331 int(SDLCALL *userfunc)(void *) = thread->userfunc; 332 333 int *statusloc = &thread->status; 334 335 // Perform any system-dependent setup - this function may not fail 336 SDL_SYS_SetupThread(thread->name); 337 338 // Get the thread id 339 thread->threadid = SDL_GetCurrentThreadID(); 340 341 SDL_SignalSemaphore(thread->ready_sem); // the thread is officially ready to run! 342 343 // Run the function 344 *statusloc = userfunc(userdata); 345 346 // Clean up thread-local storage 347 SDL_CleanupTLS(); 348 349 // Mark us as ready to be joined (or detached) 350 if (!SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_ALIVE, SDL_THREAD_COMPLETE)) { 351 // Clean up if something already detached us. 352 if (SDL_GetAtomicInt(&thread->state) == SDL_THREAD_DETACHED) { 353 SDL_free(thread->name); // Can't free later, we've already cleaned up TLS 354 SDL_free(thread); 355 } 356 } 357} 358 359SDL_Thread *SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props, 360 SDL_FunctionPointer pfnBeginThread, 361 SDL_FunctionPointer pfnEndThread) 362{ 363 // rather than check this in every backend, just make sure it's correct upfront. Only allow non-NULL if Windows, or Microsoft GDK. 364 #if !defined(SDL_PLATFORM_WINDOWS) 365 if (pfnBeginThread || pfnEndThread) { 366 SDL_SetError("_beginthreadex/_endthreadex not supported on this platform"); 367 return NULL; 368 } 369 #endif 370 371 SDL_ThreadFunction fn = (SDL_ThreadFunction) SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, NULL); 372 const char *name = SDL_GetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, NULL); 373 const size_t stacksize = (size_t) SDL_GetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, 0); 374 void *userdata = SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, NULL); 375 376 if (!fn) { 377 SDL_SetError("Thread entry function is NULL"); 378 return NULL; 379 } 380 381 SDL_InitMainThread(); 382 383 SDL_Thread *thread = (SDL_Thread *)SDL_calloc(1, sizeof(*thread)); 384 if (!thread) { 385 return NULL; 386 } 387 thread->status = -1; 388 SDL_SetAtomicInt(&thread->state, SDL_THREAD_ALIVE); 389 390 // Set up the arguments for the thread 391 if (name) { 392 thread->name = SDL_strdup(name); 393 if (!thread->name) { 394 SDL_free(thread); 395 return NULL; 396 } 397 } 398 399 thread->ready_sem = SDL_CreateSemaphore(0); 400 if (!thread->ready_sem) { 401 SDL_free(thread->name); 402 SDL_free(thread); 403 return NULL; 404 } 405 406 thread->userfunc = fn; 407 thread->userdata = userdata; 408 thread->stacksize = stacksize; 409 410 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, true); 411 412 // Create the thread and go! 413 if (!SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread)) { 414 // Oops, failed. Gotta free everything 415 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); 416 SDL_DestroySemaphore(thread->ready_sem); 417 SDL_free(thread->name); 418 SDL_free(thread); 419 return NULL; 420 } 421 422 SDL_WaitSemaphore(thread->ready_sem); 423 SDL_DestroySemaphore(thread->ready_sem); 424 thread->ready_sem = NULL; 425 426 // Everything is running now 427 return thread; 428} 429 430SDL_Thread *SDL_CreateThreadRuntime(SDL_ThreadFunction fn, 431 const char *name, void *userdata, 432 SDL_FunctionPointer pfnBeginThread, 433 SDL_FunctionPointer pfnEndThread) 434{ 435 const SDL_PropertiesID props = SDL_CreateProperties(); 436 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn); 437 SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name); 438 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata); 439 SDL_Thread *thread = SDL_CreateThreadWithPropertiesRuntime(props, pfnBeginThread, pfnEndThread); 440 SDL_DestroyProperties(props); 441 return thread; 442} 443 444// internal helper function, not in the public API. 445SDL_Thread *SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, size_t stacksize, void *userdata) 446{ 447 const SDL_PropertiesID props = SDL_CreateProperties(); 448 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn); 449 SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name); 450 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata); 451 SDL_SetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, (Sint64) stacksize); 452 SDL_Thread *thread = SDL_CreateThreadWithProperties(props); 453 SDL_DestroyProperties(props); 454 return thread; 455} 456 457SDL_ThreadID SDL_GetThreadID(SDL_Thread *thread) 458{ 459 SDL_ThreadID id = 0; 460 461 if (thread) { 462 if (ThreadValid(thread)) { 463 id = thread->threadid; 464 } 465 } else { 466 id = SDL_GetCurrentThreadID(); 467 } 468 return id; 469} 470 471const char *SDL_GetThreadName(SDL_Thread *thread) 472{ 473 if (ThreadValid(thread)) { 474 return SDL_GetPersistentString(thread->name); 475 } else { 476 return NULL; 477 } 478} 479 480bool SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority) 481{ 482 return SDL_SYS_SetThreadPriority(priority); 483} 484 485void SDL_WaitThread(SDL_Thread *thread, int *status) 486{ 487 if (!ThreadValid(thread)) { 488 if (status) { 489 *status = -1; 490 } 491 return; 492 } 493 494 SDL_SYS_WaitThread(thread); 495 if (status) { 496 *status = thread->status; 497 } 498 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); 499 SDL_free(thread->name); 500 SDL_free(thread); 501} 502 503SDL_ThreadState SDL_GetThreadState(SDL_Thread *thread) 504{ 505 if (!ThreadValid(thread)) { 506 return SDL_THREAD_UNKNOWN; 507 } 508 509 return (SDL_ThreadState)SDL_GetAtomicInt(&thread->state); 510} 511 512void SDL_DetachThread(SDL_Thread *thread) 513{ 514 if (!ThreadValid(thread)) { 515 return; 516 } 517 518 // Grab dibs if the state is alive+joinable. 519 if (SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_ALIVE, SDL_THREAD_DETACHED)) { 520 // The thread may vanish at any time, it's no longer valid 521 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); 522 SDL_SYS_DetachThread(thread); 523 } else { 524 // all other states are pretty final, see where we landed. 525 SDL_ThreadState thread_state = SDL_GetThreadState(thread); 526 if (thread_state == SDL_THREAD_DETACHED) { 527 return; // already detached (you shouldn't call this twice!) 528 } else if (thread_state == SDL_THREAD_COMPLETE) { 529 SDL_WaitThread(thread, NULL); // already done, clean it up. 530 } 531 } 532} 533 534void SDL_WaitSemaphore(SDL_Semaphore *sem) 535{ 536 SDL_WaitSemaphoreTimeoutNS(sem, -1); 537} 538 539bool SDL_TryWaitSemaphore(SDL_Semaphore *sem) 540{ 541 return SDL_WaitSemaphoreTimeoutNS(sem, 0); 542} 543 544bool SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Sint32 timeoutMS) 545{ 546 Sint64 timeoutNS; 547 548 if (timeoutMS >= 0) { 549 timeoutNS = SDL_MS_TO_NS(timeoutMS); 550 } else { 551 timeoutNS = -1; 552 } 553 return SDL_WaitSemaphoreTimeoutNS(sem, timeoutNS); 554} 555 556void SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mutex) 557{ 558 SDL_WaitConditionTimeoutNS(cond, mutex, -1); 559} 560 561bool SDL_WaitConditionTimeout(SDL_Condition *cond, SDL_Mutex *mutex, Sint32 timeoutMS) 562{ 563 Sint64 timeoutNS; 564 565 if (timeoutMS >= 0) { 566 timeoutNS = SDL_MS_TO_NS(timeoutMS); 567 } else { 568 timeoutNS = -1; 569 } 570 return SDL_WaitConditionTimeoutNS(cond, mutex, timeoutNS); 571} 572 573bool SDL_ShouldInit(SDL_InitState *state) 574{ 575 while (SDL_GetAtomicInt(&state->status) != SDL_INIT_STATUS_INITIALIZED) { 576 if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_UNINITIALIZED, SDL_INIT_STATUS_INITIALIZING)) { 577 state->thread = SDL_GetCurrentThreadID(); 578 return true; 579 } 580 581 // Wait for the other thread to complete transition 582 SDL_Delay(1); 583 } 584 return false; 585} 586 587bool SDL_ShouldQuit(SDL_InitState *state) 588{ 589 while (SDL_GetAtomicInt(&state->status) != SDL_INIT_STATUS_UNINITIALIZED) { 590 if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED, SDL_INIT_STATUS_UNINITIALIZING)) { 591 state->thread = SDL_GetCurrentThreadID(); 592 return true; 593 } 594 595 // Wait for the other thread to complete transition 596 SDL_Delay(1); 597 } 598 return false; 599} 600 601void SDL_SetInitialized(SDL_InitState *state, bool initialized) 602{ 603 SDL_assert(state->thread == SDL_GetCurrentThreadID()); 604 605 if (initialized) { 606 SDL_SetAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED); 607 } else { 608 SDL_SetAtomicInt(&state->status, SDL_INIT_STATUS_UNINITIALIZED); 609 } 610} 611 612
[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.