Added SDL_IsMainThread() and SDL_RunOnMainThread()

This commit is contained in:
Sam Lantinga
2024-12-05 12:35:23 -08:00
parent bc4185c685
commit 23828b62d4
7 changed files with 312 additions and 0 deletions

View File

@@ -183,6 +183,7 @@ static bool SDL_MainIsReady = false;
#else
static bool SDL_MainIsReady = true;
#endif
static SDL_ThreadID SDL_MainThreadID = 0;
static bool SDL_bInMainQuit = false;
static Uint8 SDL_SubsystemRefCount[32];
@@ -250,6 +251,22 @@ static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem)
void SDL_SetMainReady(void)
{
SDL_MainIsReady = true;
if (SDL_MainThreadID == 0) {
SDL_MainThreadID = SDL_GetCurrentThreadID();
}
}
bool SDL_IsMainThread(void)
{
if (SDL_MainThreadID == 0) {
// Not initialized yet?
return true;
}
if (SDL_MainThreadID == SDL_GetCurrentThreadID()) {
return true;
}
return false;
}
// Initialize all the subsystems that require initialization before threads start
@@ -330,6 +347,11 @@ bool SDL_InitSubSystem(SDL_InitFlags flags)
goto quit_and_error;
}
// We initialize video on the main thread
// On Apple platforms this is a requirement.
// On other platforms, this is the definition.
SDL_MainThreadID = SDL_GetCurrentThreadID();
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
if (!SDL_VideoInit(NULL)) {
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);

View File

@@ -1201,6 +1201,8 @@ SDL3_0.0.0 {
SDL_SignalAsyncIOQueue;
SDL_LoadFileAsync;
SDL_ShowFileDialogWithProperties;
SDL_IsMainThread;
SDL_RunOnMainThread;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@@ -1226,3 +1226,5 @@
#define SDL_SignalAsyncIOQueue SDL_SignalAsyncIOQueue_REAL
#define SDL_LoadFileAsync SDL_LoadFileAsync_REAL
#define SDL_ShowFileDialogWithProperties SDL_ShowFileDialogWithProperties_REAL
#define SDL_IsMainThread SDL_IsMainThread_REAL
#define SDL_RunOnMainThread SDL_RunOnMainThread_REAL

View File

@@ -1232,3 +1232,5 @@ SDL_DYNAPI_PROC(bool,SDL_WaitAsyncIOResult,(SDL_AsyncIOQueue *a, SDL_AsyncIOOutc
SDL_DYNAPI_PROC(void,SDL_SignalAsyncIOQueue,(SDL_AsyncIOQueue *a),(a),)
SDL_DYNAPI_PROC(bool,SDL_LoadFileAsync,(const char *a, SDL_AsyncIOQueue *b, void *c),(a,b,c),return)
SDL_DYNAPI_PROC(void,SDL_ShowFileDialogWithProperties,(SDL_FileDialogType a, SDL_DialogFileCallback b, void *c, SDL_PropertiesID d),(a,b,c,d),)
SDL_DYNAPI_PROC(bool,SDL_IsMainThread,(void),(),return)
SDL_DYNAPI_PROC(bool,SDL_RunOnMainThread,(SDL_MainThreadCallback a,void *b,bool c),(a,b,c),return)

View File

@@ -1175,6 +1175,177 @@ void SDL_FlushEvents(Uint32 minType, Uint32 maxType)
SDL_UnlockMutex(SDL_EventQ.lock);
}
typedef enum
{
SDL_MAIN_CALLBACK_WAITING,
SDL_MAIN_CALLBACK_COMPLETE,
SDL_MAIN_CALLBACK_CANCELED,
} SDL_MainThreadCallbackState;
typedef struct SDL_MainThreadCallbackEntry
{
SDL_MainThreadCallback callback;
void *userdata;
SDL_AtomicInt state;
SDL_Semaphore *semaphore;
struct SDL_MainThreadCallbackEntry *next;
} SDL_MainThreadCallbackEntry;
static SDL_Mutex *SDL_main_callbacks_lock;
static SDL_MainThreadCallbackEntry *SDL_main_callbacks_head;
static SDL_MainThreadCallbackEntry *SDL_main_callbacks_tail;
static SDL_MainThreadCallbackEntry *SDL_CreateMainThreadCallback(SDL_MainThreadCallback callback, void *userdata, bool wait_complete)
{
SDL_MainThreadCallbackEntry *entry = (SDL_MainThreadCallbackEntry *)SDL_malloc(sizeof(*entry));
if (!entry) {
return NULL;
}
entry->callback = callback;
entry->userdata = userdata;
SDL_SetAtomicInt(&entry->state, SDL_MAIN_CALLBACK_WAITING);
if (wait_complete) {
entry->semaphore = SDL_CreateSemaphore(0);
if (!entry->semaphore) {
SDL_free(entry);
return NULL;
}
} else {
entry->semaphore = NULL;
}
entry->next = NULL;
return entry;
}
static void SDL_DestroyMainThreadCallback(SDL_MainThreadCallbackEntry *entry)
{
if (entry->semaphore) {
SDL_DestroySemaphore(entry->semaphore);
}
SDL_free(entry);
}
static void SDL_InitMainThreadCallbacks(void)
{
SDL_main_callbacks_lock = SDL_CreateMutex();
SDL_assert(SDL_main_callbacks_head == NULL &&
SDL_main_callbacks_tail == NULL);
}
static void SDL_QuitMainThreadCallbacks(void)
{
SDL_MainThreadCallbackEntry *entry;
SDL_LockMutex(SDL_main_callbacks_lock);
{
entry = SDL_main_callbacks_head;
SDL_main_callbacks_head = NULL;
SDL_main_callbacks_tail = NULL;
}
SDL_UnlockMutex(SDL_main_callbacks_lock);
while (entry) {
SDL_MainThreadCallbackEntry *next = entry->next;
if (entry->semaphore) {
// Let the waiting thread know this is canceled
SDL_SetAtomicInt(&entry->state, SDL_MAIN_CALLBACK_CANCELED);
SDL_SignalSemaphore(entry->semaphore);
} else {
// Nobody's waiting for this, clean it up
SDL_DestroyMainThreadCallback(entry);
}
entry = next;
}
SDL_DestroyMutex(SDL_main_callbacks_lock);
SDL_main_callbacks_lock = NULL;
}
static void SDL_RunMainThreadCallbacks(void)
{
SDL_MainThreadCallbackEntry *entry;
SDL_LockMutex(SDL_main_callbacks_lock);
{
entry = SDL_main_callbacks_head;
SDL_main_callbacks_head = NULL;
SDL_main_callbacks_tail = NULL;
}
SDL_UnlockMutex(SDL_main_callbacks_lock);
while (entry) {
SDL_MainThreadCallbackEntry *next = entry->next;
entry->callback(entry->userdata);
if (entry->semaphore) {
// Let the waiting thread know this is done
SDL_SetAtomicInt(&entry->state, SDL_MAIN_CALLBACK_COMPLETE);
SDL_SignalSemaphore(entry->semaphore);
} else {
// Nobody's waiting for this, clean it up
SDL_DestroyMainThreadCallback(entry);
}
entry = next;
}
}
bool SDL_RunOnMainThread(SDL_MainThreadCallback callback, void *userdata, bool wait_complete)
{
if (SDL_IsMainThread() || !SDL_WasInit(SDL_INIT_EVENTS)) {
// No need to queue the callback
callback(userdata);
return true;
}
SDL_MainThreadCallbackEntry *entry = SDL_CreateMainThreadCallback(callback, userdata, wait_complete);
if (!entry) {
return false;
}
SDL_LockMutex(SDL_main_callbacks_lock);
{
if (SDL_main_callbacks_tail) {
SDL_main_callbacks_tail->next = entry;
SDL_main_callbacks_tail = entry;
} else {
SDL_main_callbacks_head = entry;
SDL_main_callbacks_tail = entry;
}
}
SDL_UnlockMutex(SDL_main_callbacks_lock);
if (!wait_complete) {
// Queued for execution, wait not requested
return true;
}
// Maximum wait of 30 seconds to prevent deadlocking forever
const Sint32 MAX_CALLBACK_WAIT = 30 * 1000;
SDL_WaitSemaphoreTimeout(entry->semaphore, MAX_CALLBACK_WAIT);
switch (SDL_GetAtomicInt(&entry->state)) {
case SDL_MAIN_CALLBACK_COMPLETE:
// Execution complete!
SDL_DestroyMainThreadCallback(entry);
return true;
case SDL_MAIN_CALLBACK_CANCELED:
// The callback was canceled on the main thread
SDL_DestroyMainThreadCallback(entry);
return SDL_SetError("Callback canceled");
default:
// Probably hit a deadlock in the callback
// We can't destroy the entry as the semaphore will be signaled
// if it ever comes back, just leak it here.
return SDL_SetError("Callback timed out");
}
}
// Run the system dependent event loops
static void SDL_PumpEventsInternal(bool push_sentinel)
{
@@ -1184,6 +1355,9 @@ static void SDL_PumpEventsInternal(bool push_sentinel)
// Release any keys held down from last frame
SDL_ReleaseAutoReleaseKeys();
// Run any pending main thread callbacks
SDL_RunMainThreadCallbacks();
#ifdef SDL_PLATFORM_ANDROID
// Android event processing is independent of the video subsystem
Android_PumpEvents(0);
@@ -1792,6 +1966,7 @@ bool SDL_InitEvents(void)
#endif
SDL_AddHintCallback(SDL_HINT_EVENT_LOGGING, SDL_EventLoggingChanged, NULL);
SDL_AddHintCallback(SDL_HINT_POLL_SENTINEL, SDL_PollSentinelChanged, NULL);
SDL_InitMainThreadCallbacks();
if (!SDL_StartEventLoop()) {
SDL_RemoveHintCallback(SDL_HINT_EVENT_LOGGING, SDL_EventLoggingChanged, NULL);
return false;
@@ -1806,6 +1981,7 @@ void SDL_QuitEvents(void)
{
SDL_QuitQuit();
SDL_StopEventLoop();
SDL_QuitMainThreadCallbacks();
SDL_RemoveHintCallback(SDL_HINT_POLL_SENTINEL, SDL_PollSentinelChanged, NULL);
SDL_RemoveHintCallback(SDL_HINT_EVENT_LOGGING, SDL_EventLoggingChanged, NULL);
#ifndef SDL_JOYSTICK_DISABLED