SDL: thread: SDL_CreateThreadWithStackSize is now SDL_CreateThreadWithProperties.

From b83bb035e670176c6b8d7da2694d7d99328c1161 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 22 May 2024 01:47:09 -0400
Subject: [PATCH] thread: SDL_CreateThreadWithStackSize is now
 SDL_CreateThreadWithProperties.

---
 docs/README-migration.md          |  6 ++-
 include/SDL3/SDL_thread.h         | 82 ++++++++++++++++++++++---------
 src/dynapi/SDL_dynapi.sym         |  2 +-
 src/dynapi/SDL_dynapi_overrides.h |  2 +-
 src/dynapi/SDL_dynapi_procs.h     |  2 +-
 src/thread/SDL_systhread.h        |  3 ++
 src/thread/SDL_thread.c           | 55 +++++++++++++++------
 7 files changed, 108 insertions(+), 44 deletions(-)

diff --git a/docs/README-migration.md b/docs/README-migration.md
index f8d33a0d235bc..b5d7c784ee0dc 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -734,7 +734,7 @@ The following hints have been removed:
 * SDL_HINT_RENDER_LOGICAL_SIZE_MODE - the logical size mode is explicitly set with SDL_SetRenderLogicalPresentation()
 * SDL_HINT_RENDER_OPENGL_SHADERS - shaders are always used if they are available
 * SDL_HINT_RENDER_SCALE_QUALITY - textures now default to linear filtering, use SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST) if you want nearest pixel mode instead
-* SDL_HINT_THREAD_STACK_SIZE - the stack size can be specified using SDL_CreateThreadWithStackSize()
+* SDL_HINT_THREAD_STACK_SIZE - the stack size can be specified using SDL_CreateThreadWithProperties()
 * SDL_HINT_VIDEO_EXTERNAL_CONTEXT - replaced with SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN in SDL_CreateWindowWithProperties()
 * SDL_HINT_VIDEO_FOREIGN_WINDOW_OPENGL - replaced with SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN in SDL_CreateWindowWithProperties()
 * SDL_HINT_VIDEO_FOREIGN_WINDOW_VULKAN - replaced with SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN in SDL_CreateWindowWithProperties()
@@ -1630,7 +1630,9 @@ becomes:
 
 ## SDL_thread.h
 
-SDL_CreateThread and SDL_CreateThreadWithStackSpace now take beginthread/endthread function pointers on all platforms (ignoring them on most), and have been replaced with macros that hide this detail on all platforms. This works the same as before at the source code level, but the actual function signature that is called in SDL has changed. The library's exported symbol is SDL_CreateThreadRuntime, and looking for "SDL_CreateThread" in the DLL/Shared Library/Dylib will fail. You should not call this directly, but instead always use the macro!
+SDL_CreateThreadWithStackSpace has been replaced with SDL_CreateThreadWithProperties.
+
+SDL_CreateThread and SDL_CreateThreadWithProperties now take beginthread/endthread function pointers on all platforms (ignoring them on most), and have been replaced with macros that hide this detail on all platforms. This works the same as before at the source code level, but the actual function signature that is called in SDL has changed. The library's exported symbol is SDL_CreateThreadRuntime, and looking for "SDL_CreateThread" in the DLL/Shared Library/Dylib will fail. You should not call this directly, but instead always use the macro!
 
 The following functions have been renamed:
 * SDL_TLSCleanup() => SDL_CleanupTLS()
diff --git a/include/SDL3/SDL_thread.h b/include/SDL3/SDL_thread.h
index e830470569cf3..ba87bc5ae1322 100644
--- a/include/SDL3/SDL_thread.h
+++ b/include/SDL3/SDL_thread.h
@@ -197,11 +197,12 @@ typedef int (SDLCALL * SDL_ThreadFunction) (void *data);
 /**
  * Create a new thread with a default stack size.
  *
- * This is equivalent to calling:
+ * This is a convenience function, equivalent to calling
+ * SDL_CreateThreadWithProperties with the following properties set:
  *
- * ```c
- * SDL_CreateThreadWithStackSize(fn, name, 0, data);
- * ```
+ * - `SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER`: `fn`
+ * - `SDL_PROP_THREAD_CREATE_NAME_STRING`: `name`
+ * - `SDL_PROP_THREAD_CREATE_USERDATA_POINTER: `data`
  *
  * Note that this "function" is actually a macro that calls an internal
  * function with two extra parameters not listed here; they are
@@ -222,16 +223,28 @@ typedef int (SDLCALL * SDL_ThreadFunction) (void *data);
  *
  * \since This function is available since SDL 3.0.0.
  *
- * \sa SDL_CreateThreadWithStackSize
+ * \sa SDL_CreateThreadWithProperties
  * \sa SDL_WaitThread
  */
 extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data);
 
 /**
- * Create a new thread with a specific stack size.
+ * Create a new thread with with the specified properties.
+ *
+ * These are the supported properties:
  *
- * SDL makes an attempt to report `name` to the system, so that debuggers can
- * display it. Not all platforms support this.
+ * - `SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER`: an SDL_ThreadFunction
+ *   value that will be called at the start of the new thread's life. Required.
+ * - `SDL_PROP_THREAD_CREATE_NAME_STRING`: the name of the new thread,
+ *   which might be available to debuggers. Optional, defaults to NULL.
+ * - `SDL_PROP_THREAD_CREATE_USERDATA_POINTER`: an arbitrary app-defined
+ *   pointer, which is passed to the entry function on the new thread, as
+ *   its only parameter. Optional, defaults to NULL.
+ * - `SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER`: the size, in bytes, of the
+ *   new thread's stack. Optional, defaults to 0 (system-defined default).
+ *
+ * SDL makes an attempt to report `SDL_PROP_THREAD_CREATE_NAME_STRING` to the
+ * system, so that debuggers can display it. Not all platforms support this.
  *
  * Thread naming is a little complicated: Most systems have very small limits
  * for the string length (Haiku has 32 bytes, Linux currently has 16, Visual
@@ -247,7 +260,8 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(SDL_ThreadFunction fn,
  * (truncate, etc), but the original string contents will be available from
  * SDL_GetThreadName().
  *
- * The size (in bytes) of the new stack can be specified. Zero means "use the
+ * The size (in bytes) of the new stack can be specified with
+ * `SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER`. Zero means "use the
  * system default" which might be wildly different between platforms. x86
  * Linux generally defaults to eight megabytes, an embedded device might be a
  * few kilobytes instead. You generally need to specify a stack that is a
@@ -260,19 +274,15 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(SDL_ThreadFunction fn,
  * runtimes at the point of the function call. Language bindings that aren't
  * using the C headers will need to deal with this.
  *
- * The actual symbol in SDL's library is `SDL_CreateThreadRuntime` (or
- * `SDL_CreateThreadWithStackSpaceRuntime`), so there is no symbol clash, but
- * trying to load an SDL shared library and look for "SDL_CreateThread"
- * will fail.
+ * The actual symbol in SDL is `SDL_CreateThreadWithPropertiesRuntime`,
+ * so there is no symbol clash, but trying to load an SDL shared library
+ * and look for "SDL_CreateThreadWithProperties" will fail.
  *
  * Usually, apps should just call this function the same way on every platform and
  * let the macros hide the details. See SDL_BeginThreadFunction for the
  * technical details.
  *
- * \param fn the SDL_ThreadFunction function to call in the new thread
- * \param name the name of the thread
- * \param stacksize the size, in bytes, to allocate for the new thread stack.
- * \param data a pointer that is passed to `fn`
+ * \param props the properties to use
  * \returns an opaque pointer to the new thread object on success, NULL if the
  *          new thread could not be created; call SDL_GetError() for more
  *          information.
@@ -282,15 +292,41 @@ extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(SDL_ThreadFunction fn,
  * \sa SDL_CreateThread
  * \sa SDL_WaitThread
  */
-extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data);
+extern SDL_DECLSPEC SDL_Thread * SDLCALL SDL_CreateThreadWithProperties(SDL_PropertiesID props);
+
+#define SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER                  "entry_function"
+#define SDL_PROP_THREAD_CREATE_NAME_STRING                             "name"
+#define SDL_PROP_THREAD_CREATE_USERDATA_POINTER                        "userdata"
+#define SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER                        "stacksize"
+
+/* end wiki documentation for macros that are meant to look like functions. */
 #endif
 
-#ifndef SDL_WIKI_DOCUMENTATION_SECTION
-/* These are the actual functions exported from SDL! Don't use them directly! Use the SDL_CreateThread and SDL_CreateThreadWithStackSize macros! */
+
+/* These are the actual functions exported from SDL! Don't use them directly! Use the SDL_CreateThread and SDL_CreateThreadWithProperties macros! */
+
+/**
+ * The actual symbol in SDL's library for SDL_CreateThread.
+ *
+ * **Do not call this function directly!** Use SDL_CreateThread, which is a macro that handles C runtime support.
+ */
 extern SDL_DECLSPEC SDL_Thread *SDLCALL SDL_CreateThreadRuntime(SDL_ThreadFunction fn, const char *name, void *data, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread);
-extern SDL_DECLSPEC SDL_Thread *SDLCALL SDL_CreateThreadWithStackSizeRuntime(SDL_ThreadFunction fn, const char *name, const size_t stacksize, void *data,SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread);
-#define SDL_CreateThread(fn, name, data) SDL_CreateThreadRuntime(fn, name, data, (SDL_FunctionPointer) (SDL_BeginThreadFunction), (SDL_FunctionPointer) (SDL_EndThreadFunction))
-#define SDL_CreateThreadWithStackSize(fn, name, stacksize, data) SDL_CreateThreadWithStackSizeRuntime(fn, name, stacksize, data, (SDL_FunctionPointer) (SDL_BeginThreadFunction), (SDL_FunctionPointer) (SDL_EndThreadFunction))
+
+/**
+ * The actual symbol in SDL's library for SDL_CreateThreadWithProperties.
+ *
+ * **Do not call this function directly!** Use SDL_CreateThreadWithProperties, which is a macro that handles C runtime support.
+ */
+extern SDL_DECLSPEC SDL_Thread *SDLCALL SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props, SDL_FunctionPointer pfnBeginThread, SDL_FunctionPointer pfnEndThread);
+
+
+#ifndef SDL_WIKI_DOCUMENTATION_SECTION
+#define SDL_CreateThread(fn, name, data) SDL_CreateThreadRuntime((fn), (name), (data), (SDL_FunctionPointer) (SDL_BeginThreadFunction), (SDL_FunctionPointer) (SDL_EndThreadFunction))
+#define SDL_CreateThreadWithProperties(props) SDL_CreateThreadWithPropertiesRuntime((props), (SDL_FunctionPointer) (SDL_BeginThreadFunction), (SDL_FunctionPointer) (SDL_EndThreadFunction))
+#define SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER                  "entry_function"
+#define SDL_PROP_THREAD_CREATE_NAME_STRING                             "name"
+#define SDL_PROP_THREAD_CREATE_USERDATA_POINTER                        "userdata"
+#define SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER                        "stacksize"
 #endif
 
 /**
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 82c6165c31b06..4b5758984e38b 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -85,7 +85,7 @@ SDL3_0.0.0 {
     SDL_CreateTextureFromSurface;
     SDL_CreateTextureWithProperties;
     SDL_CreateThreadRuntime;
-    SDL_CreateThreadWithStackSizeRuntime;
+    SDL_CreateThreadWithPropertiesRuntime;
     SDL_CreateWindow;
     SDL_CreateWindowAndRenderer;
     SDL_CreateWindowWithProperties;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 45b44358debb5..d4301d0df8857 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -110,7 +110,7 @@
 #define SDL_CreateTextureFromSurface SDL_CreateTextureFromSurface_REAL
 #define SDL_CreateTextureWithProperties SDL_CreateTextureWithProperties_REAL
 #define SDL_CreateThreadRuntime    SDL_CreateThreadRuntime_REAL
-#define SDL_CreateThreadWithStackSizeRuntime   SDL_CreateThreadWithStackSizeRuntime_REAL
+#define SDL_CreateThreadWithPropertiesRuntime   SDL_CreateThreadWithPropertiesRuntime_REAL
 #define SDL_CreateWindow SDL_CreateWindow_REAL
 #define SDL_CreateWindowAndRenderer SDL_CreateWindowAndRenderer_REAL
 #define SDL_CreateWindowWithProperties SDL_CreateWindowWithProperties_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 67a212163370d..fd97c77e5f4ea 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -130,7 +130,7 @@ SDL_DYNAPI_PROC(SDL_Texture*,SDL_CreateTexture,(SDL_Renderer *a, SDL_PixelFormat
 SDL_DYNAPI_PROC(SDL_Texture*,SDL_CreateTextureFromSurface,(SDL_Renderer *a, SDL_Surface *b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_Texture*,SDL_CreateTextureWithProperties,(SDL_Renderer *a, SDL_PropertiesID b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_Thread*,SDL_CreateThreadRuntime,(SDL_ThreadFunction a, const char *b, void *c, SDL_FunctionPointer d, SDL_FunctionPointer e),(a,b,c,d,e),return)
-SDL_DYNAPI_PROC(SDL_Thread*,SDL_CreateThreadWithStackSizeRuntime,(SDL_ThreadFunction a, const char *b, const size_t c, void *d, SDL_FunctionPointer e, SDL_FunctionPointer f),(a,b,c,d,e,f),return)
+SDL_DYNAPI_PROC(SDL_Thread*,SDL_CreateThreadWithPropertiesRuntime,(SDL_PropertiesID a, SDL_FunctionPointer b, SDL_FunctionPointer c),(a,b,c),return)
 SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindow,(const char *a, int b, int c, SDL_WindowFlags d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_CreateWindowAndRenderer,(const char *a, int b, int c, SDL_WindowFlags d, SDL_Window **e, SDL_Renderer **f),(a,b,c,d,e,f),return)
 SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindowWithProperties,(SDL_PropertiesID a),(a),return)
diff --git a/src/thread/SDL_systhread.h b/src/thread/SDL_systhread.h
index c3370221dd972..c1c6ba2923ea2 100644
--- a/src/thread/SDL_systhread.h
+++ b/src/thread/SDL_systhread.h
@@ -60,6 +60,9 @@ extern SDL_TLSData *SDL_SYS_GetTLSData(void);
 /* Set the thread local storage for this thread */
 extern int SDL_SYS_SetTLSData(SDL_TLSData *data);
 
+/* A helper function for setting up a thread with a stack size. */
+extern SDL_Thread *SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, size_t stacksize, void *data);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/src/thread/SDL_thread.c b/src/thread/SDL_thread.c
index 31340253eb059..c7c82b1f365d5 100644
--- a/src/thread/SDL_thread.c
+++ b/src/thread/SDL_thread.c
@@ -307,14 +307,10 @@ void SDL_RunThread(SDL_Thread *thread)
     }
 }
 
-SDL_Thread *SDL_CreateThreadWithStackSizeRuntime(SDL_ThreadFunction fn,
-                              const char *name, const size_t stacksize, void *data,
+SDL_Thread *SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props,
                               SDL_FunctionPointer pfnBeginThread,
                               SDL_FunctionPointer pfnEndThread)
 {
-    SDL_Thread *thread;
-    int ret;
-
     // rather than check this in every backend, just make sure it's correct upfront. Only allow non-NULL if non-WinRT Windows, or Microsoft GDK.
     #if (!defined(SDL_PLATFORM_WIN32) && !defined(SDL_PLATFORM_GDK)) || defined(SDL_PLATFORM_WINRT)
     if (pfnBeginThread || pfnEndThread) {
@@ -323,15 +319,24 @@ SDL_Thread *SDL_CreateThreadWithStackSizeRuntime(SDL_ThreadFunction fn,
     }
     #endif
 
-    /* Allocate memory for the thread info structure */
-    thread = (SDL_Thread *)SDL_calloc(1, sizeof(*thread));
+    SDL_ThreadFunction fn = (SDL_ThreadFunction) SDL_GetProperty(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_GetProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, NULL);
+
+    if (!fn) {
+        SDL_SetError("Thread entry function is NULL");
+        return NULL;
+    }
+
+    SDL_Thread *thread = (SDL_Thread *)SDL_calloc(1, sizeof(*thread));
     if (!thread) {
         return NULL;
     }
     thread->status = -1;
     SDL_AtomicSet(&thread->state, SDL_THREAD_STATE_ALIVE);
 
-    /* Set up the arguments for the thread */
+    // Set up the arguments for the thread
     if (name) {
         thread->name = SDL_strdup(name);
         if (!thread->name) {
@@ -341,28 +346,46 @@ SDL_Thread *SDL_CreateThreadWithStackSizeRuntime(SDL_ThreadFunction fn,
     }
 
     thread->userfunc = fn;
-    thread->userdata = data;
+    thread->userdata = userdata;
     thread->stacksize = stacksize;
 
-    /* Create the thread and go! */
-    ret = SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread);
-    if (ret < 0) {
-        /* Oops, failed.  Gotta free everything */
+    // Create the thread and go!
+    if (SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread) < 0) {
+        // Oops, failed.  Gotta free everything
         SDL_free(thread->name);
         SDL_free(thread);
         thread = NULL;
     }
 
-    /* Everything is running now */
+    // Everything is running now
     return thread;
 }
 
 SDL_Thread *SDL_CreateThreadRuntime(SDL_ThreadFunction fn,
-                 const char *name, void *data,
+                 const char *name, void *userdata,
                  SDL_FunctionPointer pfnBeginThread,
                  SDL_FunctionPointer pfnEndThread)
 {
-    return SDL_CreateThreadWithStackSizeRuntime(fn, name, 0, data, pfnBeginThread, pfnEndThread);
+    const SDL_PropertiesID props = SDL_CreateProperties();
+    SDL_SetProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn);
+    SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name);
+    SDL_SetProperty(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_SetProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn);
+    SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name);
+    SDL_SetProperty(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)