From 4481754359009b2e54e5786e17be489a216bd0c1 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sat, 4 Nov 2023 11:06:42 -0700 Subject: [PATCH] Make sure we only dispatch events on the main thread when using application callbacks --- src/main/SDL_main_callbacks.c | 81 ++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c index 076c84259..6fd0783a4 100644 --- a/src/main/SDL_main_callbacks.c +++ b/src/main/SDL_main_callbacks.c @@ -27,11 +27,57 @@ 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 int SDLCALL EventWatcher(void *userdata, SDL_Event *event) +// Return true if this event needs to be processed before returning from the event watcher +static SDL_bool ShouldDispatchImmediately(SDL_Event *event) { - if (SDL_AtomicGet(&apprc) == 0) { // if already quitting, don't send the event to the app. + switch (event->type) { + case SDL_EVENT_TERMINATING: + case SDL_EVENT_LOW_MEMORY: + case SDL_EVENT_WILL_ENTER_BACKGROUND: + case SDL_EVENT_DID_ENTER_BACKGROUND: + case SDL_EVENT_WILL_ENTER_FOREGROUND: + case SDL_EVENT_DID_ENTER_FOREGROUND: + return SDL_TRUE; + default: + return SDL_FALSE; + } +} + +static void SDL_DispatchMainCallbackEvent(SDL_Event *event) +{ + if (SDL_AtomicGet(&apprc) == 0) { // if already quitting, don't send the event to the app. SDL_AtomicCAS(&apprc, 0, SDL_main_event_callback(event)); } + SDL_CleanupEvent(event); +} + +static void SDL_DispatchMainCallbackEvents() +{ + SDL_Event events[16]; + + for (;;) { + int count = SDL_PeepEvents(events, SDL_arraysize(events), SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST); + if (count <= 0) { + break; + } + for (int i = 0; i < count; ++i) { + SDL_Event *event = &events[i]; + if (!ShouldDispatchImmediately(event)) { + SDL_DispatchMainCallbackEvent(event); + } + } + } +} + +static int SDLCALL SDL_MainCallbackEventWatcher(void *userdata, SDL_Event *event) +{ + if (ShouldDispatchImmediately(event)) { + // Make sure any currently queued events are processed then dispatch this before continuing + SDL_DispatchMainCallbackEvents(); + SDL_DispatchMainCallbackEvent(event); + } else { + // We'll process this event later from the main event queue + } return 0; } @@ -50,34 +96,10 @@ int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_ return -1; } - // drain any initial events that might have arrived before we added a watcher. - SDL_Event event; - SDL_Event *pending_events = NULL; - int total_pending_events = 0; - while (SDL_PollEvent(&event)) { - void *ptr = SDL_realloc(pending_events, sizeof (SDL_Event) * (total_pending_events + 1)); - if (!ptr) { - SDL_OutOfMemory(); - SDL_free(pending_events); - SDL_AtomicSet(&apprc, -1); - return -1; - } - pending_events = (SDL_Event *) ptr; - SDL_copyp(&pending_events[total_pending_events], &event); - total_pending_events++; - } - - if (SDL_AddEventWatch(EventWatcher, NULL) == -1) { - SDL_free(pending_events); + if (SDL_AddEventWatch(SDL_MainCallbackEventWatcher, NULL) < 0) { SDL_AtomicSet(&apprc, -1); return -1; } - - for (int i = 0; i < total_pending_events; i++) { - SDL_PushEvent(&pending_events[i]); - } - - SDL_free(pending_events); } return SDL_AtomicGet(&apprc); @@ -85,9 +107,8 @@ int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_ int SDL_IterateMainCallbacks(void) { - // Just pump events and empty the queue, EventWatcher sends the events to the app. SDL_PumpEvents(); - SDL_FlushEvents(SDL_EVENT_FIRST, SDL_EVENT_LAST); + SDL_DispatchMainCallbackEvents(); int rc = SDL_main_iteration_callback(); if (!SDL_AtomicCAS(&apprc, 0, rc)) { @@ -99,7 +120,7 @@ int SDL_IterateMainCallbacks(void) void SDL_QuitMainCallbacks(void) { - SDL_DelEventWatch(EventWatcher, NULL); + SDL_DelEventWatch(SDL_MainCallbackEventWatcher, NULL); SDL_main_quit_callback(); // for symmetry, you should explicitly Quit what you Init, but we might come through here uninitialized and SDL_Quit() will clear everything anyhow.