-
-
Notifications
You must be signed in to change notification settings - Fork 49
Add opt-in support for SDL's main callbacks #550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pusewicz
wants to merge
9
commits into
RandyGaul:master
Choose a base branch
from
pusewicz:sdl-main-callbacks
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
278e0a8
Split per-event handling out of cf_pump_input_msgs
pusewicz 275b4b5
Add opt-in support for SDL's main callbacks
pusewicz 92e0396
Fix SDL_EVENT_FINGER_MOTION never updating stored touch state
pusewicz ba79774
Guard s_handle_event against NULL SDL text pointers
pusewicz 5d5f340
Lock buffered_events_mutex in cf_free_buffered_events
pusewicz ae73d64
Document CF_MAIN_USE_CALLBACKS so doc references resolve
pusewicz 5f34811
Split main-callbacks glue into its own cute_main.h header
pusewicz b422fad
Fix stale CF_MAIN_USE_CALLBACKS comment references to cute_main.h
pusewicz 3309e77
Forward-declare SDL_Event for cf_app_process_event
pusewicz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| /* | ||
| Cute Framework | ||
| Copyright (C) 2024 Randy Gaul https://randygaul.github.io/ | ||
|
|
||
| This software is dual-licensed with zlib or Unlicense, check LICENSE.txt for more info | ||
| */ | ||
|
|
||
| #ifndef CF_MAIN_H | ||
| #define CF_MAIN_H | ||
|
|
||
| // Deliberately not included by cute.h -- the entry-point glue below has external linkage and must | ||
| // live in exactly one translation unit, so including this header is itself the opt-in. Include it | ||
| // alongside cute.h in the one file that owns your app's entry point, not anywhere else. | ||
|
|
||
| #if defined(CF_MAIN) | ||
| # error "cute_main.h: define either CF_MAIN or include cute_main.h, not both -- they are two different entry points." | ||
| #endif | ||
|
|
||
| // If something already pulled in <SDL3/SDL_main.h>, its include guard would silently swallow the | ||
| // callback machinery below and your program would link with no `main` at all. Catch it here | ||
| // instead: include cute_main.h before any other header that reaches SDL_main.h. | ||
| #ifdef SDL_main_h_ | ||
| # error "cute_main.h: <SDL3/SDL_main.h> was already included. Include cute_main.h first, before any header that reaches it." | ||
| #endif | ||
|
|
||
| #define SDL_MAIN_USE_CALLBACKS | ||
| #include "cute_app.h" | ||
| #include <SDL3/SDL_main.h> | ||
| #include <SDL3/SDL_log.h> | ||
|
|
||
| //-------------------------------------------------------------------------------------------------- | ||
| // C API | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif // __cplusplus | ||
|
|
||
| /** | ||
| * @function cf_main_init | ||
| * @category app | ||
| * @brief Your app's startup function -- implement it and call `cf_make_app` inside. | ||
| * @param argc The argument count, forwarded from the entry point. | ||
| * @param argv The argument vector, forwarded from the entry point. Pass `argv[0]` along to `cf_make_app`. | ||
| * @return Return the `CF_Result` from `cf_make_app` (or your own error). An error result aborts startup. | ||
| * @remarks Include `cute_main.h` in exactly one source file to let the host drive your main loop instead of | ||
| * writing your own `while` loop -- don't define `main` yourself, implement `cf_main_init`, | ||
| * `cf_main_update` and `cf_main_quit` instead. All three must live in this one file: in C++ they | ||
| * are only declared here, so defining one in a file that lacks this include gives it C++ linkage | ||
| * and you get an undefined-symbol error at link time. | ||
| * | ||
| * Called once before the first `cf_main_update`. Your `cf_make_app` call must include | ||
| * `CF_APP_OPTIONS_MAIN_CALLBACKS_BIT` -- startup fails with a message if it doesn't, rather than | ||
| * leaving you with a window that silently receives no input. | ||
| * | ||
| * The exact same code then runs on desktop, web, and mobile -- no platform-specific main-loop | ||
| * forks needed. The app quits once `cf_app_is_running` returns false (window close, or call | ||
| * `cf_app_signal_shutdown`). The platform decides the frame rate, so drive animation off | ||
| * `CF_DELTA_TIME` rather than assuming 60hz. | ||
| * | ||
| * Note for fixed-timestep games (`cf_set_fixed_timestep`): events are delivered between frames, | ||
| * so all sub-steps of one frame share the same input snapshot (the classic loop re-polls the OS | ||
| * queue per sub-step). | ||
| * | ||
| * Implemented on [SDL's main callbacks](https://wiki.libsdl.org/SDL3/README-main-functions): CF | ||
| * defines `SDL_AppInit`/`SDL_AppIterate`/`SDL_AppEvent`/`SDL_AppQuit` for you. If you want those | ||
| * four yourself, skip this header entirely: define `SDL_MAIN_USE_CALLBACKS`, write them, pass | ||
| * `CF_APP_OPTIONS_MAIN_CALLBACKS_BIT` to `cf_make_app`, and forward every event to | ||
| * `cf_app_process_event` (see cute_app.h). | ||
| * @related cf_main_update cf_main_quit cf_make_app cf_app_process_event CF_AppOptionFlagBits | ||
| */ | ||
| CF_Result cf_main_init(int argc, char* argv[]); | ||
|
|
||
| /** | ||
| * @function cf_main_update | ||
| * @category app | ||
| * @brief Your app's frame function -- one frame of the main loop. | ||
| * @remarks Call `cf_app_update` at the top and `cf_app_draw_onto_screen` at the bottom, with your game logic in | ||
| * between, exactly like the body of a classic `while (cf_app_is_running())` loop. The platform decides | ||
| * the call rate (e.g. the browser's requestAnimationFrame on web). The app quits once `cf_app_is_running` | ||
| * returns false -- call `cf_app_signal_shutdown` to request that. | ||
| * @related cf_main_init cf_main_quit cf_app_update cf_app_draw_onto_screen cf_app_signal_shutdown | ||
| */ | ||
| void cf_main_update(void); | ||
|
|
||
| /** | ||
| * @function cf_main_quit | ||
| * @category app | ||
| * @brief Your app's cleanup function -- called once at shutdown. | ||
| * @remarks Free your own resources here. `cf_destroy_app` is called for you right afterwards. Note this runs even | ||
| * when `cf_main_init` failed, so don't assume the app was created. | ||
| * @related cf_main_init cf_main_update cf_destroy_app | ||
| */ | ||
| void cf_main_quit(void); | ||
|
|
||
| SDL_AppResult SDLCALL SDL_AppInit(void** appstate, int argc, char* argv[]) | ||
| { | ||
| (void)appstate; | ||
| if (cf_is_error(cf_main_init(argc, argv))) return SDL_APP_FAILURE; | ||
| // Without the bit CF would keep polling an event queue the callback harness already drained, | ||
| // leaving a window with no keyboard, no mouse and no way to quit. Fail loudly instead. | ||
| if (!(cf_app_get_options() & CF_APP_OPTIONS_MAIN_CALLBACKS_BIT)) { | ||
| SDL_SetError("cf_make_app was called without CF_APP_OPTIONS_MAIN_CALLBACKS_BIT, which cute_main.h requires. Add it to the options passed to cf_make_app in cf_main_init."); | ||
| SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", SDL_GetError()); | ||
| return SDL_APP_FAILURE; | ||
| } | ||
| return SDL_APP_CONTINUE; | ||
| } | ||
|
|
||
| SDL_AppResult SDLCALL SDL_AppIterate(void* appstate) | ||
| { | ||
| (void)appstate; | ||
| cf_main_update(); | ||
| return cf_app_is_running() ? SDL_APP_CONTINUE : SDL_APP_SUCCESS; | ||
| } | ||
|
|
||
| SDL_AppResult SDLCALL SDL_AppEvent(void* appstate, SDL_Event* event) | ||
| { | ||
| (void)appstate; | ||
| cf_app_process_event(event); | ||
| return SDL_APP_CONTINUE; | ||
| } | ||
|
|
||
| void SDLCALL SDL_AppQuit(void* appstate, SDL_AppResult result) | ||
| { | ||
| (void)appstate; | ||
| (void)result; | ||
| cf_main_quit(); | ||
| cf_destroy_app(); | ||
| } | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif // __cplusplus | ||
|
|
||
| #endif // CF_MAIN_H |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // Lets the host drive the main loop instead of writing your own `while` loop. The same three | ||
| // functions run on desktop, web and mobile -- no `#ifdef CF_EMSCRIPTEN` fork anywhere. | ||
| // | ||
| // Try dragging the window edge and holding it: the orbiting dot keeps moving and the clock keeps | ||
| // ticking, where a classic `while (cf_app_is_running())` loop freezes until you let go. | ||
| // | ||
| // Including cute_main.h is itself the opt-in -- do it in exactly one source file, the one that | ||
| // owns your app's entry point. | ||
| #include <cute.h> | ||
| #include <cute_main.h> | ||
|
|
||
| #include <stdio.h> | ||
|
|
||
| static float s_elapsed; | ||
| static int s_presses; | ||
|
|
||
| CF_Result cf_main_init(int argc, char* argv[]) | ||
| { | ||
| (void)argc; | ||
| // CF checks this result for you -- no `if (cf_is_error(result)) return -1;` needed. | ||
| // CF_APP_OPTIONS_MAIN_CALLBACKS_BIT is required here: it stops CF from polling an event | ||
| // queue it no longer owns. Leave it out and CF refuses to start rather than handing you a | ||
| // window that silently receives no input. | ||
| return cf_make_app("Main Callbacks", 0, 0, 0, 640, 480, | ||
| CF_APP_OPTIONS_WINDOW_POS_CENTERED_BIT | CF_APP_OPTIONS_RESIZABLE_BIT | CF_APP_OPTIONS_MAIN_CALLBACKS_BIT, argv[0]); | ||
| } | ||
|
|
||
| void cf_main_update(void) | ||
| { | ||
| // One frame of the main loop -- exactly the body of a classic `while` loop. | ||
| cf_app_update(NULL); | ||
|
|
||
| // The host picks the frame rate (requestAnimationFrame on web), so animate off delta time | ||
| // instead of assuming 60hz. | ||
| s_elapsed += CF_DELTA_TIME; | ||
| if (cf_key_just_pressed(CF_KEY_SPACE)) s_presses++; | ||
|
|
||
| cf_draw_circle_fill2(cf_v2(cosf(s_elapsed * 2.0f) * 120.0f, sinf(s_elapsed * 2.0f) * 120.0f), 16.0f); | ||
|
|
||
| char buf[64]; | ||
| snprintf(buf, sizeof(buf), "elapsed %.2fs", s_elapsed); | ||
| cf_draw_text(buf, cf_v2(-60, 30), -1); | ||
| snprintf(buf, sizeof(buf), "space pressed %d", s_presses); | ||
| cf_draw_text(buf, cf_v2(-60, 10), -1); | ||
|
|
||
| cf_app_draw_onto_screen(true); | ||
| } | ||
|
|
||
| void cf_main_quit(void) | ||
| { | ||
| // Free your own resources here -- cf_destroy_app runs for you right afterwards. | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the limitation the PR description already calls out: "cf_destroy_app frees the event buffer and destroys buffered_events_mutex while another thread could still be inside cf_app_process_event ... Narrow (mobile lifecycle events only) but real." Agreed it's real -- a proper fix needs a shutdown protocol (e.g. an in-flight counter the destroy path waits to drain, or moving the mutex/queue to a lifetime outside CF_App) that's a bigger design change than this PR's scope. Leaving it as a disclosed limitation for now rather than bolting on synchronization here; open to doing it as a follow-up if you'd rather it land before merge.