From 2816745f48cd4bf415159b5f3ef6578d6d5c8dc0 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 18 Jul 2024 10:32:13 -0700
Subject: [PATCH] Allow passing a pointer to SDL_FreeEventMemory()
Also clarify in the documentation that you should not call SDL_FreeEventMemory(NULL) from your main thread.
---
include/SDL3/SDL_events.h | 14 +++++++---
include/SDL3/SDL_thread.h | 2 ++
src/dynapi/SDL_dynapi_procs.h | 2 +-
src/events/SDL_events.c | 50 ++++++++++++++++++++++++++++-------
4 files changed, 54 insertions(+), 14 deletions(-)
diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index 6ed114aaf3444..da25244f844a0 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -1428,14 +1428,20 @@ extern SDL_DECLSPEC void * SDLCALL SDL_AllocateEventMemory(size_t size);
* Free temporary event memory allocated by SDL.
*
* This function frees temporary memory allocated for events and APIs that
- * return temporary strings. This memory is local to the thread that creates
+ * follow the SDL_GetStringRule. This memory is local to the thread that creates
* it and is automatically freed for the main thread when pumping the event
- * loop. For other threads you may want to call this function periodically to
+ * loop. For other threads you may call this function periodically to
* free any temporary memory created by that thread.
*
+ * You can free a specific pointer, to provide more fine grained control over memory management, or you can pass NULL to free all pending temporary allocations. You should *NOT* pass NULL on your main event handling thread, as there may be temporary memory being used by events in-flight. For that thread SDL will call this internally when it's safe to do so.
+ *
* Note that if you call SDL_AllocateEventMemory() on one thread and pass it
* to another thread, e.g. via a user event, then you should be sure the other
- * thread has finished processing it before calling this function.
+ * thread has finished processing it before calling this function with NULL.
+ *
+ * All temporary memory is freed on the main thread in SDL_Quit() and for other threads when they call SDL_CleanupTLS(), which is automatically called at cleanup time for threads created using SDL_CreateThread().
+ *
+ * \param mem a pointer allocated with SDL_AllocateEventMemory(), or NULL to free all pending temporary allocations.
*
* \threadsafety It is safe to call this function from any thread.
*
@@ -1443,7 +1449,7 @@ extern SDL_DECLSPEC void * SDLCALL SDL_AllocateEventMemory(size_t size);
*
* \sa SDL_AllocateEventMemory
*/
-extern SDL_DECLSPEC void SDLCALL SDL_FreeEventMemory(void);
+extern SDL_DECLSPEC void SDLCALL SDL_FreeEventMemory(const void *mem);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
diff --git a/include/SDL3/SDL_thread.h b/include/SDL3/SDL_thread.h
index ff84a81d58f3f..9f411a93833c4 100644
--- a/include/SDL3/SDL_thread.h
+++ b/include/SDL3/SDL_thread.h
@@ -520,6 +520,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_SetTLS(SDL_TLSID *id, const void *value, SDL
/**
* Cleanup all TLS data for this thread.
*
+ * If you are creating your threads outside of SDL and then calling SDL functions, you should call this function before your thread exits, to properly clean up SDL memory.
+ *
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.0.0.
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index f384bd9839d97..1a494d81fe37d 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -169,7 +169,7 @@ SDL_DYNAPI_PROC(int,SDL_FlushAudioStream,(SDL_AudioStream *a),(a),return)
SDL_DYNAPI_PROC(void,SDL_FlushEvent,(Uint32 a),(a),)
SDL_DYNAPI_PROC(void,SDL_FlushEvents,(Uint32 a, Uint32 b),(a,b),)
SDL_DYNAPI_PROC(int,SDL_FlushRenderer,(SDL_Renderer *a),(a),return)
-SDL_DYNAPI_PROC(void,SDL_FreeEventMemory,(void),(),)
+SDL_DYNAPI_PROC(void,SDL_FreeEventMemory,(const void *a),(a),)
SDL_DYNAPI_PROC(void,SDL_GDKSuspendComplete,(void),(),)
SDL_DYNAPI_PROC(SDL_GLContext,SDL_GL_CreateContext,(SDL_Window *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_GL_DestroyContext,(SDL_GLContext a),(a),return)
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index da4f0881b46aa..60bf7d6b18c68 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -110,12 +110,7 @@ static void SDL_CleanupEventMemory(void *data)
{
SDL_EventMemoryState *state = (SDL_EventMemoryState *)data;
- while (state->head) {
- SDL_EventMemory *entry = state->head;
- state->head = entry->next;
- SDL_free(entry->memory);
- SDL_free(entry);
- }
+ SDL_FreeEventMemory(NULL);
SDL_free(state);
}
@@ -217,9 +212,46 @@ static void SDL_FlushEventMemory(Uint32 eventID)
}
}
-void SDL_FreeEventMemory(void)
+void SDL_FreeEventMemory(const void *mem)
{
- SDL_FlushEventMemory(0);
+ SDL_EventMemoryState *state;
+
+ state = SDL_GetEventMemoryState(SDL_FALSE);
+ if (!state) {
+ return;
+ }
+
+ if (mem) {
+ SDL_EventMemory *prev = NULL, *entry;
+
+ for (entry = state->head; entry; prev = entry, entry = entry->next) {
+ if (mem == entry->memory) {
+ if (prev) {
+ prev->next = entry->next;
+ }
+ if (entry == state->head) {
+ state->head = entry->next;
+ }
+ if (entry == state->tail) {
+ state->tail = prev;
+ }
+ SDL_free(entry->memory);
+ SDL_free(entry);
+ break;
+ }
+ }
+ } else {
+ if (state->head) {
+ while (state->head) {
+ SDL_EventMemory *entry = state->head;
+
+ state->head = entry->next;
+ SDL_free(entry->memory);
+ SDL_free(entry);
+ }
+ state->tail = NULL;
+ }
+ }
}
#ifndef SDL_JOYSTICK_DISABLED
@@ -741,7 +773,7 @@ void SDL_StopEventLoop(void)
SDL_EventQ.free = NULL;
SDL_AtomicSet(&SDL_sentinel_pending, 0);
- SDL_FlushEventMemory(0);
+ SDL_FreeEventMemory(NULL);
/* Clear disabled event state */
for (i = 0; i < SDL_arraysize(SDL_disabled_events); ++i) {