Atlas - SDL_thread.c
Home / ext / SDL / src / thread Lines: 1 | Size: 17599 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// 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 - 1) * 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_str[128]; 266 SDL_global_error.str = SDL_global_error_str; 267 SDL_global_error.len = sizeof(SDL_global_error_str); 268 return &SDL_global_error; 269} 270 271#ifndef SDL_THREADS_DISABLED 272static void SDLCALL SDL_FreeErrBuf(void *data) 273{ 274 SDL_error *errbuf = (SDL_error *)data; 275 276 if (errbuf->str) { 277 errbuf->free_func(errbuf->str); 278 } 279 errbuf->free_func(errbuf); 280} 281#endif 282 283// Routine to get the thread-specific error variable 284SDL_error *SDL_GetErrBuf(bool create) 285{ 286#ifdef SDL_THREADS_DISABLED 287 return SDL_GetStaticErrBuf(); 288#else 289 static SDL_TLSID tls_errbuf; 290 SDL_error *errbuf; 291 292 errbuf = (SDL_error *)SDL_GetTLS(&tls_errbuf); 293 if (!errbuf) { 294 if (!create) { 295 return NULL; 296 } 297 298 /* Get the original memory functions for this allocation because the lifetime 299 * of the error buffer may span calls to SDL_SetMemoryFunctions() by the app 300 */ 301 SDL_realloc_func realloc_func; 302 SDL_free_func free_func; 303 SDL_GetOriginalMemoryFunctions(NULL, NULL, &realloc_func, &free_func); 304 305 errbuf = (SDL_error *)realloc_func(NULL, sizeof(*errbuf)); 306 if (!errbuf) { 307 return SDL_GetStaticErrBuf(); 308 } 309 SDL_zerop(errbuf); 310 errbuf->realloc_func = realloc_func; 311 errbuf->free_func = free_func; 312 SDL_SetTLS(&tls_errbuf, errbuf, SDL_FreeErrBuf); 313 } 314 return errbuf; 315#endif // SDL_THREADS_DISABLED 316} 317 318static bool ThreadValid(SDL_Thread *thread) 319{ 320 return SDL_ObjectValid(thread, SDL_OBJECT_TYPE_THREAD); 321} 322 323void SDL_RunThread(SDL_Thread *thread) 324{ 325 void *userdata = thread->userdata; 326 int(SDLCALL *userfunc)(void *) = thread->userfunc; 327 328 int *statusloc = &thread->status; 329 330 // Perform any system-dependent setup - this function may not fail 331 SDL_SYS_SetupThread(thread->name); 332 333 // Get the thread id 334 thread->threadid = SDL_GetCurrentThreadID(); 335 336 // Run the function 337 *statusloc = userfunc(userdata); 338 339 // Clean up thread-local storage 340 SDL_CleanupTLS(); 341 342 // Mark us as ready to be joined (or detached) 343 if (!SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_ALIVE, SDL_THREAD_COMPLETE)) { 344 // Clean up if something already detached us. 345 if (SDL_GetAtomicInt(&thread->state) == SDL_THREAD_DETACHED) { 346 SDL_free(thread->name); // Can't free later, we've already cleaned up TLS 347 SDL_free(thread); 348 } 349 } 350} 351 352SDL_Thread *SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props, 353 SDL_FunctionPointer pfnBeginThread, 354 SDL_FunctionPointer pfnEndThread) 355{ 356 // rather than check this in every backend, just make sure it's correct upfront. Only allow non-NULL if Windows, or Microsoft GDK. 357 #if !defined(SDL_PLATFORM_WINDOWS) 358 if (pfnBeginThread || pfnEndThread) { 359 SDL_SetError("_beginthreadex/_endthreadex not supported on this platform"); 360 return NULL; 361 } 362 #endif 363 364 SDL_ThreadFunction fn = (SDL_ThreadFunction) SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, NULL); 365 const char *name = SDL_GetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, NULL); 366 const size_t stacksize = (size_t) SDL_GetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, 0); 367 void *userdata = SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, NULL); 368 369 if (!fn) { 370 SDL_SetError("Thread entry function is NULL"); 371 return NULL; 372 } 373 374 SDL_InitMainThread(); 375 376 SDL_Thread *thread = (SDL_Thread *)SDL_calloc(1, sizeof(*thread)); 377 if (!thread) { 378 return NULL; 379 } 380 thread->status = -1; 381 SDL_SetAtomicInt(&thread->state, SDL_THREAD_ALIVE); 382 383 // Set up the arguments for the thread 384 if (name) { 385 thread->name = SDL_strdup(name); 386 if (!thread->name) { 387 SDL_free(thread); 388 return NULL; 389 } 390 } 391 392 thread->userfunc = fn; 393 thread->userdata = userdata; 394 thread->stacksize = stacksize; 395 396 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, true); 397 398 // Create the thread and go! 399 if (!SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread)) { 400 // Oops, failed. Gotta free everything 401 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); 402 SDL_free(thread->name); 403 SDL_free(thread); 404 thread = NULL; 405 } 406 407 // Everything is running now 408 return thread; 409} 410 411SDL_Thread *SDL_CreateThreadRuntime(SDL_ThreadFunction fn, 412 const char *name, void *userdata, 413 SDL_FunctionPointer pfnBeginThread, 414 SDL_FunctionPointer pfnEndThread) 415{ 416 const SDL_PropertiesID props = SDL_CreateProperties(); 417 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn); 418 SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name); 419 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata); 420 SDL_Thread *thread = SDL_CreateThreadWithPropertiesRuntime(props, pfnBeginThread, pfnEndThread); 421 SDL_DestroyProperties(props); 422 return thread; 423} 424 425// internal helper function, not in the public API. 426SDL_Thread *SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, size_t stacksize, void *userdata) 427{ 428 const SDL_PropertiesID props = SDL_CreateProperties(); 429 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn); 430 SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name); 431 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata); 432 SDL_SetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, (Sint64) stacksize); 433 SDL_Thread *thread = SDL_CreateThreadWithProperties(props); 434 SDL_DestroyProperties(props); 435 return thread; 436} 437 438SDL_ThreadID SDL_GetThreadID(SDL_Thread *thread) 439{ 440 SDL_ThreadID id = 0; 441 442 if (thread) { 443 if (ThreadValid(thread)) { 444 id = thread->threadid; 445 } 446 } else { 447 id = SDL_GetCurrentThreadID(); 448 } 449 return id; 450} 451 452const char *SDL_GetThreadName(SDL_Thread *thread) 453{ 454 if (ThreadValid(thread)) { 455 return SDL_GetPersistentString(thread->name); 456 } else { 457 return NULL; 458 } 459} 460 461bool SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority) 462{ 463 return SDL_SYS_SetThreadPriority(priority); 464} 465 466void SDL_WaitThread(SDL_Thread *thread, int *status) 467{ 468 if (!ThreadValid(thread)) { 469 if (status) { 470 *status = -1; 471 } 472 return; 473 } 474 475 SDL_SYS_WaitThread(thread); 476 if (status) { 477 *status = thread->status; 478 } 479 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); 480 SDL_free(thread->name); 481 SDL_free(thread); 482} 483 484SDL_ThreadState SDL_GetThreadState(SDL_Thread *thread) 485{ 486 if (!ThreadValid(thread)) { 487 return SDL_THREAD_UNKNOWN; 488 } 489 490 return (SDL_ThreadState)SDL_GetAtomicInt(&thread->state); 491} 492 493void SDL_DetachThread(SDL_Thread *thread) 494{ 495 if (!ThreadValid(thread)) { 496 return; 497 } 498 499 // Grab dibs if the state is alive+joinable. 500 if (SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_ALIVE, SDL_THREAD_DETACHED)) { 501 // The thread may vanish at any time, it's no longer valid 502 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); 503 SDL_SYS_DetachThread(thread); 504 } else { 505 // all other states are pretty final, see where we landed. 506 SDL_ThreadState thread_state = SDL_GetThreadState(thread); 507 if (thread_state == SDL_THREAD_DETACHED) { 508 return; // already detached (you shouldn't call this twice!) 509 } else if (thread_state == SDL_THREAD_COMPLETE) { 510 SDL_WaitThread(thread, NULL); // already done, clean it up. 511 } 512 } 513} 514 515void SDL_WaitSemaphore(SDL_Semaphore *sem) 516{ 517 SDL_WaitSemaphoreTimeoutNS(sem, -1); 518} 519 520bool SDL_TryWaitSemaphore(SDL_Semaphore *sem) 521{ 522 return SDL_WaitSemaphoreTimeoutNS(sem, 0); 523} 524 525bool SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Sint32 timeoutMS) 526{ 527 Sint64 timeoutNS; 528 529 if (timeoutMS >= 0) { 530 timeoutNS = SDL_MS_TO_NS(timeoutMS); 531 } else { 532 timeoutNS = -1; 533 } 534 return SDL_WaitSemaphoreTimeoutNS(sem, timeoutNS); 535} 536 537void SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mutex) 538{ 539 SDL_WaitConditionTimeoutNS(cond, mutex, -1); 540} 541 542bool SDL_WaitConditionTimeout(SDL_Condition *cond, SDL_Mutex *mutex, Sint32 timeoutMS) 543{ 544 Sint64 timeoutNS; 545 546 if (timeoutMS >= 0) { 547 timeoutNS = SDL_MS_TO_NS(timeoutMS); 548 } else { 549 timeoutNS = -1; 550 } 551 return SDL_WaitConditionTimeoutNS(cond, mutex, timeoutNS); 552} 553 554bool SDL_ShouldInit(SDL_InitState *state) 555{ 556 while (SDL_GetAtomicInt(&state->status) != SDL_INIT_STATUS_INITIALIZED) { 557 if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_UNINITIALIZED, SDL_INIT_STATUS_INITIALIZING)) { 558 state->thread = SDL_GetCurrentThreadID(); 559 return true; 560 } 561 562 // Wait for the other thread to complete transition 563 SDL_Delay(1); 564 } 565 return false; 566} 567 568bool SDL_ShouldQuit(SDL_InitState *state) 569{ 570 while (SDL_GetAtomicInt(&state->status) != SDL_INIT_STATUS_UNINITIALIZED) { 571 if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED, SDL_INIT_STATUS_UNINITIALIZING)) { 572 state->thread = SDL_GetCurrentThreadID(); 573 return true; 574 } 575 576 // Wait for the other thread to complete transition 577 SDL_Delay(1); 578 } 579 return false; 580} 581 582void SDL_SetInitialized(SDL_InitState *state, bool initialized) 583{ 584 SDL_assert(state->thread == SDL_GetCurrentThreadID()); 585 586 if (initialized) { 587 SDL_SetAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED); 588 } else { 589 SDL_SetAtomicInt(&state->status, SDL_INIT_STATUS_UNINITIALIZED); 590 } 591} 592 593[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.