diff --git a/include/SDL3/SDL_mutex.h b/include/SDL3/SDL_mutex.h index 66625618cf..d4467430ea 100644 --- a/include/SDL3/SDL_mutex.h +++ b/include/SDL3/SDL_mutex.h @@ -30,6 +30,7 @@ #include #include +#include /******************************************************************************/ /* Enable thread safety attributes only with clang. @@ -757,6 +758,140 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WaitConditionTimeout(SDL_Condition *cond, /* @} *//* Condition variable functions */ +/** + * \name Thread-safe initialization state functions + */ +/* @{ */ + +/** + * The current status of an SDL_InitState structure. + * + * \since This enum is available since SDL 3.0.0. + */ +typedef enum SDL_InitStatus +{ + SDL_INIT_STATUS_UNINITIALIZED, + SDL_INIT_STATUS_INITIALIZING, + SDL_INIT_STATUS_INITIALIZED, + SDL_INIT_STATUS_UNINITIALIZING +} SDL_InitStatus; + +/** + * A structure used for thread-safe initialization and shutdown. + * + * Here is an example of using this: + * + * ```c + * static SDL_AtomicInitState init; + * + * bool InitSystem(void) + * { + * if (!SDL_ShouldInit(&init)) { + * // The system is initialized + * return true; + * } + * + * // At this point, you should not leave this function without calling SDL_SetInitialized() + * + * bool initialized = DoInitTasks(); + * SDL_SetInitialized(&init, initialized); + * return initialized; + * } + * + * bool UseSubsystem(void) + * { + * if (SDL_ShouldInit(&init)) { + * // Error, the subsystem isn't initialized + * SDL_SetInitialized(&init, false); + * return false; + * } + * + * // Do work using the initialized subsystem + * + * return true; + * } + * + * void QuitSystem(void) + * { + * if (!SDL_ShouldQuit(&init)) { + * // The system is not initialized + * return true; + * } + * + * // At this point, you should not leave this function without calling SDL_SetInitialized() + * + * DoQuitTasks(); + * SDL_SetInitialized(&init, false); + * } + * ``` + * + * Note that this doesn't protect any resources created during initialization, or guarantee that nobody is using those resources during cleanup. You should use other mechanisms to protect those, if that's a concern for your code. + * + * \since This struct is available since SDL 3.0.0. + */ +typedef struct SDL_InitState +{ + SDL_AtomicInt status; + SDL_ThreadID thread; + void *reserved; +} SDL_InitState; + +/** + * Return whether initialization should be done. + * + * This function checks the passed in state and if initialization should be done, sets the status to `SDL_INIT_STATUS_INITIALIZING` and returns true. If another thread is already modifying this state, it will wait until that's done before returning. + * + * If this function returns true, the calling code must call SDL_SetInitialized() to complete the initialization. + * + * \param state the initialization state to check. + * \returns true if initialization needs to be done, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetInitialized + * \sa SDL_ShouldQuit + */ +extern SDL_DECLSPEC bool SDLCALL SDL_ShouldInit(SDL_InitState *state); + +/** + * Return whether cleanup should be done. + * + * This function checks the passed in state and if cleanup should be done, sets the status to `SDL_INIT_STATUS_UNINITIALIZING` and returns true. + * + * If this function returns true, the calling code must call SDL_SetInitialized() to complete the cleanup. + * + * \param state the initialization state to check. + * \returns true if cleanup needs to be done, false otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_SetInitialized + * \sa SDL_ShouldInit + */ +extern SDL_DECLSPEC bool SDLCALL SDL_ShouldQuit(SDL_InitState *state); + +/** + * Finish an initialization state transition. + * + * This function sets the status of the passed in state to `SDL_INIT_STATUS_INITIALIZED` or `SDL_INIT_STATUS_UNINITIALIZED` and allows any threads waiting for the status to proceed. + * + * \param state the initialization state to check. + * \param initialized the new initialization state. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_ShouldInit + * \sa SDL_ShouldQuit + */ +extern SDL_DECLSPEC void SDLCALL SDL_SetInitialized(SDL_InitState *state, bool initialized); + +/* @} *//* Thread-safe initialization state functions */ /* Ends C function definitions when using C++ */ #ifdef __cplusplus diff --git a/src/SDL_utils.c b/src/SDL_utils.c index 1263fea3b0..d6de998a93 100644 --- a/src/SDL_utils.c +++ b/src/SDL_utils.c @@ -121,40 +121,6 @@ bool SDL_endswith(const char *string, const char *suffix) return false; } -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) -{ - if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED, SDL_INIT_STATUS_UNINITIALIZING)) { - state->thread = SDL_GetCurrentThreadID(); - return true; - } - 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); - } -} - SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32)); Uint32 SDL_GetNextObjectID(void) diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h index 0a0cfef274..500f804fe3 100644 --- a/src/SDL_utils_c.h +++ b/src/SDL_utils_c.h @@ -47,24 +47,6 @@ extern bool SDL_endswith(const char *string, const char *suffix); */ extern int SDL_URIToLocal(const char *src, char *dst); -typedef enum SDL_InitStatus -{ - SDL_INIT_STATUS_UNINITIALIZED, - SDL_INIT_STATUS_INITIALIZING, - SDL_INIT_STATUS_INITIALIZED, - SDL_INIT_STATUS_UNINITIALIZING -} SDL_InitStatus; - -typedef struct SDL_InitState -{ - SDL_AtomicInt status; - SDL_ThreadID thread; -} SDL_InitState; - -extern bool SDL_ShouldInit(SDL_InitState *state); -extern bool SDL_ShouldQuit(SDL_InitState *state); -extern void SDL_SetInitialized(SDL_InitState *state, bool initialized); - typedef enum { SDL_OBJECT_TYPE_UNKNOWN, diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 1b22f05440..84cc539b30 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -817,6 +817,7 @@ SDL3_0.0.0 { SDL_SetHapticGain; SDL_SetHint; SDL_SetHintWithPriority; + SDL_SetInitialized; SDL_SetJoystickEventsEnabled; SDL_SetJoystickLED; SDL_SetJoystickPlayerIndex; @@ -895,6 +896,8 @@ SDL3_0.0.0 { SDL_SetX11EventHook; SDL_SetiOSAnimationCallback; SDL_SetiOSEventPump; + SDL_ShouldInit; + SDL_ShouldQuit; SDL_ShowAndroidToast; SDL_ShowCursor; SDL_ShowMessageBox; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 4b59a825aa..d0707e7db9 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -842,6 +842,7 @@ #define SDL_SetHapticGain SDL_SetHapticGain_REAL #define SDL_SetHint SDL_SetHint_REAL #define SDL_SetHintWithPriority SDL_SetHintWithPriority_REAL +#define SDL_SetInitialized SDL_SetInitialized_REAL #define SDL_SetJoystickEventsEnabled SDL_SetJoystickEventsEnabled_REAL #define SDL_SetJoystickLED SDL_SetJoystickLED_REAL #define SDL_SetJoystickPlayerIndex SDL_SetJoystickPlayerIndex_REAL @@ -920,6 +921,8 @@ #define SDL_SetX11EventHook SDL_SetX11EventHook_REAL #define SDL_SetiOSAnimationCallback SDL_SetiOSAnimationCallback_REAL #define SDL_SetiOSEventPump SDL_SetiOSEventPump_REAL +#define SDL_ShouldInit SDL_ShouldInit_REAL +#define SDL_ShouldQuit SDL_ShouldQuit_REAL #define SDL_ShowAndroidToast SDL_ShowAndroidToast_REAL #define SDL_ShowCursor SDL_ShowCursor_REAL #define SDL_ShowMessageBox SDL_ShowMessageBox_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index ae93bf3e0e..f0986cd3ac 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -852,6 +852,7 @@ SDL_DYNAPI_PROC(bool,SDL_SetHapticAutocenter,(SDL_Haptic *a, int b),(a,b),return SDL_DYNAPI_PROC(bool,SDL_SetHapticGain,(SDL_Haptic *a, int b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_SetHint,(const char *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_SetHintWithPriority,(const char *a, const char *b, SDL_HintPriority c),(a,b,c),return) +SDL_DYNAPI_PROC(void,SDL_SetInitialized,(SDL_InitState *a, bool b),(a,b),) SDL_DYNAPI_PROC(void,SDL_SetJoystickEventsEnabled,(bool a),(a),) SDL_DYNAPI_PROC(bool,SDL_SetJoystickLED,(SDL_Joystick *a, Uint8 b, Uint8 c, Uint8 d),(a,b,c,d),return) SDL_DYNAPI_PROC(bool,SDL_SetJoystickPlayerIndex,(SDL_Joystick *a, int b),(a,b),return) @@ -930,6 +931,8 @@ SDL_DYNAPI_PROC(void,SDL_SetWindowsMessageHook,(SDL_WindowsMessageHook a, void * SDL_DYNAPI_PROC(void,SDL_SetX11EventHook,(SDL_X11EventHook a, void *b),(a,b),) SDL_DYNAPI_PROC(bool,SDL_SetiOSAnimationCallback,(SDL_Window *a, int b, SDL_iOSAnimationCallback c, void *d),(a,b,c,d),return) SDL_DYNAPI_PROC(void,SDL_SetiOSEventPump,(bool a),(a),) +SDL_DYNAPI_PROC(bool,SDL_ShouldInit,(SDL_InitState *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_ShouldQuit,(SDL_InitState *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_ShowAndroidToast,(const char *a, int b, int c, int d, int e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(bool,SDL_ShowCursor,(void),(),return) SDL_DYNAPI_PROC(bool,SDL_ShowMessageBox,(const SDL_MessageBoxData *a, int *b),(a,b),return) diff --git a/src/thread/SDL_thread.c b/src/thread/SDL_thread.c index 5d4550a040..d2b914524c 100644 --- a/src/thread/SDL_thread.c +++ b/src/thread/SDL_thread.c @@ -517,3 +517,37 @@ bool SDL_WaitConditionTimeout(SDL_Condition *cond, SDL_Mutex *mutex, Sint32 time 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) +{ + if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED, SDL_INIT_STATUS_UNINITIALIZING)) { + state->thread = SDL_GetCurrentThreadID(); + return true; + } + 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); + } +} +