main: Added _optional_ callback entry points.

This lets apps optionally have a handful of callbacks for their entry points instead of a single main function. If used, the actual main/SDL_main/whatever entry point will be implemented in the single-header library SDL_main.h and the app will implement four separate functions:

First:

    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:

     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:

    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:

    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.

The app does not implement SDL_main if using this. To turn this on, define SDL_MAIN_USE_CALLBACKS before including SDL_main.h. Defines like SDL_MAIN_HANDLED and SDL_MAIN_NOIMPL are also respected for callbacks, if the app wants to do some sort of magic main implementation thing.

In theory, on most platforms these can be implemented in the app itself, but this saves some #ifdefs in the app and lets everyone struggle less against some platforms, and might be more efficient in the long run, too.

On some platforms, it's possible this is the only reasonable way to go, but we haven't actually hit one that 100% requires it yet (but we will, if we want to write a RetroArch backend, for example).

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 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.

Fixes #6785.
Reference PR #8247.
This commit is contained in:
Ryan C. Gordon 2023-11-01 18:40:41 -04:00 committed by GitHub
parent 9323417e9c
commit 9c664b0062
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1155 additions and 529 deletions

View File

@ -475,6 +475,7 @@ sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/hidapi/*.c"
"${SDL3_SOURCE_DIR}/src/libm/*.c"
"${SDL3_SOURCE_DIR}/src/locale/*.c"
"${SDL3_SOURCE_DIR}/src/main/*.c"
"${SDL3_SOURCE_DIR}/src/misc/*.c"
"${SDL3_SOURCE_DIR}/src/power/*.c"
"${SDL3_SOURCE_DIR}/src/render/*.c"
@ -1383,6 +1384,9 @@ elseif(EMSCRIPTEN)
# project. Uncomment at will for verbose cross-compiling -I/../ path info.
sdl_compile_options(PRIVATE "-Wno-warn-absolute-paths")
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/emscripten/*.c")
set(HAVE_SDL_MAIN_CALLBACKS TRUE)
if(SDL_MISC)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/misc/emscripten/*.c")
set(HAVE_SDL_MISC TRUE)
@ -2033,6 +2037,8 @@ elseif(WINDOWS)
elseif(APPLE)
# TODO: rework this all for proper macOS, iOS and Darwin support
# !!! FIXME: all the `if(IOS OR TVOS OR VISIONOS)` checks should get merged into one variable, so we're ready for the next platform (or just WatchOS).
# We always need these libs on macOS at the moment.
# !!! FIXME: we need Carbon for some very old API calls in
# !!! FIXME: src/video/cocoa/SDL_cocoakeyboard.c, but we should figure out
@ -2044,6 +2050,12 @@ elseif(APPLE)
set(SDL_FRAMEWORK_FOUNDATION 1)
set(SDL_FRAMEWORK_COREVIDEO 1)
# iOS can use a CADisplayLink for main callbacks. macOS just uses the generic one atm.
if(IOS OR TVOS OR VISIONOS)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/ios/*.m")
set(HAVE_SDL_MAIN_CALLBACKS TRUE)
endif()
# Requires the darwin file implementation
if(SDL_FILE)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/file/cocoa/*.m")
@ -2803,6 +2815,11 @@ if(NOT HAVE_SDL_TIMERS)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/timer/dummy/*.c")
endif()
# Most platforms use this.
if(NOT HAVE_SDL_MAIN_CALLBACKS)
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/main/generic/*.c")
endif()
# config variables may contain generator expression, so we need to generate SDL_build_config.h in 2 steps:
# 1. replace all `#cmakedefine`'s and `@abc@`
configure_file("${SDL3_SOURCE_DIR}/include/build_config/SDL_build_config.h.cmake"

View File

@ -418,6 +418,7 @@
<ClInclude Include="..\..\src\libm\math_libm.h" />
<ClInclude Include="..\..\src\libm\math_private.h" />
<ClInclude Include="..\..\src\locale\SDL_syslocale.h" />
<ClInclude Include="..\..\src\main\SDL_main_callbacks.h" />
<ClInclude Include="..\..\src\misc\SDL_sysurl.h" />
<ClInclude Include="..\..\src\power\SDL_syspower.h" />
<ClInclude Include="..\..\src\render\direct3d11\SDL_shaders_d3d11.h" />
@ -468,6 +469,8 @@
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|Gaming.Desktop.x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Desktop.x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
</ClCompile>
<ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c" />
<ClCompile Include="..\..\src\main\SDL_main_callbacks.c" />
<ClCompile Include="..\..\src\SDL_guid.c" />
<ClInclude Include="..\..\src\SDL_hashtable.h" />
<ClInclude Include="..\..\src\SDL_hints_c.h" />

View File

@ -175,6 +175,12 @@
<Filter Include="video\gdk">
<UniqueIdentifier>{3ad16a8a-0ed8-439c-a771-383af2e2867f}</UniqueIdentifier>
</Filter>
<Filter Include="main">
<UniqueIdentifier>{00002ddb6c5ea921181bf32d50e40000}</UniqueIdentifier>
</Filter>
<Filter Include="main\generic">
<UniqueIdentifier>{00000a808f8ba6b489985f82a4e80000}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\include\SDL3\SDL_begin_code.h">
@ -399,6 +405,9 @@
<ClInclude Include="..\..\include\SDL3\SDL_vulkan.h">
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\src\main\SDL_main_callbacks.h">
<Filter>main</Filter>
</ClInclude>
<ClInclude Include="..\..\src\SDL_error_c.h" />
<ClInclude Include="..\..\src\SDL_hashtable.h" />
<ClInclude Include="..\..\src\SDL_list.h" />
@ -846,6 +855,12 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />
<ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c">
<Filter>main\generic</Filter>
</ClCompile>
<ClCompile Include="..\..\src\main\SDL_main_callbacks.c">
<Filter>main</Filter>
</ClCompile>
<ClCompile Include="..\..\src\SDL.c" />
<ClCompile Include="..\..\src\SDL_assert.c" />
<ClCompile Include="..\..\src\SDL_error.c" />

View File

@ -133,6 +133,7 @@
<ClInclude Include="..\src\joystick\windows\SDL_windowsjoystick_c.h" />
<ClInclude Include="..\src\joystick\windows\SDL_xinputjoystick_c.h" />
<ClInclude Include="..\src\locale\SDL_syslocale.h" />
<ClInclude Include="..\src\main\SDL_main_callbacks.h" />
<ClInclude Include="..\src\render\direct3d11\SDL_render_winrt.h" />
<ClInclude Include="..\src\render\direct3d11\SDL_shaders_d3d11.h" />
<ClInclude Include="..\src\render\opengles2\SDL_gles2funcs.h" />
@ -337,6 +338,8 @@
<ClCompile Include="..\src\loadso\windows\SDL_sysloadso.c" />
<ClCompile Include="..\src\locale\SDL_locale.c" />
<ClCompile Include="..\src\locale\winrt\SDL_syslocale.c" />
<ClCompile Include="..\src\main\generic\SDL_sysmain_callbacks.c" />
<ClCompile Include="..\src\main\SDL_main_callbacks.c" />
<ClCompile Include="..\src\misc\SDL_url.c" />
<ClCompile Include="..\src\misc\winrt\SDL_sysurl.cpp">
<CompileAsWinRT>true</CompileAsWinRT>

View File

@ -7,6 +7,12 @@
<Filter Include="Source Files">
<UniqueIdentifier>{68e1b30b-19ed-4612-93e4-6260c5a979e5}</UniqueIdentifier>
</Filter>
<Filter Include="main">
<UniqueIdentifier>{00004a2523fc69c7128c60648c860000}</UniqueIdentifier>
</Filter>
<Filter Include="main\generic">
<UniqueIdentifier>{0000318d975e0a2867ab1d5727bf0000}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\SDL3\SDL_begin_code.h">
@ -270,6 +276,9 @@
<ClInclude Include="..\src\joystick\windows\SDL_xinputjoystick_c.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="..\src\main\SDL_main_callbacks.h">
<Filter>main</Filter>
</ClInclude>
<ClInclude Include="..\src\render\direct3d11\SDL_render_winrt.h">
<Filter>Source Files</Filter>
</ClInclude>
@ -588,6 +597,12 @@
<ClCompile Include="..\src\loadso\windows\SDL_sysloadso.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\main\generic\SDL_sysmain_callbacks.c">
<Filter>main\generic</Filter>
</ClCompile>
<ClCompile Include="..\src\main\SDL_main_callbacks.c">
<Filter>main</Filter>
</ClCompile>
<ClCompile Include="..\src\power\SDL_power.c">
<Filter>Source Files</Filter>
</ClCompile>

View File

@ -368,6 +368,7 @@
<ClInclude Include="..\..\src\libm\math_libm.h" />
<ClInclude Include="..\..\src\libm\math_private.h" />
<ClInclude Include="..\..\src\locale\SDL_syslocale.h" />
<ClInclude Include="..\..\src\main\SDL_main_callbacks.h" />
<ClInclude Include="..\..\src\misc\SDL_sysurl.h" />
<ClInclude Include="..\..\src\power\SDL_syspower.h" />
<ClInclude Include="..\..\src\render\direct3d11\SDL_shaders_d3d11.h" />
@ -397,6 +398,8 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c" />
<ClCompile Include="..\..\src\main\SDL_main_callbacks.c" />
<ClCompile Include="..\..\src\SDL_guid.c" />
<ClInclude Include="..\..\src\SDL_hashtable.h" />
<ClInclude Include="..\..\src\SDL_hints_c.h" />

View File

@ -169,6 +169,12 @@
<Filter Include="render\direct3d12">
<UniqueIdentifier>{f48c2b17-1bee-4fec-a7c8-24cf619abe08}</UniqueIdentifier>
</Filter>
<Filter Include="main">
<UniqueIdentifier>{00001967ea2801028a046a722a070000}</UniqueIdentifier>
</Filter>
<Filter Include="main\generic">
<UniqueIdentifier>{0000ddc7911820dbe64274d3654f0000}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\include\SDL3\SDL_begin_code.h">
@ -390,6 +396,9 @@
<ClInclude Include="..\..\include\SDL3\SDL_vulkan.h">
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\src\main\SDL_main_callbacks.h">
<Filter>main</Filter>
</ClInclude>
<ClInclude Include="..\..\src\SDL_error_c.h" />
<ClInclude Include="..\..\src\SDL_hashtable.h" />
<ClInclude Include="..\..\src\SDL_list.h" />
@ -828,6 +837,12 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />
<ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c">
<Filter>main\generic</Filter>
</ClCompile>
<ClCompile Include="..\..\src\main\SDL_main_callbacks.c">
<Filter>main</Filter>
</ClCompile>
<ClCompile Include="..\..\src\SDL.c" />
<ClCompile Include="..\..\src\SDL_assert.c" />
<ClCompile Include="..\..\src\SDL_error.c" />

View File

@ -34,6 +34,7 @@
/* Begin PBXBuildFile section */
000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */ = {isa = PBXBuildFile; fileRef = 000078E1881E857EBB6C0000 /* SDL_hashtable.c */; };
0000A4DA2F45A31DC4F00000 /* SDL_sysmain_callbacks.m in Sources */ = {isa = PBXBuildFile; fileRef = 0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); };
007317A40858DECD00B2BC32 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179D0858DECD00B2BC32 /* Cocoa.framework */; platformFilters = (macos, ); };
007317A60858DECD00B2BC32 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0073179F0858DECD00B2BC32 /* IOKit.framework */; platformFilters = (ios, maccatalyst, macos, ); };
00CFA89D106B4BA100758660 /* ForceFeedback.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CFA89C106B4BA100758660 /* ForceFeedback.framework */; platformFilters = (macos, ); };
@ -476,6 +477,8 @@
F3F7D9E12933074E00816151 /* SDL_begin_code.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8E72933074E00816151 /* SDL_begin_code.h */; settings = {ATTRIBUTES = (Public, ); }; };
F3F7D9E52933074E00816151 /* SDL_system.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7D8E82933074E00816151 /* SDL_system.h */; settings = {ATTRIBUTES = (Public, ); }; };
FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); };
000028F8113A53F4333E0000 /* SDL_main_callbacks.c in Sources */ = {isa = PBXBuildFile; fileRef = 00009366FB9FBBD54C390000 /* SDL_main_callbacks.c */; };
0000C3B22D46279F99170000 /* SDL_main_callbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 00003260407E1002EAC10000 /* SDL_main_callbacks.h */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -504,6 +507,7 @@
/* Begin PBXFileReference section */
000078E1881E857EBB6C0000 /* SDL_hashtable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hashtable.c; sourceTree = "<group>"; };
0000B6ADCD88CAD6610F0000 /* SDL_hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hashtable.h; sourceTree = "<group>"; };
0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_sysmain_callbacks.m; sourceTree = "<group>"; };
0073179D0858DECD00B2BC32 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
0073179F0858DECD00B2BC32 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
007317C10858E15000B2BC32 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
@ -977,6 +981,8 @@
F59C710600D5CB5801000001 /* SDL.info */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = SDL.info; sourceTree = "<group>"; };
F5A2EF3900C6A39A01000001 /* BUGS.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; name = BUGS.txt; path = ../../BUGS.txt; sourceTree = SOURCE_ROOT; };
FA73671C19A540EF004122E4 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
00009366FB9FBBD54C390000 /* SDL_main_callbacks.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_main_callbacks.c; path = SDL_main_callbacks.c; sourceTree = "<group>"; };
00003260407E1002EAC10000 /* SDL_main_callbacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_main_callbacks.h; path = SDL_main_callbacks.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -1002,6 +1008,24 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
000082EF09C89B62BD840000 /* main */ = {
isa = PBXGroup;
children = (
00008B5A0CB83D2069E80000 /* ios */,
00009366FB9FBBD54C390000 /* SDL_main_callbacks.c */,
00003260407E1002EAC10000 /* SDL_main_callbacks.h */,
);
path = main;
sourceTree = "<group>";
};
00008B5A0CB83D2069E80000 /* ios */ = {
isa = PBXGroup;
children = (
0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */,
);
path = ios;
sourceTree = "<group>";
};
0153844A006D81B07F000001 /* Public Headers */ = {
isa = PBXGroup;
children = (
@ -1122,6 +1146,7 @@
A7D8A91123E2514000DCD162 /* libm */,
A7D8A85D23E2513F00DCD162 /* loadso */,
566E26CB246274AE00718109 /* locale */,
000082EF09C89B62BD840000 /* main */,
5616CA47252BB278005D5928 /* misc */,
A7D8A7DF23E2513F00DCD162 /* power */,
A7D8A8DA23E2514000DCD162 /* render */,
@ -2614,6 +2639,8 @@
A7D8AB6123E2514100DCD162 /* SDL_offscreenwindow.c in Sources */,
566E26D8246274CC00718109 /* SDL_locale.c in Sources */,
000040E76FDC6AE48CBF0000 /* SDL_hashtable.c in Sources */,
0000A4DA2F45A31DC4F00000 /* SDL_sysmain_callbacks.m in Sources */,
000028F8113A53F4333E0000 /* SDL_main_callbacks.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -2531,6 +2531,22 @@ extern "C" {
*/
#define SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES "SDL_AUDIO_DEVICE_SAMPLE_FRAMES"
/**
* Request SDL_AppIterate() be called at a specific rate.
*
* This number is in Hz, so "60" means try to iterate 60 times per second.
*
* 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
* changed at any time.
*
* This defaults to 60, and specifying NULL for the hint's value will restore
* the default.
*/
#define SDL_HINT_MAIN_CALLBACK_RATE "SDL_MAIN_CALLBACK_RATE"
/**
* \brief An enumeration of hint priorities
*/

View File

@ -140,7 +140,7 @@
* \endcode
*/
#if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE)
#if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE) || defined(SDL_MAIN_USE_CALLBACKS)
#define main SDL_main
#endif
@ -149,11 +149,199 @@
extern "C" {
#endif
union SDL_Event;
typedef int (SDLCALL *SDL_AppInit_func)(int argc, char *argv[]);
typedef int (SDLCALL *SDL_AppIterate_func)(void);
typedef int (SDLCALL *SDL_AppEvent_func)(const union SDL_Event *event);
typedef void (SDLCALL *SDL_AppQuit_func)(void);
/**
* You can (optionally!) define SDL_MAIN_USE_CALLBACKS before including
* SDL_main.h, and then your application will _not_ have a standard
* "main" entry point. Instead, it will operate as a collection of
* functions that are called as necessary by the system. On some
* platforms, this is just a layer where SDL drives your program
* instead of your program driving SDL, on other platforms this might
* hook into the OS to manage the lifecycle. Programs on most platforms
* can use whichever approach they prefer, but the decision boils down
* to:
*
* - Using a standard "main" function: this works like it always has for
* the past 50+ years in C programming, and your app is in control.
* - Using the callback functions: this might clean up some code,
* avoid some #ifdef blocks in your program for some platforms, be more
* resource-friendly to the system, and possibly be the primary way to
* access some future platforms (but none require this at the moment).
*
* This is up to the app; both approaches are considered valid and supported
* ways to write SDL apps.
*
* If using the callbacks, don't define a "main" function. Instead, implement
* the functions listed below in your program.
*/
#ifdef SDL_MAIN_USE_CALLBACKS
/**
* App-implemented initial entry point for SDL_MAIN_USE_CALLBACKS apps.
*
* Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If
* using a standard "main" function, you should not supply this.
*
* This function is called by SDL once, at startup. The function should
* initialize whatever is necessary, possibly create windows and open
* audio devices, etc. The `argc` and `argv` parameters work like they would
* with a standard "main" function.
*
* This function should not go into an infinite mainloop; it should do any
* one-time setup it requires and then return.
*
* If this function returns 0, the app will proceed to normal operation,
* and will begin receiving repeated calls to SDL_AppIterate and SDL_AppEvent
* for the life of the program. If this function 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 SDL calls SDL_AppQuit
* and terminates with an exit code that reports success to the platform.
*
* \param argc The standard ANSI C main's argc; number of elements in `argv`
* \param argv The standard ANSI C main's argv; array of command line arguments.
* \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue.
*
* \threadsafety This function is not thread safe.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_AppIterate
* \sa SDL_AppEvent
* \sa SDL_AppQuit
*/
extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppInit(int argc, char *argv[]);
/**
* App-implemented iteration entry point for SDL_MAIN_USE_CALLBACKS apps.
*
* Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If
* using a standard "main" function, you should not supply this.
*
* This function is called repeatedly by SDL after SDL_AppInit returns 0.
* The function should operate as a single iteration the program's primary
* loop; it should update whatever state it needs and draw a new frame of
* video, usually.
*
* On some platforms, this function will be called at the refresh rate of
* the display (which might change during the life of your app!). There are
* no promises made about what frequency this function might run at. You
* should use SDL's timer functions if you need to see how much time has
* passed since the last iteration.
*
* There is no need to process the SDL event queue during this function;
* SDL will send events as they arrive in SDL_AppEvent, and in most cases
* the event queue will be empty when this function runs anyhow.
*
* This function should not go into an infinite mainloop; it should do one
* iteration of whatever the program does and return.
*
* If this function returns 0, the app will continue normal operation,
* receiving repeated calls to SDL_AppIterate and SDL_AppEvent for the life
* of the program. If this function 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 SDL calls SDL_AppQuit and terminates with
* an exit code that reports success to the platform.
*
* \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue.
*
* \threadsafety This function is not thread safe.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_AppInit
* \sa SDL_AppEvent
* \sa SDL_AppQuit
*/
extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppIterate(void);
/**
* App-implemented event entry point for SDL_MAIN_USE_CALLBACKS apps.
*
* Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If
* using a standard "main" function, you should not supply this.
*
* This function is called as needed by SDL after SDL_AppInit returns 0;
* It is called once for each new event.
*
* There is (currently) no guarantee about what thread this will be called
* from; whatever thread pushes an event onto SDL's queue will trigger this
* function. 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, but be careful if you have a thread that
* explicitly calls SDL_PushEvent.
*
* Events sent to this function are not owned by the app; if you need to
* save the data, you should copy it.
*
* You do not need to free event data (such as the `file` string in
* SDL_EVENT_DROP_FILE), as SDL will free it once this function returns.
* Note that this is different than one might expect when using a standard
* "main" function!
*
* This function should not go into an infinite mainloop; it should handle
* the provided event appropriately and return.
*
* If this function returns 0, the app will continue normal operation,
* receiving repeated calls to SDL_AppIterate and SDL_AppEvent for the life
* of the program. If this function 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 SDL calls SDL_AppQuit and terminates with
* an exit code that reports success to the platform.
*
* \returns -1 to terminate with an error, 1 to terminate with success, 0 to continue.
*
* \threadsafety This function is not thread safe.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_AppInit
* \sa SDL_AppIterate
* \sa SDL_AppQuit
*/
extern SDLMAIN_DECLSPEC int SDLCALL SDL_AppEvent(const SDL_Event *event);
/**
* App-implemented deinit entry point for SDL_MAIN_USE_CALLBACKS apps.
*
* Apps implement this function when using SDL_MAIN_USE_CALLBACKS. If
* using a standard "main" function, you should not supply this.
*
* This function is called once by SDL before terminating the program.
*
* This function will be called no matter what, even if SDL_AppInit
* requests termination.
*
* This function should not go into an infinite mainloop; it should
* deinitialize any resources necessary, perform whatever shutdown
* activities, and return.
*
* You do not need to call SDL_Quit() in this function, as SDL will call
* it after this function returns and before the process terminates, but
* it is safe to do so.
*
* \threadsafety This function is not thread safe.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_AppInit
* \sa SDL_AppIterate
* \sa SDL_AppEvent
*/
extern SDLMAIN_DECLSPEC void SDLCALL SDL_AppQuit(void);
#endif /* SDL_MAIN_USE_CALLBACKS */
/**
* The prototype for the application's main() function
*/
typedef int (*SDL_main_func)(int argc, char *argv[]);
extern SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]);
typedef int (SDLCALL *SDL_main_func)(int argc, char *argv[]);
extern SDLMAIN_DECLSPEC int SDLCALL SDL_main(int argc, char *argv[]);
/**
@ -198,6 +386,33 @@ extern DECLSPEC void SDLCALL SDL_SetMainReady(void);
*/
extern DECLSPEC int SDLCALL SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved);
/**
* An entry point for SDL's use in SDL_MAIN_USE_CALLBACKS.
*
* Generally, you should not call this function directly. This only exists
* to hand off work into SDL as soon as possible, where it has a lot more
* control and functionality available, and make the inline code in
* SDL_main.h as small as possible.
*
* Not all platforms use this, it's actual use is hidden in a magic
* header-only library, and you should not call this directly unless you
* _really_ know what you're doing.
*
* \param argc standard Unix main argc
* \param argv standard Unix main argv
* \param appinit The application's SDL_AppInit function
* \param appiter The application's SDL_AppIterate function
* \param appevent The application's SDL_AppEvent function
* \param appquit The application's SDL_AppQuit function
* \returns standard Unix main return value
*
* \threadsafety It is not safe to call this anywhere except as the only function call in SDL_main.
*
* \since This function is available since SDL 3.0.0.
*/
extern DECLSPEC int SDLCALL SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit);
#if defined(__WIN32__) || defined(__GDK__)
/**
@ -282,7 +497,8 @@ extern DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void);
#if !defined(SDL_MAIN_HANDLED) && !defined(SDL_MAIN_NOIMPL)
/* include header-only SDL_main implementations */
#if defined(__WIN32__) || defined(__GDK__) || defined(__IOS__) || defined(__TVOS__) \
#if defined(SDL_MAIN_USE_CALLBACKS) \
|| defined(__WIN32__) || defined(__GDK__) || defined(__IOS__) || defined(__TVOS__) \
|| defined(__3DS__) || defined(__NGAGE__) || defined(__PS2__) || defined(__PSP__)
/* platforms which main (-equivalent) can be implemented in plain C */

View File

@ -41,6 +41,30 @@
# undef main
#endif /* main */
#ifdef SDL_MAIN_USE_CALLBACKS
#if 0
/* currently there are no platforms that _need_ a magic entry point here
for callbacks, but if one shows up, implement it here. */
#else /* use a standard SDL_main, which the app SHOULD NOT ALSO SUPPLY. */
/* this define makes the normal SDL_main entry point stuff work...we just provide SDL_main() instead of the app. */
#define SDL_MAIN_CALLBACK_STANDARD 1
int SDL_main(int argc, char **argv)
{
return SDL_EnterAppMainCallbacks(argc, argv, SDL_AppInit, SDL_AppIterate, SDL_AppEvent, SDL_AppQuit);
}
#endif /* platform-specific tests */
#endif /* SDL_MAIN_USE_CALLBACKS */
/* set up the usual SDL_main stuff if we're not using callbacks or if we are but need the normal entry point. */
#if !defined(SDL_MAIN_USE_CALLBACKS) || defined(SDL_MAIN_CALLBACK_STANDARD)
#if defined(__WIN32__) || defined(__GDK__)
/* these defines/typedefs are needed for the WinMain() definition */
@ -193,6 +217,8 @@ int main(int argc, char *argv[])
#endif /* __WIN32__ etc */
#endif /* !defined(SDL_MAIN_USE_CALLBACKS) || defined(SDL_MAIN_CALLBACK_STANDARD) */
/* rename users main() function to SDL_main() so it can be called from the wrappers above */
#define main SDL_main

View File

@ -201,15 +201,27 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state);
SDL_bool SDLTest_CommonDefaultArgs(SDLTest_CommonState *state, const int argc, char **argv);
/**
* \brief Common event handler for test windows.
* Common event handler for test windows if you use a standard SDL_main.
*
* This will free data from the event, like the string in a drop event!
*
* \param state The common state used to create test window.
* \param event The event to handle.
* \param done Flag indicating we are done.
*
*/
void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done);
/**
* Common event handler for test windows if you use SDL_AppEvent.
*
* This does _not_ free anything in `event`.
*
* \param state The common state used to create test window.
* \param event The event to handle.
* \returns Value suitable for returning from SDL_AppEvent().
*/
int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event *event);
/**
* \brief Close test window.
*

View File

@ -920,6 +920,7 @@ SDL3_0.0.0 {
SDL_GetSurfaceProperties;
SDL_GetWindowProperties;
SDL_ClearProperty;
SDL_EnterAppMainCallbacks;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@ -945,3 +945,4 @@
#define SDL_GetSurfaceProperties SDL_GetSurfaceProperties_REAL
#define SDL_GetWindowProperties SDL_GetWindowProperties_REAL
#define SDL_ClearProperty SDL_ClearProperty_REAL
#define SDL_EnterAppMainCallbacks SDL_EnterAppMainCallbacks_REAL

View File

@ -977,3 +977,4 @@ SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetSensorProperties,(SDL_Sensor *a),(a),ret
SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetSurfaceProperties,(SDL_Surface *a),(a),return)
SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetWindowProperties,(SDL_Window *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_ClearProperty,(SDL_PropertiesID a, const char *b),(a,b),return)
SDL_DYNAPI_PROC(int,SDL_EnterAppMainCallbacks,(int a, char *b[], SDL_AppInit_func c, SDL_AppIterate_func d, SDL_AppEvent_func e, SDL_AppQuit_func f),(a,b,c,d,e,f),return)

View File

@ -0,0 +1,114 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_main_callbacks.h"
static SDL_AppEvent_func SDL_main_event_callback;
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)
{
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));
}
return 0;
}
int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
{
SDL_main_iteration_callback = appiter;
SDL_main_event_callback = appevent;
SDL_main_quit_callback = appquit;
SDL_AtomicSet(&apprc, 0);
const int rc = appinit(argc, argv);
if (SDL_AtomicCAS(&apprc, 0, rc) && (rc == 0)) { // bounce if SDL_AppInit already said abort, otherwise...
// make sure we definitely have events initialized, even if the app didn't do it.
if (SDL_InitSubSystem(SDL_INIT_EVENTS) == -1) {
SDL_AtomicSet(&apprc, -1);
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++;
}
SDL_AddEventWatch(EventWatcher, NULL); // !!! FIXME: this should really return an error.
for (int i = 0; i < total_pending_events; i++) {
SDL_PushEvent(&pending_events[i]);
}
SDL_free(pending_events);
}
return SDL_AtomicGet(&apprc);
}
int SDL_IterateMainCallbacks(void)
{
SDL_Event event;
SDL_PumpEvents();
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) == 1) {
// just empty the queue, EventWatcher sends the events to the app.
switch (event.type) {
case SDL_EVENT_DROP_FILE:
case SDL_EVENT_DROP_TEXT:
SDL_free(event.drop.file);
break;
}
}
int rc = SDL_main_iteration_callback();
if (!SDL_AtomicCAS(&apprc, 0, rc)) {
rc = SDL_AtomicGet(&apprc); // something else already set a quit result, keep that.
}
return rc;
}
void SDL_QuitMainCallbacks(void)
{
SDL_DelEventWatch(EventWatcher, 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.
//SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_Quit();
}

View File

@ -0,0 +1,31 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_main_callbacks_h_
#define SDL_main_callbacks_h_
int SDL_InitMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func _appiter, SDL_AppEvent_func _appevent, SDL_AppQuit_func _appquit);
int SDL_IterateMainCallbacks(void);
void SDL_QuitMainCallbacks(void);
#endif // SDL_main_callbacks_h_

View File

@ -0,0 +1,47 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../SDL_main_callbacks.h"
#include <emscripten.h>
static void EmscriptenInternalMainloop(void)
{
const int rc = SDL_IterateMainCallbacks();
if (rc != 0) {
SDL_QuitMainCallbacks();
emscripten_cancel_main_loop(); // kill" the mainloop, so it stops calling back into it.
exit((rc < 0) ? 1 : 0); // hopefully this takes down everything else, too.
}
}
int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
{
const int rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit);
if (rc == 0) {
emscripten_set_main_loop(EmscriptenInternalMainloop, 0, 0); // run at refresh rate, don't throw an exception since we do an orderly return.
} else {
SDL_QuitMainCallbacks();
}
return (rc < 0) ? 1 : 0;
}

View File

@ -0,0 +1,80 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../SDL_main_callbacks.h"
#include "../../video/SDL_sysvideo.h"
static int callback_rate_increment = 0;
static void SDLCALL MainCallbackRateHintChanged(void *userdata, const char *name, const char *oldValue, const char *newValue)
{
const int callback_rate = newValue ? SDL_atoi(newValue) : 60;
if (callback_rate > 0) {
callback_rate_increment = ((Uint64) 1000000000) / ((Uint64) callback_rate);
} else {
callback_rate_increment = 0;
}
}
int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
{
int rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit);
SDL_AddHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL);
Uint64 next_iteration = callback_rate_increment ? (SDL_GetTicksNS() + callback_rate_increment) : 0;
while ((rc = SDL_IterateMainCallbacks()) == 0) {
// !!! FIXME: this can be made more complicated if we decide to
// !!! FIXME: optionally hand off callback responsibility to the
// !!! FIXME: video subsystem (for example, if Wayland has a
// !!! FIXME: protocol to drive an animation loop, maybe we hand
// !!! FIXME: off to them here if/when the video subsystem becomes
// !!! FIXME: initialized).
// !!! FIXME: maybe respect this hint even if there _is_ a window.
// if there's no window, try to run at about 60fps (or whatever rate
// the hint requested). This makes this not eat all the CPU in
// simple things like loopwave. If there's a window, we run as fast
// as possible, which means we'll clamp to vsync in common cases,
// and won't be restrained to vsync if the app is doing a benchmark
// or doesn't want to be, based on how they've set up that window.
if ((callback_rate_increment == 0) || SDL_HasWindows()) {
next_iteration = 0; // just clear the timer and run at the pace the video subsystem allows.
} else {
const Uint64 now = SDL_GetTicksNS();
if (next_iteration > now) { // Running faster than the limit, sleep a little.
SDL_DelayNS(next_iteration - now);
} else {
next_iteration = now; // running behind (or just lost the window)...reset the timer.
}
next_iteration += callback_rate_increment;
}
}
SDL_DelHintCallback(SDL_HINT_MAIN_CALLBACK_RATE, MainCallbackRateHintChanged, NULL);
SDL_QuitMainCallbacks();
return (rc < 0) ? 1 : 0;
}

View File

@ -0,0 +1,82 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../SDL_main_callbacks.h"
#ifdef __IOS__
#import <UIKit/UIKit.h>
@interface SDLIosMainCallbacksDisplayLink : NSObject
@property(nonatomic, retain) CADisplayLink *displayLink;
- (void)appIteration:(CADisplayLink *)sender;
- (instancetype)init:(SDL_AppIterate_func)_appiter quitfunc:(SDL_AppQuit_func)_appquit;
@end
static SDLIosMainCallbacksDisplayLink *globalDisplayLink;
@implementation SDLIosMainCallbacksDisplayLink
- (instancetype)init:(SDL_AppIterate_func)_appiter quitfunc:(SDL_AppQuit_func)_appquit;
{
if ((self = [super init])) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(appIteration:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
return self;
}
- (void)appIteration:(CADisplayLink *)sender
{
const int rc = SDL_IterateMainCallbacks();
if (rc != 0) {
[self.displayLink invalidate];
self.displayLink = nil;
globalDisplayLink = nil;
SDL_QuitMainCallbacks();
exit((rc < 0) ? 1 : 0);
}
}
@end
// SDL_RunApp will land in UIApplicationMain, which calls SDL_main from postFinishLaunch, which calls this.
// When we return from here, we're living in the RunLoop, and a CADisplayLink is firing regularly for us.
int SDL_EnterAppMainCallbacks(int argc, char* argv[], SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit)
{
const int rc = SDL_InitMainCallbacks(argc, argv, appinit, appiter, appevent, appquit);
if (rc == 0) {
globalDisplayLink = [[SDLIosMainCallbacksDisplayLink alloc] init:appiter quitfunc:appquit];
if (globalDisplayLink != nil) {
return 0; // this will fall all the way out of SDL_main, where UIApplicationMain will keep running the RunLoop.
}
}
// appinit requested quit, just bounce out now.
SDL_QuitMainCallbacks();
exit((rc < 0) ? 1 : 0);
return 1; // just in case.
}
#endif

View File

@ -1560,7 +1560,7 @@ static const char *GamepadButtonName(const SDL_GamepadButton button)
}
}
static void SDLTest_PrintEvent(SDL_Event *event)
static void SDLTest_PrintEvent(const SDL_Event *event)
{
switch (event->type) {
case SDL_EVENT_SYSTEM_THEME_CHANGED:
@ -2029,7 +2029,7 @@ static void FullscreenTo(SDLTest_CommonState *state, int index, int windowId)
SDL_free(displays);
}
void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done)
int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event *event)
{
int i;
@ -2408,20 +2408,27 @@ void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done
}
break;
case SDLK_ESCAPE:
*done = 1;
break;
return 1;
default:
break;
}
break;
}
case SDL_EVENT_QUIT:
*done = 1;
break;
return 1;
}
return 0; /* keep going */
}
void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done)
{
*done = SDLTest_CommonEventMainCallbacks(state, event) ? 1 : 0;
switch (event->type) {
case SDL_EVENT_DROP_FILE:
case SDL_EVENT_DROP_TEXT:
SDL_free(event->drop.file);
SDL_free(event->drop.file); // SDL frees these if you use SDL_AppEvent, not us, so explicitly handle it here.
break;
}
}

View File

@ -47,11 +47,16 @@ if(WINDOWS_STORE)
target_link_libraries(sdl_test_main_uwp PRIVATE SDL3::Headers)
target_compile_options(sdl_test_main_uwp PRIVATE "/ZW")
add_library(sdl_test_main_callbacks_uwp OBJECT main.cpp)
target_link_libraries(sdl_test_main_callbacks_uwp PRIVATE SDL3::Headers)
target_compile_options(sdl_test_main_callbacks_uwp PRIVATE "/ZW")
target_compile_definitions(sdl_test_main_callbacks_uwp PRIVATE "SDL_MAIN_USE_CALLBACKS")
set_source_files_properties(${RESOURCE_FILES} PROPERTIES VS_DEPLOYENT_LOCATION "Assets")
endif()
macro(add_sdl_test_executable TARGET)
cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;NO_C90" "" "NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;SOURCES" ${ARGN})
cmake_parse_arguments(AST "BUILD_DEPENDENT;NONINTERACTIVE;NEEDS_RESOURCES;TESTUTILS;NO_C90;MAIN_CALLBACKS" "" "NONINTERACTIVE_TIMEOUT;NONINTERACTIVE_ARGS;SOURCES" ${ARGN})
if(AST_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unknown argument(s): ${AST_UNPARSED_ARGUMENTS}")
endif()
@ -73,8 +78,14 @@ macro(add_sdl_test_executable TARGET)
TARGET "${TARGET}"
)
set_property(SOURCE "${uwp_bindir}/${TARGET}.appxmanifest" PROPERTY VS_DEPLOYMENT_CONTENT 1)
if(AST_MAIN_CALLBACKS)
list(APPEND EXTRA_SOURCES "$<TARGET_OBJECTS:sdl_test_main_callbacks_uwp>")
else()
list(APPEND EXTRA_SOURCES "$<TARGET_OBJECTS:sdl_test_main_uwp>")
endif()
list(APPEND EXTRA_SOURCES
"$<TARGET_OBJECTS:sdl_test_main_uwp>"
"${uwp_bindir}/${TARGET}.appxmanifest"
"uwp/logo-50x50.png"
"uwp/square-44x44.png"
@ -213,7 +224,7 @@ endif()
add_sdl_test_executable(checkkeys SOURCES checkkeys.c)
add_sdl_test_executable(checkkeysthreads SOURCES checkkeysthreads.c)
add_sdl_test_executable(loopwave NEEDS_RESOURCES TESTUTILS SOURCES loopwave.c)
add_sdl_test_executable(loopwave NEEDS_RESOURCES TESTUTILS MAIN_CALLBACKS SOURCES loopwave.c)
add_sdl_test_executable(testsurround SOURCES testsurround.c)
add_sdl_test_executable(testresample NEEDS_RESOURCES SOURCES testresample.c)
add_sdl_test_executable(testaudioinfo SOURCES testaudioinfo.c)
@ -223,7 +234,7 @@ file(GLOB TESTAUTOMATION_SOURCE_FILES testautomation*.c)
add_sdl_test_executable(testautomation NONINTERACTIVE NONINTERACTIVE_TIMEOUT 120 NEEDS_RESOURCES NO_C90 SOURCES ${TESTAUTOMATION_SOURCE_FILES})
add_sdl_test_executable(testmultiaudio NEEDS_RESOURCES TESTUTILS SOURCES testmultiaudio.c)
add_sdl_test_executable(testaudiohotplug NEEDS_RESOURCES TESTUTILS SOURCES testaudiohotplug.c)
add_sdl_test_executable(testaudiocapture SOURCES testaudiocapture.c)
add_sdl_test_executable(testaudiocapture MAIN_CALLBACKS SOURCES testaudiocapture.c)
add_sdl_test_executable(testatomic NONINTERACTIVE SOURCES testatomic.c)
add_sdl_test_executable(testintersections SOURCES testintersections.c)
add_sdl_test_executable(testrelative SOURCES testrelative.c)
@ -304,7 +315,7 @@ files2headers(gamepad_image_headers
)
files2headers(icon_bmp_header icon.bmp)
add_sdl_test_executable(testaudio NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c)
add_sdl_test_executable(testaudio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c)
add_sdl_test_executable(testfile NONINTERACTIVE SOURCES testfile.c)
add_sdl_test_executable(testcontroller TESTUTILS SOURCES testcontroller.c gamepadutils.c ${gamepad_image_headers})
add_sdl_test_executable(testgeometry TESTUTILS SOURCES testgeometry.c)
@ -341,7 +352,7 @@ add_sdl_test_executable(testsem NONINTERACTIVE NONINTERACTIVE_ARGS 10 NONINTERAC
add_sdl_test_executable(testsensor SOURCES testsensor.c)
add_sdl_test_executable(testshader NEEDS_RESOURCES TESTUTILS SOURCES testshader.c)
add_sdl_test_executable(testshape NEEDS_RESOURCES SOURCES testshape.c)
add_sdl_test_executable(testsprite NEEDS_RESOURCES TESTUTILS SOURCES testsprite.c)
add_sdl_test_executable(testsprite MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testsprite.c)
add_sdl_test_executable(testspriteminimal SOURCES testspriteminimal.c ${icon_bmp_header})
add_sdl_test_executable(teststreaming NEEDS_RESOURCES TESTUTILS SOURCES teststreaming.c)
add_sdl_test_executable(testtimer NONINTERACTIVE NONINTERACTIVE_ARGS --no-interactive NONINTERACTIVE_TIMEOUT 60 SOURCES testtimer.c)

View File

@ -17,10 +17,7 @@
*/
#include <stdlib.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h>
@ -34,68 +31,24 @@ static struct
} wave;
static SDL_AudioStream *stream;
static SDLTest_CommonState *state;
static void fillerup(void)
static int fillerup(void)
{
const int minimum = (wave.soundlen / SDL_AUDIO_FRAMESIZE(wave.spec)) / 2;
if (SDL_GetAudioStreamQueued(stream) < minimum) {
SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen);
}
return 0;
}
/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
static void
quit(int rc)
{
SDL_Quit();
/* Let 'main()' return normally */
if (rc != 0) {
exit(rc);
}
}
static void
close_audio(void)
{
if (stream) {
SDL_DestroyAudioStream(stream);
stream = NULL;
}
}
static void
open_audio(void)
{
stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec, NULL, NULL);
if (!stream) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError());
SDL_free(wave.sound);
quit(2);
}
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));
}
static int done = 0;
#ifdef __EMSCRIPTEN__
static void loop(void)
{
if (done) {
emscripten_cancel_main_loop();
} else {
fillerup();
}
}
#endif
int main(int argc, char *argv[])
int SDL_AppInit(int argc, char *argv[])
{
int i;
char *filename = NULL;
SDLTest_CommonState *state;
/* this doesn't have to run very much, so give up tons of CPU time between iterations. */
SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "5");
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
@ -129,22 +82,25 @@ int main(int argc, char *argv[])
/* Load the SDL library */
if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_EVENTS) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
return 1;
return -1;
}
filename = GetResourceFilename(filename, "sample.wav");
if (filename == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError());
quit(1);
return -1;
}
/* Load the wave file into memory */
if (SDL_LoadWAV(filename, &wave.spec, &wave.sound, &wave.soundlen) == -1) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError());
quit(1);
SDL_free(filename);
return -1;
}
SDL_free(filename);
/* Show the list of available drivers */
SDL_Log("Available audio drivers:");
for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) {
@ -153,30 +109,30 @@ int main(int argc, char *argv[])
SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver());
open_audio();
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(loop, 0, 1);
#else
while (!done) {
SDL_Event event;
while (SDL_PollEvent(&event) > 0) {
if (event.type == SDL_EVENT_QUIT) {
done = 1;
}
}
fillerup();
SDL_Delay(100);
stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec, NULL, NULL);
if (!stream) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError());
return -1;
}
#endif
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));
/* Clean up on signal */
close_audio();
SDL_free(wave.sound);
SDL_free(filename);
SDL_Quit();
SDLTest_CommonDestroyState(state);
return 0;
}
int SDL_AppEvent(const SDL_Event *event)
{
return (event->type == SDL_EVENT_QUIT) ? 1 : 0;
}
int SDL_AppIterate(void)
{
return fillerup();
}
void SDL_AppQuit(void)
{
SDL_DestroyAudioStream(stream);
SDL_free(wave.sound);
SDLTest_CommonDestroyState(state);
}

View File

@ -1,9 +1,4 @@
#include <stdlib.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_test.h>
#include <SDL3/SDL_test_common.h>
#include <SDL3/SDL_main.h>
@ -103,7 +98,6 @@ struct Thing
static Uint64 app_ready_ticks = 0;
static int done = 0;
static SDLTest_CommonState *state = NULL;
static Thing *things = NULL;
@ -124,51 +118,6 @@ static Texture *trashcan_texture = NULL;
static Texture *soundboard_texture = NULL;
static Texture *soundboard_levels_texture = NULL;
static void DestroyTexture(Texture *tex);
static void DestroyThing(Thing *thing);
/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
static void Quit(int rc)
{
while (things != NULL) {
DestroyThing(things); /* make sure all the audio devices are closed, etc. */
}
DestroyTexture(physdev_texture);
DestroyTexture(logdev_texture);
DestroyTexture(audio_texture);
DestroyTexture(trashcan_texture);
DestroyTexture(soundboard_texture);
DestroyTexture(soundboard_levels_texture);
SDLTest_CommonQuit(state);
/* Let 'main()' return normally */
if (rc != 0) {
exit(rc);
}
}
static char *xstrdup(const char *str)
{
char *ptr = SDL_strdup(str);
if (!ptr) {
SDL_Log("Out of memory!");
Quit(1);
}
return ptr;
}
static void *xalloc(const size_t len)
{
void *ptr = SDL_calloc(1, len);
if (!ptr) {
SDL_Log("Out of memory!");
Quit(1);
}
return ptr;
}
static void SetTitleBar(const char *fmt, ...)
{
@ -232,7 +181,12 @@ static Thing *CreateThing(ThingType what, float x, float y, float z, float w, fl
Thing *i;
Thing *thing;
thing = (Thing *) xalloc(sizeof (Thing));
thing = (Thing *) SDL_calloc(1, sizeof (Thing));
if (!thing) {
SDL_Log("Out of memory!");
return NULL;
}
if ((w < 0) || (h < 0)) {
SDL_assert(texture != NULL);
if (w < 0) {
@ -256,7 +210,7 @@ static Thing *CreateThing(ThingType what, float x, float y, float z, float w, fl
thing->scale = 1.0f;
thing->createticks = SDL_GetTicks();
thing->texture = texture;
thing->titlebar = titlebar ? xstrdup(titlebar) : NULL;
thing->titlebar = titlebar ? SDL_strdup(titlebar) : NULL; /* if allocation fails, oh well. */
/* insert in list by Z order (furthest from the "camera" first, so they get drawn over; negative Z is not drawn at all). */
if (things == NULL) {
@ -515,12 +469,14 @@ static Thing *CreatePoofThing(Thing *poofing_thing)
const float centery = poofing_thing->rect.y + (poofing_thing->rect.h / 2);
const float z = poofing_thing->z;
Thing *thing = CreateThing(THING_POOF, poofing_thing->rect.x, poofing_thing->rect.y, z, poofing_thing->rect.w, poofing_thing->rect.h, poofing_thing->texture, NULL);
thing->data.poof.startw = poofing_thing->rect.w;
thing->data.poof.starth = poofing_thing->rect.h;
thing->data.poof.centerx = centerx;
thing->data.poof.centery = centery;
thing->ontick = PoofThing_ontick;
thing->ondrag = PoofThing_ondrag;
if (thing) {
thing->data.poof.startw = poofing_thing->rect.w;
thing->data.poof.starth = poofing_thing->rect.h;
thing->data.poof.centerx = centerx;
thing->data.poof.centery = centery;
thing->ontick = PoofThing_ontick;
thing->ondrag = PoofThing_ondrag;
}
return thing;
}
@ -638,18 +594,20 @@ static Thing *CreateStreamThing(const SDL_AudioSpec *spec, const Uint8 *buf, con
{
static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_LOGDEV, THING_LOGDEV_CAPTURE, THING_NULL };
Thing *thing = CreateThing(THING_STREAM, x, y, 0, -1, -1, soundboard_texture, fname);
SDL_Log("Adding audio stream for %s", fname ? fname : "(null)");
thing->data.stream.stream = SDL_CreateAudioStream(spec, spec);
if (buf && buflen) {
SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen);
SDL_FlushAudioStream(thing->data.stream.stream);
thing->data.stream.total_bytes = SDL_GetAudioStreamAvailable(thing->data.stream.stream);
if (thing) {
SDL_Log("Adding audio stream for %s", fname ? fname : "(null)");
thing->data.stream.stream = SDL_CreateAudioStream(spec, spec);
if (buf && buflen) {
SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen);
SDL_FlushAudioStream(thing->data.stream.stream);
thing->data.stream.total_bytes = SDL_GetAudioStreamAvailable(thing->data.stream.stream);
}
thing->ontick = StreamThing_ontick;
thing->ondrag = StreamThing_ondrag;
thing->ondrop = StreamThing_ondrop;
thing->ondraw = StreamThing_ondraw;
thing->can_be_dropped_onto = can_be_dropped_onto;
}
thing->ontick = StreamThing_ontick;
thing->ondrag = StreamThing_ondrag;
thing->ondrop = StreamThing_ondrop;
thing->ondraw = StreamThing_ondraw;
thing->can_be_dropped_onto = can_be_dropped_onto;
return thing;
}
@ -703,13 +661,15 @@ static Thing *LoadWavThing(const char *fname, float x, float y)
SDL_asprintf(&titlebar, "WAV file (\"%s\", %s, %s, %uHz)", nodirs, AudioFmtToString(spec.format), AudioChansToStr(spec.channels), (unsigned int) spec.freq);
thing = CreateThing(THING_WAV, x - (audio_texture->w / 2), y - (audio_texture->h / 2), 5, -1, -1, audio_texture, titlebar);
SDL_free(titlebar);
SDL_memcpy(&thing->data.wav.spec, &spec, sizeof (SDL_AudioSpec));
thing->data.wav.buf = buf;
thing->data.wav.buflen = buflen;
thing->can_be_dropped_onto = can_be_dropped_onto;
thing->ondrag = WavThing_ondrag;
thing->ondrop = WavThing_ondrop;
if (thing) {
SDL_free(titlebar);
SDL_memcpy(&thing->data.wav.spec, &spec, sizeof (SDL_AudioSpec));
thing->data.wav.buf = buf;
thing->data.wav.buflen = buflen;
thing->can_be_dropped_onto = can_be_dropped_onto;
thing->ondrag = WavThing_ondrag;
thing->ondrop = WavThing_ondrop;
}
}
SDL_free(path);
@ -743,17 +703,21 @@ static void DestroyTexture(Texture *tex)
static Texture *CreateTexture(const char *fname)
{
Texture *tex = (Texture *) xalloc(sizeof (Texture));
int texw, texh;
tex->texture = LoadTexture(state->renderers[0], fname, SDL_TRUE, &texw, &texh);
if (!tex->texture) {
SDL_Log("Failed to load '%s': %s", fname, SDL_GetError());
SDL_free(tex);
Quit(1);
Texture *tex = (Texture *) SDL_calloc(1, sizeof (Texture));
if (!tex) {
SDL_Log("Out of memory!");
} else {
int texw, texh;
tex->texture = LoadTexture(state->renderers[0], fname, SDL_TRUE, &texw, &texh);
if (!tex->texture) {
SDL_Log("Failed to load '%s': %s", fname, SDL_GetError());
SDL_free(tex);
return NULL;
}
SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND);
tex->w = (float) texw;
tex->h = (float) texh;
}
SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND);
tex->w = (float) texw;
tex->h = (float) texh;
return tex;
}
@ -763,9 +727,11 @@ static void DeviceThing_ondrag(Thing *thing, int button, float x, float y)
{
if ((button == SDL_BUTTON_MIDDLE) && (thing->what == THING_LOGDEV_CAPTURE)) { /* drag out a new stream. This is a UX mess. :/ */
dragging_thing = CreateStreamThing(&thing->data.logdev.spec, NULL, 0, NULL, x, y);
dragging_thing->data.stream.next_level_update = SDL_GetTicks() + 100;
SDL_BindAudioStream(thing->data.logdev.devid, dragging_thing->data.stream.stream); /* bind to new device! */
dragging_thing->line_connected_to = thing;
if (dragging_thing) {
dragging_thing->data.stream.next_level_update = SDL_GetTicks() + 100;
SDL_BindAudioStream(thing->data.logdev.devid, dragging_thing->data.stream.stream); /* bind to new device! */
dragging_thing->line_connected_to = thing;
}
} else if (button == SDL_BUTTON_RIGHT) { /* drag out a new logical device. */
const SDL_AudioDeviceID which = ((thing->what == THING_LOGDEV) || (thing->what == THING_LOGDEV_CAPTURE)) ? thing->data.logdev.devid : thing->data.physdev.devid;
const SDL_AudioDeviceID devid = SDL_OpenAudioDevice(which, NULL);
@ -929,22 +895,24 @@ static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID wh
SDL_Log("Adding logical audio device %u", (unsigned int) which);
thing = CreateThing(iscapture ? THING_LOGDEV_CAPTURE : THING_LOGDEV, x, y, 5, -1, -1, logdev_texture, NULL);
thing->data.logdev.devid = which;
thing->data.logdev.iscapture = iscapture;
thing->data.logdev.physdev = physthing;
thing->data.logdev.visualizer = SDL_CreateTexture(state->renderers[0], SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, VISUALIZER_WIDTH, VISUALIZER_HEIGHT);
thing->data.logdev.postmix_lock = SDL_CreateMutex();
if (thing->data.logdev.visualizer) {
SDL_SetTextureBlendMode(thing->data.logdev.visualizer, SDL_BLENDMODE_BLEND);
}
thing->line_connected_to = physthing;
thing->ontick = LogicalDeviceThing_ontick;
thing->ondrag = DeviceThing_ondrag;
thing->ondrop = LogicalDeviceThing_ondrop;
thing->ondraw = LogicalDeviceThing_ondraw;
thing->can_be_dropped_onto = can_be_dropped_onto;
if (thing) {
thing->data.logdev.devid = which;
thing->data.logdev.iscapture = iscapture;
thing->data.logdev.physdev = physthing;
thing->data.logdev.visualizer = SDL_CreateTexture(state->renderers[0], SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, VISUALIZER_WIDTH, VISUALIZER_HEIGHT);
thing->data.logdev.postmix_lock = SDL_CreateMutex();
if (thing->data.logdev.visualizer) {
SDL_SetTextureBlendMode(thing->data.logdev.visualizer, SDL_BLENDMODE_BLEND);
}
thing->line_connected_to = physthing;
thing->ontick = LogicalDeviceThing_ontick;
thing->ondrag = DeviceThing_ondrag;
thing->ondrop = LogicalDeviceThing_ondrop;
thing->ondraw = LogicalDeviceThing_ondraw;
thing->can_be_dropped_onto = can_be_dropped_onto;
SetLogicalDeviceTitlebar(thing);
SetLogicalDeviceTitlebar(thing);
}
return thing;
}
@ -1002,21 +970,23 @@ static Thing *CreatePhysicalDeviceThing(const SDL_AudioDeviceID which, const SDL
SDL_Log("Adding physical audio device %u", (unsigned int) which);
thing = CreateThing(iscapture ? THING_PHYSDEV_CAPTURE : THING_PHYSDEV, next_physdev_x, 170, 5, -1, -1, physdev_texture, NULL);
thing->data.physdev.devid = which;
thing->data.physdev.iscapture = iscapture;
thing->data.physdev.name = SDL_GetAudioDeviceName(which);
thing->ondrag = DeviceThing_ondrag;
thing->ondrop = PhysicalDeviceThing_ondrop;
thing->ontick = PhysicalDeviceThing_ontick;
thing->can_be_dropped_onto = can_be_dropped_onto;
if (thing) {
thing->data.physdev.devid = which;
thing->data.physdev.iscapture = iscapture;
thing->data.physdev.name = SDL_GetAudioDeviceName(which);
thing->ondrag = DeviceThing_ondrag;
thing->ondrop = PhysicalDeviceThing_ondrop;
thing->ontick = PhysicalDeviceThing_ontick;
thing->can_be_dropped_onto = can_be_dropped_onto;
SetPhysicalDeviceTitlebar(thing);
if (SDL_GetTicks() <= (app_ready_ticks + 2000)) { /* assume this is the initial batch if it happens in the first two seconds. */
RepositionRowOfThings(THING_PHYSDEV, 10.0f); /* don't rearrange them after the initial add. */
RepositionRowOfThings(THING_PHYSDEV_CAPTURE, 170.0f); /* don't rearrange them after the initial add. */
next_physdev_x = 0.0f;
} else {
next_physdev_x += physdev_texture->w * 1.5f;
SetPhysicalDeviceTitlebar(thing);
if (SDL_GetTicks() <= (app_ready_ticks + 2000)) { /* assume this is the initial batch if it happens in the first two seconds. */
RepositionRowOfThings(THING_PHYSDEV, 10.0f); /* don't rearrange them after the initial add. */
RepositionRowOfThings(THING_PHYSDEV_CAPTURE, 170.0f); /* don't rearrange them after the initial add. */
next_physdev_x = 0.0f;
} else {
next_physdev_x += physdev_texture->w * 1.5f;
}
}
return thing;
@ -1066,157 +1036,13 @@ static void WindowResized(const int newwinw, const int newwinh)
state->window_h = newwinh;
}
static void Loop(void)
{
SDL_Event event;
SDL_bool saw_event = SDL_FALSE;
if (app_ready_ticks == 0) {
app_ready_ticks = SDL_GetTicks();
}
while (SDL_PollEvent(&event)) {
Thing *thing = NULL;
saw_event = SDL_TRUE;
switch (event.type) {
case SDL_EVENT_MOUSE_MOTION:
thing = UpdateMouseOver(event.motion.x, event.motion.y);
if ((dragging_button == -1) && event.motion.state) {
if (event.motion.state & SDL_BUTTON_LMASK) {
/* for people that don't have all three buttons... */
if (ctrl_held) {
dragging_button = SDL_BUTTON_RIGHT;
} else if (alt_held) {
dragging_button = SDL_BUTTON_MIDDLE;
} else {
dragging_button = SDL_BUTTON_LEFT;
}
dragging_button_real = SDL_BUTTON_LEFT;
} else if (event.motion.state & SDL_BUTTON_RMASK) {
dragging_button = SDL_BUTTON_RIGHT;
dragging_button_real = SDL_BUTTON_RIGHT;
} else if (event.motion.state & SDL_BUTTON_MMASK) {
dragging_button = SDL_BUTTON_MIDDLE;
dragging_button_real = SDL_BUTTON_MIDDLE;
}
if (dragging_button != -1) {
dragging_thing = thing;
if (thing && thing->ondrag) {
thing->ondrag(thing, dragging_button, event.motion.x, event.motion.y);
}
}
}
droppable_highlighted_thing = NULL;
if (dragging_thing) {
dragging_thing->rect.x = event.motion.x - (dragging_thing->rect.w / 2);
dragging_thing->rect.y = event.motion.y - (dragging_thing->rect.h / 2);
if (dragging_thing->can_be_dropped_onto) {
thing = FindThingAtPoint(event.motion.x, event.motion.y);
if (thing) {
int i;
for (i = 0; dragging_thing->can_be_dropped_onto[i]; i++) {
if (dragging_thing->can_be_dropped_onto[i] == thing->what) {
droppable_highlighted_thing = thing;
break;
}
}
}
}
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
thing = UpdateMouseOver(event.button.x, event.button.y);
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
if (dragging_button_real == event.button.button) {
Thing *dropped_thing = dragging_thing;
dragging_thing = NULL;
dragging_button = -1;
dragging_button_real = -1;
if (dropped_thing && dropped_thing->ondrop) {
dropped_thing->ondrop(dropped_thing, event.button.button, event.button.x, event.button.y);
}
droppable_highlighted_thing = NULL;
}
thing = UpdateMouseOver(event.button.x, event.button.y);
break;
case SDL_EVENT_MOUSE_WHEEL:
UpdateMouseOver(event.wheel.mouseX, event.wheel.mouseY);
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
ctrl_held = ((event.key.keysym.mod & SDL_KMOD_CTRL) != 0) ? SDL_TRUE : SDL_FALSE;
alt_held = ((event.key.keysym.mod & SDL_KMOD_ALT) != 0) ? SDL_TRUE : SDL_FALSE;
break;
case SDL_EVENT_DROP_FILE:
SDL_Log("Drop file! '%s'", event.drop.file);
LoadWavThing(event.drop.file, event.drop.x, event.drop.y);
/* SDLTest_CommonEvent will free the string, below. */
break;
case SDL_EVENT_WINDOW_RESIZED:
WindowResized(event.window.data1, event.window.data2);
break;
case SDL_EVENT_AUDIO_DEVICE_ADDED:
CreatePhysicalDeviceThing(event.adevice.which, event.adevice.iscapture);
break;
case SDL_EVENT_AUDIO_DEVICE_REMOVED: {
const SDL_AudioDeviceID which = event.adevice.which;
Thing *i, *next;
SDL_Log("Removing audio device %u", (unsigned int) which);
for (i = things; i != NULL; i = next) {
next = i->next;
if (((i->what == THING_PHYSDEV) || (i->what == THING_PHYSDEV_CAPTURE)) && (i->data.physdev.devid == which)) {
TrashThing(i);
next = things; /* in case we mangled the list. */
} else if (((i->what == THING_LOGDEV) || (i->what == THING_LOGDEV_CAPTURE)) && (i->data.logdev.devid == which)) {
TrashThing(i);
next = things; /* in case we mangled the list. */
}
}
break;
}
default: break;
}
SDLTest_CommonEvent(state, &event, &done);
}
TickThings();
Draw();
if (!saw_event) {
SDL_Delay(10);
}
#ifdef __EMSCRIPTEN__
if (done) {
emscripten_cancel_main_loop();
}
#endif
}
int main(int argc, char *argv[])
int SDL_AppInit(int argc, char *argv[])
{
int i;
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO);
if (state == NULL) {
Quit(1);
return -1;
}
state->window_flags |= SDL_WINDOW_RESIZABLE;
@ -1234,13 +1060,13 @@ int main(int argc, char *argv[])
NULL
};
SDLTest_CommonLogUsage(state, argv[0], options);
Quit(1);
return -1;
}
i += consumed;
}
if (!SDLTest_CommonInit(state)) {
Quit(2);
return -1;
}
if (state->audio_id) {
@ -1250,27 +1076,174 @@ int main(int argc, char *argv[])
SetDefaultTitleBar();
physdev_texture = CreateTexture("physaudiodev.bmp");
logdev_texture = CreateTexture("logaudiodev.bmp");
audio_texture = CreateTexture("audiofile.bmp");
trashcan_texture = CreateTexture("trashcan.bmp");
soundboard_texture = CreateTexture("soundboard.bmp");
soundboard_levels_texture = CreateTexture("soundboard_levels.bmp");
if ((physdev_texture = CreateTexture("physaudiodev.bmp")) == NULL) { return -1; }
if ((logdev_texture = CreateTexture("logaudiodev.bmp")) == NULL) { return -1; }
if ((audio_texture = CreateTexture("audiofile.bmp")) == NULL) { return -1; }
if ((trashcan_texture = CreateTexture("trashcan.bmp")) == NULL) { return -1; }
if ((soundboard_texture = CreateTexture("soundboard.bmp")) == NULL) { return -1; }
if ((soundboard_levels_texture = CreateTexture("soundboard_levels.bmp")) == NULL) { return -1; }
LoadStockWavThings();
CreateTrashcanThing();
CreateDefaultPhysicalDevice(SDL_FALSE);
CreateDefaultPhysicalDevice(SDL_TRUE);
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(Loop, 0, 1);
#else
while (!done) {
Loop();
}
#endif
Quit(0);
return 0;
}
static SDL_bool saw_event = SDL_FALSE;
int SDL_AppEvent(const SDL_Event *event)
{
Thing *thing = NULL;
saw_event = SDL_TRUE;
switch (event->type) {
case SDL_EVENT_MOUSE_MOTION:
thing = UpdateMouseOver(event->motion.x, event->motion.y);
if ((dragging_button == -1) && event->motion.state) {
if (event->motion.state & SDL_BUTTON_LMASK) {
/* for people that don't have all three buttons... */
if (ctrl_held) {
dragging_button = SDL_BUTTON_RIGHT;
} else if (alt_held) {
dragging_button = SDL_BUTTON_MIDDLE;
} else {
dragging_button = SDL_BUTTON_LEFT;
}
dragging_button_real = SDL_BUTTON_LEFT;
} else if (event->motion.state & SDL_BUTTON_RMASK) {
dragging_button = SDL_BUTTON_RIGHT;
dragging_button_real = SDL_BUTTON_RIGHT;
} else if (event->motion.state & SDL_BUTTON_MMASK) {
dragging_button = SDL_BUTTON_MIDDLE;
dragging_button_real = SDL_BUTTON_MIDDLE;
}
if (dragging_button != -1) {
dragging_thing = thing;
if (thing && thing->ondrag) {
thing->ondrag(thing, dragging_button, event->motion.x, event->motion.y);
}
}
}
droppable_highlighted_thing = NULL;
if (dragging_thing) {
dragging_thing->rect.x = event->motion.x - (dragging_thing->rect.w / 2);
dragging_thing->rect.y = event->motion.y - (dragging_thing->rect.h / 2);
if (dragging_thing->can_be_dropped_onto) {
thing = FindThingAtPoint(event->motion.x, event->motion.y);
if (thing) {
int i;
for (i = 0; dragging_thing->can_be_dropped_onto[i]; i++) {
if (dragging_thing->can_be_dropped_onto[i] == thing->what) {
droppable_highlighted_thing = thing;
break;
}
}
}
}
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
thing = UpdateMouseOver(event->button.x, event->button.y);
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
if (dragging_button_real == event->button.button) {
Thing *dropped_thing = dragging_thing;
dragging_thing = NULL;
dragging_button = -1;
dragging_button_real = -1;
if (dropped_thing && dropped_thing->ondrop) {
dropped_thing->ondrop(dropped_thing, event->button.button, event->button.x, event->button.y);
}
droppable_highlighted_thing = NULL;
}
thing = UpdateMouseOver(event->button.x, event->button.y);
break;
case SDL_EVENT_MOUSE_WHEEL:
UpdateMouseOver(event->wheel.mouseX, event->wheel.mouseY);
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
ctrl_held = ((event->key.keysym.mod & SDL_KMOD_CTRL) != 0) ? SDL_TRUE : SDL_FALSE;
alt_held = ((event->key.keysym.mod & SDL_KMOD_ALT) != 0) ? SDL_TRUE : SDL_FALSE;
break;
case SDL_EVENT_DROP_FILE:
SDL_Log("Drop file! '%s'", event->drop.file);
LoadWavThing(event->drop.file, event->drop.x, event->drop.y);
/* SDL frees event->drop.file for you when you use SDL_AppEvent(). */
break;
case SDL_EVENT_WINDOW_RESIZED:
WindowResized(event->window.data1, event->window.data2);
break;
case SDL_EVENT_AUDIO_DEVICE_ADDED:
CreatePhysicalDeviceThing(event->adevice.which, event->adevice.iscapture);
break;
case SDL_EVENT_AUDIO_DEVICE_REMOVED: {
const SDL_AudioDeviceID which = event->adevice.which;
Thing *i, *next;
SDL_Log("Removing audio device %u", (unsigned int) which);
for (i = things; i != NULL; i = next) {
next = i->next;
if (((i->what == THING_PHYSDEV) || (i->what == THING_PHYSDEV_CAPTURE)) && (i->data.physdev.devid == which)) {
TrashThing(i);
next = things; /* in case we mangled the list. */
} else if (((i->what == THING_LOGDEV) || (i->what == THING_LOGDEV_CAPTURE)) && (i->data.logdev.devid == which)) {
TrashThing(i);
next = things; /* in case we mangled the list. */
}
}
break;
}
default: break;
}
return SDLTest_CommonEventMainCallbacks(state, event);
}
int SDL_AppIterate(void)
{
if (app_ready_ticks == 0) {
app_ready_ticks = SDL_GetTicks();
}
TickThings();
Draw();
if (saw_event) {
saw_event = SDL_FALSE; /* reset this so we know when SDL_AppEvent() runs again */
} else {
SDL_Delay(10);
}
return 0; /* keep going. */
}
void SDL_AppQuit(void)
{
while (things != NULL) {
DestroyThing(things); /* make sure all the audio devices are closed, etc. */
}
DestroyTexture(physdev_texture);
DestroyTexture(logdev_texture);
DestroyTexture(audio_texture);
DestroyTexture(trashcan_texture);
DestroyTexture(soundboard_texture);
DestroyTexture(soundboard_levels_texture);
SDLTest_CommonQuit(state);
}

View File

@ -10,94 +10,20 @@
freely.
*/
#include <stdlib.h>
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_AudioStream *stream_in = NULL;
static SDL_AudioStream *stream_out = NULL;
static int done = 0;
static SDLTest_CommonState *state = NULL;
static void loop(void)
{
const SDL_AudioDeviceID devid_in = SDL_GetAudioStreamDevice(stream_in);
const SDL_AudioDeviceID devid_out = SDL_GetAudioStreamDevice(stream_out);
SDL_bool please_quit = SDL_FALSE;
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) {
please_quit = SDL_TRUE;
} else if (e.type == SDL_EVENT_KEY_DOWN) {
if (e.key.keysym.sym == SDLK_ESCAPE) {
please_quit = SDL_TRUE;
}
} else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
if (e.button.button == 1) {
SDL_PauseAudioDevice(devid_out);
SDL_ResumeAudioDevice(devid_in);
}
} else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
if (e.button.button == 1) {
SDL_PauseAudioDevice(devid_in);
SDL_FlushAudioStream(stream_in); /* so no samples are held back for resampling purposes. */
SDL_ResumeAudioDevice(devid_out);
}
}
}
if (!SDL_AudioDevicePaused(devid_in)) {
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
} else {
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
}
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
/* Feed any new data we captured to the output stream. It'll play when we unpause the device. */
while (!please_quit && (SDL_GetAudioStreamAvailable(stream_in) > 0)) {
Uint8 buf[1024];
const int br = SDL_GetAudioStreamData(stream_in, buf, sizeof(buf));
if (br < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read from input audio stream: %s\n", SDL_GetError());
please_quit = 1;
} else if (SDL_PutAudioStreamData(stream_out, buf, br) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to write to output audio stream: %s\n", SDL_GetError());
please_quit = 1;
}
}
if (please_quit) {
/* stop playing back, quit. */
SDL_Log("Shutting down.\n");
SDL_CloseAudioDevice(devid_in);
SDL_CloseAudioDevice(devid_out);
SDL_DestroyAudioStream(stream_in);
SDL_DestroyAudioStream(stream_out);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
#ifdef __EMSCRIPTEN__
emscripten_cancel_main_loop();
#endif
/* Let 'main()' return normally */
done = 1;
return;
}
}
int main(int argc, char **argv)
int SDL_AppInit(int argc, char **argv)
{
SDL_AudioDeviceID *devices;
SDLTest_CommonState *state;
SDL_AudioSpec outspec;
SDL_AudioSpec inspec;
SDL_AudioDeviceID device;
@ -105,6 +31,9 @@ int main(int argc, char **argv)
const char *devname = NULL;
int i;
/* this doesn't have to run very much, so give up tons of CPU time between iterations. */
SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "15");
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
if (state == NULL) {
@ -128,7 +57,7 @@ int main(int argc, char **argv)
if (consumed <= 0) {
static const char *options[] = { "[device_name]", NULL };
SDLTest_CommonLogUsage(state, argv[0], options);
exit(1);
return -1;
}
i += consumed;
@ -175,20 +104,17 @@ int main(int argc, char **argv)
device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, NULL);
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for playback: %s!\n", SDL_GetError());
SDL_Quit();
exit(1);
return -1;
}
SDL_PauseAudioDevice(device);
SDL_GetAudioDeviceFormat(device, &outspec, NULL);
stream_out = SDL_CreateAudioStream(&outspec, &outspec);
if (!stream_out) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create an audio stream for playback: %s!\n", SDL_GetError());
SDL_Quit();
exit(1);
return -1;
} else if (SDL_BindAudioStream(device, stream_out) == -1) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't bind an audio stream for playback: %s!\n", SDL_GetError());
SDL_Quit();
exit(1);
return -1;
}
SDL_Log("Opening capture device %s%s%s...\n",
@ -199,38 +125,89 @@ int main(int argc, char **argv)
device = SDL_OpenAudioDevice(want_device, NULL);
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for capture: %s!\n", SDL_GetError());
SDL_Quit();
exit(1);
return -1;
}
SDL_PauseAudioDevice(device);
SDL_GetAudioDeviceFormat(device, &inspec, NULL);
stream_in = SDL_CreateAudioStream(&inspec, &inspec);
if (!stream_in) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create an audio stream for capture: %s!\n", SDL_GetError());
SDL_Quit();
exit(1);
return -1;
} else if (SDL_BindAudioStream(device, stream_in) == -1) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't bind an audio stream for capture: %s!\n", SDL_GetError());
SDL_Quit();
exit(1);
return -1;
}
SDL_SetAudioStreamFormat(stream_in, NULL, &outspec); /* make sure we output at the playback format. */
SDL_Log("Ready! Hold down mouse or finger to record!\n");
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(loop, 0, 1);
#else
while (!done) {
loop();
if (!done) {
SDL_Delay(16);
}
}
#endif
SDLTest_CommonDestroyState(state);
return 0;
}
int SDL_AppEvent(const SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT) {
return 1; /* terminate as success. */
} else if (event->type == SDL_EVENT_KEY_DOWN) {
if (event->key.keysym.sym == SDLK_ESCAPE) {
return 1; /* terminate as success. */
}
} else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
if (event->button.button == 1) {
SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(stream_out));
SDL_FlushAudioStream(stream_out); /* so no samples are held back for resampling purposes. */
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream_in));
}
} else if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
if (event->button.button == 1) {
SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(stream_in));
SDL_FlushAudioStream(stream_in); /* so no samples are held back for resampling purposes. */
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream_out));
}
}
return 0; /* keep going. */
}
int SDL_AppIterate(void)
{
if (!SDL_AudioDevicePaused(SDL_GetAudioStreamDevice(stream_in))) {
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
} else {
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
}
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
/* Feed any new data we captured to the output stream. It'll play when we unpause the device. */
while (SDL_GetAudioStreamAvailable(stream_in) > 0) {
Uint8 buf[1024];
const int br = SDL_GetAudioStreamData(stream_in, buf, sizeof(buf));
if (br < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read from input audio stream: %s\n", SDL_GetError());
return -1; /* quit the app, report failure. */
} else if (SDL_PutAudioStreamData(stream_out, buf, br) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to write to output audio stream: %s\n", SDL_GetError());
return -1; /* quit the app, report failure. */
}
}
return 0; /* keep app going. */
}
void SDL_AppQuit(void)
{
SDL_Log("Shutting down.\n");
const SDL_AudioDeviceID devid_in = SDL_GetAudioStreamDevice(stream_in);
const SDL_AudioDeviceID devid_out = SDL_GetAudioStreamDevice(stream_out);
SDL_CloseAudioDevice(devid_in); /* !!! FIXME: use SDL_OpenAudioDeviceStream instead so we can dump this. */
SDL_CloseAudioDevice(devid_out);
SDL_DestroyAudioStream(stream_in);
SDL_DestroyAudioStream(stream_out);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDLTest_CommonDestroyState(state);
SDL_Quit();
}

View File

@ -14,10 +14,7 @@
#include <stdlib.h>
#include <time.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_test.h>
#include <SDL3/SDL_test_common.h>
#include <SDL3/SDL_main.h>
@ -48,20 +45,12 @@ static SDL_bool suspend_when_occluded;
/* -1: infinite random moves (default); >=0: enables N deterministic moves */
static int iterations = -1;
static int done;
/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
static void
quit(int rc)
void SDL_AppQuit(void)
{
SDL_free(sprites);
SDL_free(positions);
SDL_free(velocities);
SDLTest_CommonQuit(state);
/* Let 'main()' return normally */
if (rc != 0) {
exit(rc);
}
}
static int LoadSprite(const char *file)
@ -395,17 +384,17 @@ static void MoveSprites(SDL_Renderer *renderer, SDL_Texture *sprite)
SDL_RenderPresent(renderer);
}
static void loop(void)
int SDL_AppEvent(const SDL_Event *event)
{
return SDLTest_CommonEventMainCallbacks(state, event);
}
int SDL_AppIterate(void)
{
Uint64 now;
int i;
int active_windows = 0;
SDL_Event event;
/* Check for events */
while (SDL_PollEvent(&event)) {
SDLTest_CommonEvent(state, &event, &done);
}
for (i = 0; i < state->num_windows; ++i) {
if (state->windows[i] == NULL ||
(suspend_when_occluded && (SDL_GetWindowFlags(state->windows[i]) & SDL_WINDOW_OCCLUDED))) {
@ -414,14 +403,9 @@ static void loop(void)
++active_windows;
MoveSprites(state->renderers[i], sprites[i]);
}
#ifdef __EMSCRIPTEN__
if (done) {
emscripten_cancel_main_loop();
}
#endif
/* If all windows are occluded, throttle the event polling to 15hz. */
if (!done && !active_windows) {
if (!active_windows) {
SDL_DelayNS(SDL_NS_PER_SECOND / 15);
}
@ -435,9 +419,11 @@ static void loop(void)
next_fps_check = now + fps_check_delay;
frames = 0;
}
return 0; /* keep going */
}
int main(int argc, char *argv[])
int SDL_AppInit(int argc, char *argv[])
{
int i;
Uint64 seed;
@ -449,7 +435,7 @@ int main(int argc, char *argv[])
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
if (state == NULL) {
return 1;
return -1;
}
for (i = 1; i < argc;) {
@ -532,12 +518,12 @@ int main(int argc, char *argv[])
NULL
};
SDLTest_CommonLogUsage(state, argv[0], options);
quit(1);
return -1;
}
i += consumed;
}
if (!SDLTest_CommonInit(state)) {
quit(2);
return -1;
}
/* Create the windows, initialize the renderers, and load the textures */
@ -545,7 +531,7 @@ int main(int argc, char *argv[])
(SDL_Texture **)SDL_malloc(state->num_windows * sizeof(*sprites));
if (sprites == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n");
quit(2);
return -1;
}
for (i = 0; i < state->num_windows; ++i) {
SDL_Renderer *renderer = state->renderers[i];
@ -553,7 +539,7 @@ int main(int argc, char *argv[])
SDL_RenderClear(renderer);
}
if (LoadSprite(icon) < 0) {
quit(2);
return -1;
}
/* Allocate memory for the sprite info */
@ -561,7 +547,7 @@ int main(int argc, char *argv[])
velocities = (SDL_FRect *)SDL_malloc(num_sprites * sizeof(*velocities));
if (positions == NULL || velocities == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!\n");
quit(2);
return -1;
}
/* Position sprites and set their velocities using the fuzzer */
@ -586,19 +572,10 @@ int main(int argc, char *argv[])
}
}
/* Main render loop */
/* Main render loop in SDL_AppIterate will begin when this function returns. */
frames = 0;
next_fps_check = SDL_GetTicks() + fps_check_delay;
done = 0;
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(loop, 0, 1);
#else
while (!done) {
loop();
}
#endif
quit(0);
return 0;
}