From 38e3c6a4aa338d062ca2eba80728bfdf319f7104 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 27 Mar 2024 17:22:08 -0400 Subject: [PATCH] main: Add an optional `appstate` param to main callback entry points. This allows apps to maintain state data without using global variables. Fixes #9377. --- include/SDL3/SDL_main.h | 39 ++++++++++++++++++++++++++++------- src/main/SDL_main_callbacks.c | 10 +++++---- test/loopwave.c | 8 +++---- test/testaudio.c | 8 +++---- test/testaudiocapture.c | 8 +++---- test/testcamera.c | 8 +++---- test/testsprite.c | 8 +++---- 7 files changed, 57 insertions(+), 32 deletions(-) diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index b7705d660..0659ad61d 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -158,10 +158,10 @@ extern "C" { #endif -typedef int (SDLCALL *SDL_AppInit_func)(int argc, char *argv[]); -typedef int (SDLCALL *SDL_AppIterate_func)(void); -typedef int (SDLCALL *SDL_AppEvent_func)(const SDL_Event *event); -typedef void (SDLCALL *SDL_AppQuit_func)(void); +typedef int (SDLCALL *SDL_AppInit_func)(void **appstate, int argc, char *argv[]); +typedef int (SDLCALL *SDL_AppIterate_func)(void *appstate); +typedef int (SDLCALL *SDL_AppEvent_func)(void *appstate, const SDL_Event *event); +typedef void (SDLCALL *SDL_AppQuit_func)(void *appstate); /** * You can (optionally!) define SDL_MAIN_USE_CALLBACKS before including @@ -203,6 +203,12 @@ typedef void (SDLCALL *SDL_AppQuit_func)(void); * This function should not go into an infinite mainloop; it should do any * one-time setup it requires and then return. * + * The app may optionally assign a pointer to `*appstate`. This pointer will + * be provided on every future call to the other entry points, to allow + * application state to be preserved between functions without the app + * needing to use a global variable. If this isn't set, the pointer will + * be NULL in future entry points. + * * If this function returns 0, the app will proceed to normal operation, * and will begin receiving repeated calls to SDL_AppIterate and SDL_AppEvent * for the life of the program. If this function returns < 0, SDL will @@ -210,6 +216,7 @@ typedef void (SDLCALL *SDL_AppQuit_func)(void); * an error to the platform. If it returns > 0, the SDL calls SDL_AppQuit * and terminates with an exit code that reports success to the platform. * + * \param appstate a place where the app can optionally store a pointer for future use. * \param argc The standard ANSI C main's argc; number of elements in `argv` * \param argv The standard ANSI C main's argv; array of command line arguments. * \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue. @@ -222,7 +229,7 @@ typedef void (SDLCALL *SDL_AppQuit_func)(void); * \sa SDL_AppEvent * \sa SDL_AppQuit */ -extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppInit(int argc, char *argv[]); +extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[]); /** * App-implemented iteration entry point for SDL_MAIN_USE_CALLBACKS apps. @@ -248,6 +255,9 @@ extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppInit(int argc, char *argv[]); * This function should not go into an infinite mainloop; it should do one * iteration of whatever the program does and return. * + * The `appstate` parameter is an optional pointer provided by the app during + * SDL_AppInit(). If the app never provided a pointer, this will be NULL. + * * If this function returns 0, the app will continue normal operation, * receiving repeated calls to SDL_AppIterate and SDL_AppEvent for the life * of the program. If this function returns < 0, SDL will call SDL_AppQuit @@ -255,6 +265,7 @@ extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppInit(int argc, char *argv[]); * platform. If it returns > 0, the SDL calls SDL_AppQuit and terminates with * an exit code that reports success to the platform. * + * \param appstate an optional pointer, provided by the app in SDL_AppInit. * \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue. * * \threadsafety This function is not thread safe. @@ -264,7 +275,7 @@ extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppInit(int argc, char *argv[]); * \sa SDL_AppInit * \sa SDL_AppEvent */ -extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppIterate(void); +extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppIterate(void *appstate); /** * App-implemented event entry point for SDL_MAIN_USE_CALLBACKS apps. @@ -293,6 +304,9 @@ extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppIterate(void); * This function should not go into an infinite mainloop; it should handle * the provided event appropriately and return. * + * The `appstate` parameter is an optional pointer provided by the app during + * SDL_AppInit(). If the app never provided a pointer, this will be NULL. + * * If this function returns 0, the app will continue normal operation, * receiving repeated calls to SDL_AppIterate and SDL_AppEvent for the life * of the program. If this function returns < 0, SDL will call SDL_AppQuit @@ -300,6 +314,8 @@ extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppIterate(void); * platform. If it returns > 0, the SDL calls SDL_AppQuit and terminates with * an exit code that reports success to the platform. * + * \param appstate an optional pointer, provided by the app in SDL_AppInit. + * \param event the new event for the app to examine. * \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue. * * \threadsafety This function is not thread safe. @@ -309,7 +325,7 @@ extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppIterate(void); * \sa SDL_AppInit * \sa SDL_AppIterate */ -extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppEvent(const SDL_Event *event); +extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppEvent(void *appstate, const SDL_Event *event); /** * App-implemented deinit entry point for SDL_MAIN_USE_CALLBACKS apps. @@ -330,13 +346,20 @@ extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppEvent(const SDL_Event *event); * it after this function returns and before the process terminates, but * it is safe to do so. * + * The `appstate` parameter is an optional pointer provided by the app during + * SDL_AppInit(). If the app never provided a pointer, this will be NULL. + * This function call is the last time this pointer will be provided, so + * any resources to it should be cleaned up here. + * + * \param appstate an optional pointer, provided by the app in SDL_AppInit. + * * \threadsafety This function is not thread safe. * * \since This function is available since SDL 3.0.0. * * \sa SDL_AppInit */ -extern SDLMAIN_DECLSPEC void SDLCALL SDL_AppQuit(void); +extern SDLMAIN_DECLSPEC void SDLCALL SDL_AppQuit(void *appstate); #endif /* SDL_MAIN_USE_CALLBACKS */ diff --git a/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c index 37657a866..a87a56b2f 100644 --- a/src/main/SDL_main_callbacks.c +++ b/src/main/SDL_main_callbacks.c @@ -26,6 +26,7 @@ static SDL_AppEvent_func SDL_main_event_callback; static SDL_AppIterate_func SDL_main_iteration_callback; static SDL_AppQuit_func SDL_main_quit_callback; static SDL_AtomicInt apprc; // use an atomic, since events might land from any thread and we don't want to wrap this all in a mutex. A CAS makes sure we only move from zero once. +static void *SDL_main_appstate = NULL; // Return true if this event needs to be processed before returning from the event watcher static SDL_bool ShouldDispatchImmediately(SDL_Event *event) @@ -46,7 +47,7 @@ static SDL_bool ShouldDispatchImmediately(SDL_Event *event) static void SDL_DispatchMainCallbackEvent(SDL_Event *event) { if (SDL_AtomicGet(&apprc) == 0) { // if already quitting, don't send the event to the app. - SDL_AtomicCompareAndSwap(&apprc, 0, SDL_main_event_callback(event)); + SDL_AtomicCompareAndSwap(&apprc, 0, SDL_main_event_callback(SDL_main_appstate, event)); } } @@ -95,7 +96,7 @@ int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_ SDL_main_quit_callback = appquit; SDL_AtomicSet(&apprc, 0); - const int rc = appinit(argc, argv); + const int rc = appinit(&SDL_main_appstate, argc, argv); if (SDL_AtomicCompareAndSwap(&apprc, 0, rc) && (rc == 0)) { // bounce if SDL_AppInit already said abort, otherwise... // make sure we definitely have events initialized, even if the app didn't do it. if (SDL_InitSubSystem(SDL_INIT_EVENTS) == -1) { @@ -121,7 +122,7 @@ int SDL_IterateMainCallbacks(SDL_bool pump_events) int rc = SDL_AtomicGet(&apprc); if (rc == 0) { - rc = SDL_main_iteration_callback(); + rc = SDL_main_iteration_callback(SDL_main_appstate); if (!SDL_AtomicCompareAndSwap(&apprc, 0, rc)) { rc = SDL_AtomicGet(&apprc); // something else already set a quit result, keep that. } @@ -132,7 +133,8 @@ int SDL_IterateMainCallbacks(SDL_bool pump_events) void SDL_QuitMainCallbacks(void) { SDL_DelEventWatch(SDL_MainCallbackEventWatcher, NULL); - SDL_main_quit_callback(); + SDL_main_quit_callback(SDL_main_appstate); + SDL_main_appstate = NULL; // just in case. // for symmetry, you should explicitly Quit what you Init, but we might come through here uninitialized and SDL_Quit() will clear everything anyhow. //SDL_QuitSubSystem(SDL_INIT_EVENTS); diff --git a/test/loopwave.c b/test/loopwave.c index 1aa627061..5ff015119 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -42,7 +42,7 @@ static int fillerup(void) return 0; } -int SDL_AppInit(int argc, char *argv[]) +int SDL_AppInit(void **appstate, int argc, char *argv[]) { int i; char *filename = NULL; @@ -119,17 +119,17 @@ int SDL_AppInit(int argc, char *argv[]) return 0; } -int SDL_AppEvent(const SDL_Event *event) +int SDL_AppEvent(void *appstate, const SDL_Event *event) { return (event->type == SDL_EVENT_QUIT) ? 1 : 0; } -int SDL_AppIterate(void) +int SDL_AppIterate(void *appstate) { return fillerup(); } -void SDL_AppQuit(void) +void SDL_AppQuit(void *appstate) { SDL_DestroyAudioStream(stream); SDL_free(wave.sound); diff --git a/test/testaudio.c b/test/testaudio.c index 59da63065..c4e60ef20 100644 --- a/test/testaudio.c +++ b/test/testaudio.c @@ -1036,7 +1036,7 @@ static void WindowResized(const int newwinw, const int newwinh) state->window_h = newwinh; } -int SDL_AppInit(int argc, char *argv[]) +int SDL_AppInit(void **appstate, int argc, char *argv[]) { int i; @@ -1094,7 +1094,7 @@ int SDL_AppInit(int argc, char *argv[]) static SDL_bool saw_event = SDL_FALSE; -int SDL_AppEvent(const SDL_Event *event) +int SDL_AppEvent(void *appstate, const SDL_Event *event) { Thing *thing = NULL; @@ -1214,7 +1214,7 @@ int SDL_AppEvent(const SDL_Event *event) return SDLTest_CommonEventMainCallbacks(state, event); } -int SDL_AppIterate(void) +int SDL_AppIterate(void *appstate) { if (app_ready_ticks == 0) { app_ready_ticks = SDL_GetTicks(); @@ -1232,7 +1232,7 @@ int SDL_AppIterate(void) return 0; /* keep going. */ } -void SDL_AppQuit(void) +void SDL_AppQuit(void *appstate) { while (things) { DestroyThing(things); /* make sure all the audio devices are closed, etc. */ diff --git a/test/testaudiocapture.c b/test/testaudiocapture.c index 04ab2f083..82027019e 100644 --- a/test/testaudiocapture.c +++ b/test/testaudiocapture.c @@ -21,7 +21,7 @@ static SDL_AudioStream *stream_in = NULL; static SDL_AudioStream *stream_out = NULL; static SDLTest_CommonState *state = NULL; -int SDL_AppInit(int argc, char **argv) +int SDL_AppInit(void **appstate, int argc, char **argv) { SDL_AudioDeviceID *devices; SDL_AudioSpec outspec; @@ -145,7 +145,7 @@ int SDL_AppInit(int argc, char **argv) return 0; } -int SDL_AppEvent(const SDL_Event *event) +int SDL_AppEvent(void *appstate, const SDL_Event *event) { if (event->type == SDL_EVENT_QUIT) { return 1; /* terminate as success. */ @@ -169,7 +169,7 @@ int SDL_AppEvent(const SDL_Event *event) return 0; /* keep going. */ } -int SDL_AppIterate(void) +int SDL_AppIterate(void *appstate) { if (!SDL_AudioDevicePaused(SDL_GetAudioStreamDevice(stream_in))) { SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); @@ -195,7 +195,7 @@ int SDL_AppIterate(void) return 0; /* keep app going. */ } -void SDL_AppQuit(void) +void SDL_AppQuit(void *appstate) { SDL_Log("Shutting down.\n"); const SDL_AudioDeviceID devid_in = SDL_GetAudioStreamDevice(stream_in); diff --git a/test/testcamera.c b/test/testcamera.c index 51ba1d2aa..c0d9ec476 100644 --- a/test/testcamera.c +++ b/test/testcamera.c @@ -26,7 +26,7 @@ static SDL_Surface *frame_current = NULL; static SDL_CameraDeviceID front_camera = 0; static SDL_CameraDeviceID back_camera = 0; -int SDL_AppInit(int argc, char *argv[]) +int SDL_AppInit(void **appstate, int argc, char *argv[]) { int devcount = 0; int i; @@ -159,7 +159,7 @@ static int FlipCamera(void) return 0; } -int SDL_AppEvent(const SDL_Event *event) +int SDL_AppEvent(void *appstate, const SDL_Event *event) { switch (event->type) { case SDL_EVENT_KEY_DOWN: { @@ -209,7 +209,7 @@ int SDL_AppEvent(const SDL_Event *event) return SDLTest_CommonEventMainCallbacks(state, event); } -int SDL_AppIterate(void) +int SDL_AppIterate(void *appstate) { SDL_SetRenderDrawColor(renderer, 0x99, 0x99, 0x99, 255); SDL_RenderClear(renderer); @@ -262,7 +262,7 @@ int SDL_AppIterate(void) return 0; /* keep iterating. */ } -void SDL_AppQuit(void) +void SDL_AppQuit(void *appstate) { SDL_ReleaseCameraFrame(camera, frame_current); SDL_CloseCamera(camera); diff --git a/test/testsprite.c b/test/testsprite.c index 7db994c9d..8bd6e16bb 100644 --- a/test/testsprite.c +++ b/test/testsprite.c @@ -45,7 +45,7 @@ static SDL_bool suspend_when_occluded; /* -1: infinite random moves (default); >=0: enables N deterministic moves */ static int iterations = -1; -void SDL_AppQuit(void) +void SDL_AppQuit(void *appstate) { SDL_free(sprites); SDL_free(positions); @@ -386,12 +386,12 @@ static void MoveSprites(SDL_Renderer *renderer, SDL_Texture *sprite) SDL_RenderPresent(renderer); } -int SDL_AppEvent(const SDL_Event *event) +int SDL_AppEvent(void *appstate, const SDL_Event *event) { return SDLTest_CommonEventMainCallbacks(state, event); } -int SDL_AppIterate(void) +int SDL_AppIterate(void *appstate) { Uint64 now; int i; @@ -425,7 +425,7 @@ int SDL_AppIterate(void) return 0; /* keep going */ } -int SDL_AppInit(int argc, char *argv[]) +int SDL_AppInit(void **appstate, int argc, char *argv[]) { int i; Uint64 seed;