From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- contrib/SDL-3.2.8/src/thread/SDL_thread.c | 584 ++++++++++++++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/thread/SDL_thread.c (limited to 'contrib/SDL-3.2.8/src/thread/SDL_thread.c') diff --git a/contrib/SDL-3.2.8/src/thread/SDL_thread.c b/contrib/SDL-3.2.8/src/thread/SDL_thread.c new file mode 100644 index 0000000..99cb9dd --- /dev/null +++ b/contrib/SDL-3.2.8/src/thread/SDL_thread.c @@ -0,0 +1,584 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + 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 +} + +static bool ThreadValid(SDL_Thread *thread) +{ + return SDL_ObjectValid(thread, SDL_OBJECT_TYPE_THREAD); +} + +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_ALIVE, SDL_THREAD_COMPLETE)) { + // Clean up if something already detached us. + if (SDL_GetThreadState(thread) == SDL_THREAD_DETACHED) { + 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_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; + + SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, true); + + // Create the thread and go! + if (!SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread)) { + // Oops, failed. Gotta free everything + SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); + 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 = 0; + + if (thread) { + if (ThreadValid(thread)) { + id = thread->threadid; + } + } else { + id = SDL_GetCurrentThreadID(); + } + return id; +} + +const char *SDL_GetThreadName(SDL_Thread *thread) +{ + if (ThreadValid(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 (!ThreadValid(thread)) { + if (status) { + *status = -1; + } + return; + } + + SDL_SYS_WaitThread(thread); + if (status) { + *status = thread->status; + } + SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); + SDL_free(thread->name); + SDL_free(thread); +} + +SDL_ThreadState SDL_GetThreadState(SDL_Thread *thread) +{ + if (!ThreadValid(thread)) { + return SDL_THREAD_UNKNOWN; + } + + return (SDL_ThreadState)SDL_GetAtomicInt(&thread->state); +} + +void SDL_DetachThread(SDL_Thread *thread) +{ + if (!ThreadValid(thread)) { + return; + } + + // The thread may vanish at any time, it's no longer valid + SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false); + + // Grab dibs if the state is alive+joinable. + if (SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_ALIVE, SDL_THREAD_DETACHED)) { + SDL_SYS_DetachThread(thread); + } else { + // all other states are pretty final, see where we landed. + SDL_ThreadState thread_state = SDL_GetThreadState(thread); + if (thread_state == SDL_THREAD_DETACHED) { + return; // already detached (you shouldn't call this twice!) + } else if (thread_state == SDL_THREAD_COMPLETE) { + SDL_WaitThread(thread, NULL); // already done, clean it up. + } + } +} + +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); + } +} + -- cgit v1.2.3