| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #include "SDL_internal.h"
- // System independent thread management routines for SDL
- #include "SDL_thread_c.h"
- #include "SDL_systhread.h"
- #include "../SDL_error_c.h"
- // The storage is local to the thread, but the IDs are global for the process
- static SDL_AtomicInt SDL_tls_allocated;
- static SDL_AtomicInt SDL_tls_id;
- void SDL_InitTLSData(void)
- {
- SDL_SYS_InitTLSData();
- }
- void *SDL_GetTLS(SDL_TLSID *id)
- {
- SDL_TLSData *storage;
- int storage_index;
- if (id == NULL) {
- SDL_InvalidParamError("id");
- return NULL;
- }
- storage_index = SDL_GetAtomicInt(id) - 1;
- storage = SDL_SYS_GetTLSData();
- if (!storage || storage_index < 0 || storage_index >= storage->limit) {
- return NULL;
- }
- return storage->array[storage_index].data;
- }
- bool SDL_SetTLS(SDL_TLSID *id, const void *value, SDL_TLSDestructorCallback destructor)
- {
- SDL_TLSData *storage;
- int storage_index;
- if (id == NULL) {
- return SDL_InvalidParamError("id");
- }
- /* Make sure TLS is initialized.
- * There's a race condition here if you are calling this from non-SDL threads
- * and haven't called SDL_Init() on your main thread, but such is life.
- */
- SDL_InitTLSData();
- // Get the storage index associated with the ID in a thread-safe way
- storage_index = SDL_GetAtomicInt(id) - 1;
- if (storage_index < 0) {
- int new_id = (SDL_AtomicIncRef(&SDL_tls_id) + 1);
- SDL_CompareAndSwapAtomicInt(id, 0, new_id);
- /* If there was a race condition we'll have wasted an ID, but every thread
- * will have the same storage index for this id.
- */
- storage_index = SDL_GetAtomicInt(id) - 1;
- }
- // Get the storage for the current thread
- storage = SDL_SYS_GetTLSData();
- if (!storage || storage_index >= storage->limit) {
- unsigned int i, oldlimit, newlimit;
- SDL_TLSData *new_storage;
- oldlimit = storage ? storage->limit : 0;
- newlimit = (storage_index + TLS_ALLOC_CHUNKSIZE);
- new_storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage) + (newlimit - 1) * sizeof(storage->array[0]));
- if (!new_storage) {
- return false;
- }
- storage = new_storage;
- storage->limit = newlimit;
- for (i = oldlimit; i < newlimit; ++i) {
- storage->array[i].data = NULL;
- storage->array[i].destructor = NULL;
- }
- if (!SDL_SYS_SetTLSData(storage)) {
- SDL_free(storage);
- return false;
- }
- SDL_AtomicIncRef(&SDL_tls_allocated);
- }
- storage->array[storage_index].data = SDL_const_cast(void *, value);
- storage->array[storage_index].destructor = destructor;
- return true;
- }
- void SDL_CleanupTLS(void)
- {
- SDL_TLSData *storage;
- // Cleanup the storage for the current thread
- storage = SDL_SYS_GetTLSData();
- if (storage) {
- int i;
- for (i = 0; i < storage->limit; ++i) {
- if (storage->array[i].destructor) {
- storage->array[i].destructor(storage->array[i].data);
- }
- }
- SDL_SYS_SetTLSData(NULL);
- SDL_free(storage);
- (void)SDL_AtomicDecRef(&SDL_tls_allocated);
- }
- }
- void SDL_QuitTLSData(void)
- {
- SDL_CleanupTLS();
- if (SDL_GetAtomicInt(&SDL_tls_allocated) == 0) {
- SDL_SYS_QuitTLSData();
- } else {
- // Some thread hasn't called SDL_CleanupTLS()
- }
- }
- /* This is a generic implementation of thread-local storage which doesn't
- require additional OS support.
- It is not especially efficient and doesn't clean up thread-local storage
- as threads exit. If there is a real OS that doesn't support thread-local
- storage this implementation should be improved to be production quality.
- */
- typedef struct SDL_TLSEntry
- {
- SDL_ThreadID thread;
- SDL_TLSData *storage;
- struct SDL_TLSEntry *next;
- } SDL_TLSEntry;
- static SDL_Mutex *SDL_generic_TLS_mutex;
- static SDL_TLSEntry *SDL_generic_TLS;
- void SDL_Generic_InitTLSData(void)
- {
- if (!SDL_generic_TLS_mutex) {
- SDL_generic_TLS_mutex = SDL_CreateMutex();
- }
- }
- SDL_TLSData *SDL_Generic_GetTLSData(void)
- {
- SDL_ThreadID thread = SDL_GetCurrentThreadID();
- SDL_TLSEntry *entry;
- SDL_TLSData *storage = NULL;
- SDL_LockMutex(SDL_generic_TLS_mutex);
- for (entry = SDL_generic_TLS; entry; entry = entry->next) {
- if (entry->thread == thread) {
- storage = entry->storage;
- break;
- }
- }
- SDL_UnlockMutex(SDL_generic_TLS_mutex);
- return storage;
- }
- bool SDL_Generic_SetTLSData(SDL_TLSData *data)
- {
- SDL_ThreadID thread = SDL_GetCurrentThreadID();
- SDL_TLSEntry *prev, *entry;
- bool result = true;
- SDL_LockMutex(SDL_generic_TLS_mutex);
- prev = NULL;
- for (entry = SDL_generic_TLS; entry; entry = entry->next) {
- if (entry->thread == thread) {
- if (data) {
- entry->storage = data;
- } else {
- if (prev) {
- prev->next = entry->next;
- } else {
- SDL_generic_TLS = entry->next;
- }
- SDL_free(entry);
- }
- break;
- }
- prev = entry;
- }
- if (!entry && data) {
- entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry));
- if (entry) {
- entry->thread = thread;
- entry->storage = data;
- entry->next = SDL_generic_TLS;
- SDL_generic_TLS = entry;
- } else {
- result = false;
- }
- }
- SDL_UnlockMutex(SDL_generic_TLS_mutex);
- return result;
- }
- void SDL_Generic_QuitTLSData(void)
- {
- SDL_TLSEntry *entry;
- // This should have been cleaned up by the time we get here
- SDL_assert(!SDL_generic_TLS);
- if (SDL_generic_TLS) {
- SDL_LockMutex(SDL_generic_TLS_mutex);
- for (entry = SDL_generic_TLS; entry; ) {
- SDL_TLSEntry *next = entry->next;
- SDL_free(entry->storage);
- SDL_free(entry);
- entry = next;
- }
- SDL_generic_TLS = NULL;
- SDL_UnlockMutex(SDL_generic_TLS_mutex);
- }
- if (SDL_generic_TLS_mutex) {
- SDL_DestroyMutex(SDL_generic_TLS_mutex);
- SDL_generic_TLS_mutex = NULL;
- }
- }
- // Non-thread-safe global error variable
- static SDL_error *SDL_GetStaticErrBuf(void)
- {
- static SDL_error SDL_global_error;
- static char SDL_global_error_str[128];
- SDL_global_error.str = SDL_global_error_str;
- SDL_global_error.len = sizeof(SDL_global_error_str);
- return &SDL_global_error;
- }
- #ifndef SDL_THREADS_DISABLED
- static void SDLCALL SDL_FreeErrBuf(void *data)
- {
- SDL_error *errbuf = (SDL_error *)data;
- if (errbuf->str) {
- errbuf->free_func(errbuf->str);
- }
- errbuf->free_func(errbuf);
- }
- #endif
- // Routine to get the thread-specific error variable
- SDL_error *SDL_GetErrBuf(bool create)
- {
- #ifdef SDL_THREADS_DISABLED
- return SDL_GetStaticErrBuf();
- #else
- static SDL_TLSID tls_errbuf;
- SDL_error *errbuf;
- errbuf = (SDL_error *)SDL_GetTLS(&tls_errbuf);
- if (!errbuf) {
- if (!create) {
- return NULL;
- }
- /* Get the original memory functions for this allocation because the lifetime
- * of the error buffer may span calls to SDL_SetMemoryFunctions() by the app
- */
- SDL_realloc_func realloc_func;
- SDL_free_func free_func;
- SDL_GetOriginalMemoryFunctions(NULL, NULL, &realloc_func, &free_func);
- errbuf = (SDL_error *)realloc_func(NULL, sizeof(*errbuf));
- if (!errbuf) {
- return SDL_GetStaticErrBuf();
- }
- SDL_zerop(errbuf);
- errbuf->realloc_func = realloc_func;
- errbuf->free_func = free_func;
- SDL_SetTLS(&tls_errbuf, errbuf, SDL_FreeErrBuf);
- }
- return errbuf;
- #endif // SDL_THREADS_DISABLED
- }
- void SDL_RunThread(SDL_Thread *thread)
- {
- void *userdata = thread->userdata;
- int(SDLCALL * userfunc)(void *) = thread->userfunc;
- int *statusloc = &thread->status;
- // Perform any system-dependent setup - this function may not fail
- SDL_SYS_SetupThread(thread->name);
- // Get the thread id
- thread->threadid = SDL_GetCurrentThreadID();
- // Run the function
- *statusloc = userfunc(userdata);
- // Clean up thread-local storage
- SDL_CleanupTLS();
- // Mark us as ready to be joined (or detached)
- if (!SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_ZOMBIE)) {
- // Clean up if something already detached us.
- if (SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_STATE_DETACHED, SDL_THREAD_STATE_CLEANED)) {
- SDL_free(thread->name); // Can't free later, we've already cleaned up TLS
- SDL_free(thread);
- }
- }
- }
- SDL_Thread *SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props,
- SDL_FunctionPointer pfnBeginThread,
- SDL_FunctionPointer pfnEndThread)
- {
- // rather than check this in every backend, just make sure it's correct upfront. Only allow non-NULL if Windows, or Microsoft GDK.
- #if !defined(SDL_PLATFORM_WINDOWS)
- if (pfnBeginThread || pfnEndThread) {
- SDL_SetError("_beginthreadex/_endthreadex not supported on this platform");
- return NULL;
- }
- #endif
- SDL_ThreadFunction fn = (SDL_ThreadFunction) SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, NULL);
- const char *name = SDL_GetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, NULL);
- const size_t stacksize = (size_t) SDL_GetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, 0);
- void *userdata = SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, NULL);
- if (!fn) {
- SDL_SetError("Thread entry function is NULL");
- return NULL;
- }
- SDL_InitMainThread();
- SDL_Thread *thread = (SDL_Thread *)SDL_calloc(1, sizeof(*thread));
- if (!thread) {
- return NULL;
- }
- thread->status = -1;
- SDL_SetAtomicInt(&thread->state, SDL_THREAD_STATE_ALIVE);
- // Set up the arguments for the thread
- if (name) {
- thread->name = SDL_strdup(name);
- if (!thread->name) {
- SDL_free(thread);
- return NULL;
- }
- }
- thread->userfunc = fn;
- thread->userdata = userdata;
- thread->stacksize = stacksize;
- // Create the thread and go!
- if (!SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread)) {
- // Oops, failed. Gotta free everything
- SDL_free(thread->name);
- SDL_free(thread);
- thread = NULL;
- }
- // Everything is running now
- return thread;
- }
- SDL_Thread *SDL_CreateThreadRuntime(SDL_ThreadFunction fn,
- const char *name, void *userdata,
- SDL_FunctionPointer pfnBeginThread,
- SDL_FunctionPointer pfnEndThread)
- {
- const SDL_PropertiesID props = SDL_CreateProperties();
- SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn);
- SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name);
- SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata);
- SDL_Thread *thread = SDL_CreateThreadWithPropertiesRuntime(props, pfnBeginThread, pfnEndThread);
- SDL_DestroyProperties(props);
- return thread;
- }
- // internal helper function, not in the public API.
- SDL_Thread *SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, size_t stacksize, void *userdata)
- {
- const SDL_PropertiesID props = SDL_CreateProperties();
- SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn);
- SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name);
- SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata);
- SDL_SetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, (Sint64) stacksize);
- SDL_Thread *thread = SDL_CreateThreadWithProperties(props);
- SDL_DestroyProperties(props);
- return thread;
- }
- SDL_ThreadID SDL_GetThreadID(SDL_Thread *thread)
- {
- SDL_ThreadID id;
- if (thread) {
- id = thread->threadid;
- } else {
- id = SDL_GetCurrentThreadID();
- }
- return id;
- }
- const char *SDL_GetThreadName(SDL_Thread *thread)
- {
- if (thread) {
- return SDL_GetPersistentString(thread->name);
- } else {
- return NULL;
- }
- }
- bool SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority)
- {
- return SDL_SYS_SetThreadPriority(priority);
- }
- void SDL_WaitThread(SDL_Thread *thread, int *status)
- {
- if (thread) {
- SDL_SYS_WaitThread(thread);
- if (status) {
- *status = thread->status;
- }
- SDL_free(thread->name);
- SDL_free(thread);
- }
- }
- void SDL_DetachThread(SDL_Thread *thread)
- {
- if (!thread) {
- return;
- }
- // Grab dibs if the state is alive+joinable.
- if (SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_DETACHED)) {
- SDL_SYS_DetachThread(thread);
- } else {
- // all other states are pretty final, see where we landed.
- const int thread_state = SDL_GetAtomicInt(&thread->state);
- if ((thread_state == SDL_THREAD_STATE_DETACHED) || (thread_state == SDL_THREAD_STATE_CLEANED)) {
- return; // already detached (you shouldn't call this twice!)
- } else if (thread_state == SDL_THREAD_STATE_ZOMBIE) {
- SDL_WaitThread(thread, NULL); // already done, clean it up.
- } else {
- SDL_assert(0 && "Unexpected thread state");
- }
- }
- }
- void SDL_WaitSemaphore(SDL_Semaphore *sem)
- {
- SDL_WaitSemaphoreTimeoutNS(sem, -1);
- }
- bool SDL_TryWaitSemaphore(SDL_Semaphore *sem)
- {
- return SDL_WaitSemaphoreTimeoutNS(sem, 0);
- }
- bool SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Sint32 timeoutMS)
- {
- Sint64 timeoutNS;
- if (timeoutMS >= 0) {
- timeoutNS = SDL_MS_TO_NS(timeoutMS);
- } else {
- timeoutNS = -1;
- }
- return SDL_WaitSemaphoreTimeoutNS(sem, timeoutNS);
- }
- void SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mutex)
- {
- SDL_WaitConditionTimeoutNS(cond, mutex, -1);
- }
- bool SDL_WaitConditionTimeout(SDL_Condition *cond, SDL_Mutex *mutex, Sint32 timeoutMS)
- {
- Sint64 timeoutNS;
- if (timeoutMS >= 0) {
- timeoutNS = SDL_MS_TO_NS(timeoutMS);
- } else {
- timeoutNS = -1;
- }
- return SDL_WaitConditionTimeoutNS(cond, mutex, timeoutNS);
- }
- bool SDL_ShouldInit(SDL_InitState *state)
- {
- while (SDL_GetAtomicInt(&state->status) != SDL_INIT_STATUS_INITIALIZED) {
- if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_UNINITIALIZED, SDL_INIT_STATUS_INITIALIZING)) {
- state->thread = SDL_GetCurrentThreadID();
- return true;
- }
- // Wait for the other thread to complete transition
- SDL_Delay(1);
- }
- return false;
- }
- bool SDL_ShouldQuit(SDL_InitState *state)
- {
- while (SDL_GetAtomicInt(&state->status) != SDL_INIT_STATUS_UNINITIALIZED) {
- if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED, SDL_INIT_STATUS_UNINITIALIZING)) {
- state->thread = SDL_GetCurrentThreadID();
- return true;
- }
- // Wait for the other thread to complete transition
- SDL_Delay(1);
- }
- return false;
- }
- void SDL_SetInitialized(SDL_InitState *state, bool initialized)
- {
- SDL_assert(state->thread == SDL_GetCurrentThreadID());
- if (initialized) {
- SDL_SetAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED);
- } else {
- SDL_SetAtomicInt(&state->status, SDL_INIT_STATUS_UNINITIALIZED);
- }
- }
|