SDL: Added SDL_ShouldInit() and SDL_ShouldQuit()

From 125e5928445d2ea949af53ffa045b911504e1046 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 26 Sep 2024 15:49:59 -0700
Subject: [PATCH] Added SDL_ShouldInit() and SDL_ShouldQuit()

These are handy functions to support thread-safe initialization and shutdown.
---
 include/SDL3/SDL_mutex.h          | 135 ++++++++++++++++++++++++++++++
 src/SDL_utils.c                   |  34 --------
 src/SDL_utils_c.h                 |  18 ----
 src/dynapi/SDL_dynapi.sym         |   3 +
 src/dynapi/SDL_dynapi_overrides.h |   3 +
 src/dynapi/SDL_dynapi_procs.h     |   3 +
 src/thread/SDL_thread.c           |  34 ++++++++
 7 files changed, 178 insertions(+), 52 deletions(-)

diff --git a/include/SDL3/SDL_mutex.h b/include/SDL3/SDL_mutex.h
index 66625618cfd17..d4467430ea37c 100644
--- a/include/SDL3/SDL_mutex.h
+++ b/include/SDL3/SDL_mutex.h
@@ -30,6 +30,7 @@
 
 #include <SDL3/SDL_stdinc.h>
 #include <SDL3/SDL_error.h>
+#include <SDL3/SDL_thread.h>
 
 /******************************************************************************/
 /* 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 1263fea3b017d..d6de998a93c8b 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 0a0cfef274e28..500f804fe372a 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 1b22f05440d34..84cc539b30b00 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 4b59a825aa97d..d0707e7db9978 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 ae93bf3e0ece3..f0986cd3ac929 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 5d4550a040edf..d2b914524c8f1 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);
+    }
+}
+