From 853c28e6247a9981b466a9085e118c048a4c49ed Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 3 Nov 2023 11:39:53 -0400 Subject: [PATCH] docs: Added first draft of README-main-functions.md Reference PR #8247. --- docs/README-main-functions.md | 213 ++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 docs/README-main-functions.md diff --git a/docs/README-main-functions.md b/docs/README-main-functions.md new file mode 100644 index 000000000..386514d3f --- /dev/null +++ b/docs/README-main-functions.md @@ -0,0 +1,213 @@ +# Where an SDL program starts running. + +## History + +SDL has a long, complicated history with starting a program. + +In most of the civilized world, an application starts in a C-callable +function named "main". You probably learned it a long time ago: + +```c +int main(int argc, char **argv) +{ + printf("Hello world!\n"); + return 0; +} +``` + +But not all platforms work like this. Windows apps might want a different +function named "WinMain", for example, so SDL set out to paper over this +difference. + +Generally how this would work is: your app would always use the "standard" +`main(argc, argv)` function as its entry point, and `#include` the proper +SDL header before that, which did some macro magic. On platforms that used +a standard `main`, it would do nothing and what you saw was what you got. + +But those other platforms! If they needed something that _wasn't_ `main`, +SDL's macro magic would quietly rename your function to `SDL_main`, and +provide its own entry point that called it. Your app was none the wiser and +your code worked everywhere without changes. + +In SDL1, you linked with a static library that had startup code that _had_ to +run before you hit SDL_main(). For example, on macOS it would do various +magic to the process to make sure it was in the right state. Windows would +register win32 window classes and such. Things would break if you tried to +circumvent this, and you were in for a lot of trouble if you tried to use +SDL on a platform that needed this when you didn't control the entry point +(for example, as a plugin, or an SDL binding in a scripting language). + +In SDL2, the necessary support code moved into the main library, and the tiny +static library _only_ handled the basics of getting from the platform's real +entry point (like WinMain) to SDL_main; if the real entry was _already_ +standard main, the static library and macro magic was unnecessary. The goal +was to make it so you didn't have to change _your_ code to work on multiple +platforms and remove the original limitations. + +In SDL3, we've taken this much, much further. + +## The main entry point in SDL3 + +SDL3 still has the same macro tricks, but the static library is gone. Now it's +supplied by a "single-header library," which is to say you +`#include ` and that header will insert a small amount of +code into the source file that included it, so you no longer have to worry +about linking against an extra library that you might need on some platforms. +You just build your app and it works. + +You should _only_ include SDL_main.h from one file (the umbrella header, +SDL.h, does _not_ include it), and know that it will `#define main` to +something else, so if you use this symbol elsewhere as a variable name, etc, +it can cause you unexpected problems. + +SDL_main.h will also include platform-specific code (WinMain or whatnot) that +calls your _actual_ main function. This is compiled directly into your +program. + +If for some reason you need to include SDL_main.h in a file but also _don't_ +want it to generate this platform-specific code, you should define a special +macro before includin the header: + + +```c +#define SDL_MAIN_NOIMPL +``` + +If you are moving from SDL2, remove any references to the SDLmain static +library from your build system, and you should be done. Things should work as +they always have. + +If you have never controlled your process's entry point (you are using SDL +as a module from a general-purpose scripting language interpreter, or you're +SDL in a plugin for some otherwise-unrelated app), then there is nothing +required of you here; there is no startup code in SDL's entry point code that +is required, so using SDL_main.h is completely optional. Just start using +the SDL API when you are ready. + + +## Main callbacks in SDL3 + +There is a second option in SDL3 for how to structure your program. This is +completly optional and you can ignore it if you're happy using a standard +"main" function. + +Some platforms would rather your program operate in chunks. Most of the time, +games tend to look like this at the highest level: + +```c +int main(int argc, char **argv) +{ + initialize(); + while (keep_running()) { + handle_new_events(); + do_one_frame_of_stuff(); + } + deinitialize(); +} +``` + +There are platforms that would rather be in charge of that `while` loop: +iOS would rather you return from main() immediately and then it will let you +know that it's time to update and draw the next frame of video. Emscripten +(programs that run on a web page) absolutely requires this to function at all. +Video targets like Wayland can notify the app when to draw a new frame, to +save battery life and cooperate with the compositor more closely. + +In most cases, you can add special-case code to your program to deal with this +on different platforms, but SDL3 offers a system to handle transparently on +the app's behalf. + +To use this, you have to redesign the highest level of your app a little. Once +you do, it'll work on all supported SDL platforms without problems and +`#ifdef`s in your code. + +Instead of providing a "main" function, under this system, you would provide +several functions that SDL will call as appropriate. + +Using the callback entry points works on every platform, because on platforms +that don't require them, we can fake them with a simple loop in an internal +implementation of the usual SDL_main. + +The primary way we expect people to write SDL apps is still with SDL_main, and +this is not intended to replace it. If the app chooses to use this, it just +removes some platform-specific details they might have to otherwise manage, +and maybe removes a barrier to entry on some future platform. And you might +find you enjoy structuring your program like this more! + + +## How to use main callbacks in SDL3 + +To enable the callback entry points, you include SDL_main with an extra define +from a single source file in your project: + +```c +#define SDL_MAIN_USE_CALLBACKS +#include +``` + +Once you do this, you do not include a "main" function at all (and if you do, +the app will likely fail to link). Instead, you provide the following +functions: + +First: + +```c +int SDL_AppInit(int argc, char **argv); +``` + +This will be called _once_ before anything else. argc/argv work like they +always do. If this returns 0, the app runs. If it returns < 0, the app calls +SDL_AppQuit and terminates with an exit code that reports an error to the +platform. If it returns > 0, the app calls SDL_AppQuit and terminates with +an exit code that reports success to the platform. This function should not +go into an infinite mainloop; it should do any one-time startup it requires +and then return. + +Then: + +```c +int SDL_AppIterate(void); +``` + +This is called over and over, possibly at the refresh rate of the display or +some other metric that the platform dictates. This is where the heart of your +app runs. It should return as quickly as reasonably possible, but it's not a +"run one memcpy and that's all the time you have" sort of thing. The app +should do any game updates, and render a frame of video. If it returns < 0, +SDL will call SDL_AppQuit and terminate the process with an exit code that +reports an error to the platform. If it returns > 0, the app calls +SDL_AppQuit and terminates with an exit code that reports success to the +platform. If it returns 0, then SDL_AppIterate will be called again at some +regular frequency. The platform may choose to run this more or less (perhaps +less in the background, etc), or it might just call this function in a loop +as fast as possible. You do not check the event queue in this function +(SDL_AppEvent exists for that). + +Next: + +```c +int SDL_AppEvent(const SDL_Event *event); +``` + +This will be called once for each event pushed into the SDL queue. This may be +called from any thread, and possibly in parallel to SDL_AppIterate. The fields +in `event` do not need to be free'd (as you would normally need to do for +SDL_EVENT_DROP_FILE, etc), and your app should not call SDL_PollEvent, +SDL_PumpEvent, etc, as SDL will manage this for you. Return values are the +same as from SDL_AppIterate(), so you can terminate in response to +SDL_EVENT_QUIT, etc. + + +Finally: + +```c +void SDL_AppQuit(void); +``` + +This is called once before terminating the app--assuming the app isn't being +forcibly killed or crashed--as a last chance to clean up. After this returns, +SDL will call SDL_Quit so the app doesn't have to (but it's safe for the app +to call it, too). Process termination proceeds as if the app returned normally +from main(), so atexit handles will run, if your platform supports that. + +