main: If SDL_MAIN_CALLBACK_RATE="waitevent", have at least one iteration run.

This way apps can at least draw an initial image to their windows before going
into a deep, blocking wait for user interaction.

Fixes #15027.
This commit is contained in:
Ryan C. Gordon 2026-06-19 14:14:13 -04:00
parent e63b247738
commit 3a1cfbc147
No known key found for this signature in database
GPG key ID: FA148B892AB48044
3 changed files with 27 additions and 5 deletions

View file

@ -2835,7 +2835,11 @@ extern "C" {
* There are other strings that have special meaning. If set to "waitevent",
* SDL_AppIterate will not be called until new event(s) have arrived (and been
* processed by SDL_AppEvent). This can be useful for apps that are completely
* idle except in response to input.
* idle except in response to input. As of SDL 3.6.0, SDL will allow a single
* call to SDL_AppIterate to proceed without an event immediately after this
* hint is set to "waitevent", so apps can have a minimum of activity, perhaps
* to render an initial image to a window before the user has otherwise
* interacted with the app.
*
* On some platforms, or if you are using SDL_main instead of SDL_AppIterate,
* this hint is ignored. When the hint can be used, it is allowed to be

View file

@ -58,6 +58,8 @@ static bool SDLCALL EmscriptenMainCallbackEventWatcher(void *userdata, SDL_Event
static void EmscriptenInternalMainloop(void)
{
const bool force_iterate = callback_rate_changed && iterate_after_waitevent;
// callback rate changed? Update emscripten's mainloop iteration speed.
if (callback_rate_changed) {
callback_rate_changed = false;
@ -70,7 +72,7 @@ static void EmscriptenInternalMainloop(void)
if (iterate_after_waitevent) {
SDL_PumpEvents();
if (!saw_new_event) {
if (!saw_new_event && !force_iterate) {
// do nothing yet. Note that we're still going to iterate here because we can't block,
// but we can stop the app's iteration from progressing until there's an event.
return;
@ -94,7 +96,9 @@ int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit,
rc = SDL_APP_FAILURE;
} else {
SDL_AddHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL);
callback_rate_changed = false;
if (!iterate_after_waitevent) { // if we're doing waitevent, we need callback_rate_changed to stay true for the next iteration.
callback_rate_changed = false;
}
emscripten_set_main_loop(EmscriptenInternalMainloop, 0, 0); // don't throw an exception since we do an orderly return.
if (callback_rate_increment > 0.0) {
emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, callback_rate_increment);

View file

@ -27,9 +27,11 @@
static Uint64 callback_rate_increment = 0;
static bool iterate_after_waitevent = false;
static bool callback_rate_changed = false;
static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name, const char *oldValue, const char *newValue)
{
callback_rate_changed = true;
iterate_after_waitevent = newValue && (SDL_strcmp(newValue, "waitevent") == 0);
if (iterate_after_waitevent) {
callback_rate_increment = 0;
@ -45,10 +47,22 @@ static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name
static SDL_AppResult GenericIterateMainCallbacks(void)
{
if (iterate_after_waitevent) {
bool should_wait = iterate_after_waitevent;
if (callback_rate_changed) {
callback_rate_changed = false;
if (iterate_after_waitevent) {
// just go immediately for one iteration (it will do a PumpEvents),
// and do a full blocking wait for more events next time.
should_wait = false;
}
}
if (should_wait) {
SDL_WaitEvent(NULL);
}
return SDL_IterateMainCallbacks(!iterate_after_waitevent);
return SDL_IterateMainCallbacks(!should_wait);
}
int SDL_EnterAppMainCallbacks(int argc, char *argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)