Add support for opting-in to SDL main callbacks - #618
Conversation
|
There is some prior art at #550 regarding this particular problem. |
| app->pending_event_text.add(0); | ||
| } | ||
| app->pending_events.add(pending); | ||
| app->using_main_callbacks = true; |
There was a problem hiding this comment.
Could this be moved somewhere where we do not write the boolean over and over again?
Given that we return on some of the events above, and as mentioned those are dispatched immediately, would it not be beneficial to set the using_main_callbacks to true at the very top of this function?
There was a problem hiding this comment.
A boolean write is basically nothing in the grand scheme of things. Esp when it's next to a pointer bump and write (pending_events.add). And the writes are independent.
The majority of times, this is called from the "main" thread anw.
The clanker's threading claim is overblown. SDL guarantees that events dispatched by SDL itself are serialized:
SDL is responsible for pumping the event queue between each call to SDL_AppIterate, so in normal operation one should only get events in a serial fashion
https://wiki.libsdl.org/SDL3/SDL_AppEvent
The implementation does serialize calls to SDL_AppEvent: https://github.com/libsdl-org/SDL/blob/dc05826028d6850ccc48661010ac444942cec0af/src/core/android/SDL_android.c#L2720
It's only a problem if you call SDL_PushEvent yourself because it bypasses the internal lock.
The bigger concern is since we give up the main loop, Checked the implementation, it's impossible. SDL's main loop will pump event and it will trigger pending event through AppIterate could get called before any AppEvent and some events are handled in poll mode but it's harmless.AppEvent.
This function can be simplified further actually. Just check for text and copy. Ignore the whole dropping lifecycle event thing.
There was a problem hiding this comment.
So in short, I think it's overly defensive again but I don't see a way for it to be wrong.
There was a problem hiding this comment.
Done, now it's guaranteed that if the app uses callback at all, it will always set the flag as early as possible. SDL_PollEvent won't even be called.
|
Not a big fan of callbacks. Is there not a way to get unfreezing without forcing CF to hijack the main loop? |
|
There is a way but it can be fragile and maybe equally annoying. First, the problem: Take Windows for example, during a resize, it will only call Windows is not the only platform with that behavior and resize is not the only thing: vsync in web, weird OS features like gamecenter on iOS... They all have about the same shape: The OS wants to own the loop so SDL complies and plugs the user-provided callbacks into it. That's how they deal with an OS that has a "modal mode". But I can think of a way: coroutine or thread.. If we lift the entire entrypoint into a coroutine, it can work. Probably. To do this, CF has to supplies the required callbacks to satisfy SDL. #include <cute.h>
int main(int argc, char* argv[]) // Something similar to SDL_main shenanigans to make this cf_main and lift it into a coroutine
{
// The start of the function is called by `SDL_AppInit`
cf_make_app("Fancy Window Title", 0, 0, 0, 640, 480, CF_APP_OPTIONS_WINDOW_POS_CENTERED_BIT, argv[0]);
while (cf_app_is_running()) // Yield on the first call, this is the end of `SDL_AppInit`
{
cf_app_update(); // No change
// or alternatively, yield on the first call of `cf_app_update`
// so a `while(true)` loop with `if (!cf_app_is_running){ break; }` works too
// All your game logic and updates go here...
app_draw_onto_screen(); // Yield on every call, now the entire loop is driven by `SDL_AppIterate`
}
// The moment CF detects that the app is no longer running, it sets a flag.
// `SDL_AppIterate` must **not** resume the coroutine.
// Only now `SDL_AppQuit` can resume **once**.
// The rest of this function becomes `SDL_AppQuit`
destroy_app(); // Run as usual
return 0; // Result translated into SDL status code
}So with coroutine, a single function with a loop can be split into 3 functions with carefully engineered suspension points.
Both will do the wiring:
So almost no change from user code. Why it's fragile:
The callbacks supplied by CF will be something like: extern int cf_main(int argc, char* argv[]); // `main` is renamed into this through macro
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
return cf_app_callback_init(cf_main, argc, argv); // init CF in callback mode, spawn the coroutine
// it will be suspended on the first `cf_app_update`
}
SDL_AppResult SDL_AppIterate(void** appstate)
{
return cf_app_callback_iterate(); // Resume the coroutine if it has not terminated and `cf_app_is_running` is true
}
SDL_AppResult SDL_AppQuit(void** appstate)
{
return cf_app_callback_quit(); // Resume the coroutine **once** iff `cf_app_is_running` is false
}
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
return cf_app_callback_event(event); // same behavior as cf_app_push_event in this PR
// simply buffer the events so `cf_app_update` can process them
}The majority of it is just forwarding into CF. So the answer to "not hijack the main loop" is "hijack the entire entrypoint instead". |
Motivation: using callbacks,,apps no longer freeze while being dragged. On the web, vsync is done for free. It seems
cf_app_set_present_modenever really works on web (SDL_GL_SwapWindow only yield, not wait forrequestAnimationFrame).Opt in path
SDL_MAIN_USE_CALLBACKS,CF_MAIN(existing check) then includecute.hSDL_AppInit,SDL_AppIterateandSDL_AppQuitto handle the application lifecycle.The names are not wrapped because using callback is not that common.
But they could be.
New API functions
cf_app_push_eventto send aSDL_Eventto CF.This is what
SDL_AppEventshould call.cute.hincludescute_main_callbacks.hifCF_MAINis defined.This header defines a default
SDL_AppEventwhich does the forwarding so user code only has to write Init, Iterate and Quit.This can be disabled with
CF_MAIN_CUSTOM_APP_EVENT.A custom
SDL_AppEventalso allows one to filter or see events before CF (e.g: smooth mouse look) if needed.Implementations details
Internally, events are pushed into a queue with
cf_app_push_event.Text events are deep copied.
s_poll_eventchecks whether CF is on the callback or main loop path and either drains from the queue or polls from SDL.This replaces the call to
SDL_PollEventincf_pump_input_msgs.So event works just as before.
Inputs are still coalesced at every fixed ticks.