diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bfd22464..dd24bbfb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 70b625b37..b19bb5fba 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -418,6 +418,7 @@ + @@ -468,6 +469,8 @@ $(IntDir)$(TargetName)_cpp.pch $(IntDir)$(TargetName)_cpp.pch + + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index a530ef1bf..ba454f428 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -175,6 +175,12 @@ {3ad16a8a-0ed8-439c-a771-383af2e2867f} + + {00002ddb6c5ea921181bf32d50e40000} + + + {00000a808f8ba6b489985f82a4e80000} + @@ -399,6 +405,9 @@ API Headers + + main + @@ -846,6 +855,12 @@ + + main\generic + + + main + diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj index 21c5e7ebb..0dee306b6 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj +++ b/VisualC-WinRT/SDL-UWP.vcxproj @@ -133,6 +133,7 @@ + @@ -337,6 +338,8 @@ + + true diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters index 430babceb..7d765c13c 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj.filters +++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters @@ -7,6 +7,12 @@ {68e1b30b-19ed-4612-93e4-6260c5a979e5} + + {00004a2523fc69c7128c60648c860000} + + + {0000318d975e0a2867ab1d5727bf0000} + @@ -270,6 +276,9 @@ Source Files + + main + Source Files @@ -588,6 +597,12 @@ Source Files + + main\generic + + + main + Source Files diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 84e774521..57eb07e85 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -368,6 +368,7 @@ + @@ -397,6 +398,8 @@ Create Create + + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 23fa7b2e3..cd7b2b2e3 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -169,6 +169,12 @@ {f48c2b17-1bee-4fec-a7c8-24cf619abe08} + + {00001967ea2801028a046a722a070000} + + + {0000ddc7911820dbe64274d3654f0000} + @@ -390,6 +396,9 @@ API Headers + + main + @@ -828,6 +837,12 @@ + + main\generic + + + main + diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 279a74856..bd32ee428 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -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 = ""; }; 0000B6ADCD88CAD6610F0000 /* SDL_hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hashtable.h; sourceTree = ""; }; + 0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_sysmain_callbacks.m; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; + 00003260407E1002EAC10000 /* SDL_main_callbacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_main_callbacks.h; path = SDL_main_callbacks.h; sourceTree = ""; }; /* 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 = ""; + }; + 00008B5A0CB83D2069E80000 /* ios */ = { + isa = PBXGroup; + children = ( + 0000BB287BA0A0178C1A0000 /* SDL_sysmain_callbacks.m */, + ); + path = ios; + sourceTree = ""; + }; 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; }; diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 01193b0cd..2af3c88ff 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -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 */ diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 6181c0c01..1833fa73f 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -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 */ diff --git a/include/SDL3/SDL_main_impl.h b/include/SDL3/SDL_main_impl.h index 6b73bbb3f..dd35dbd52 100644 --- a/include/SDL3/SDL_main_impl.h +++ b/include/SDL3/SDL_main_impl.h @@ -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 diff --git a/include/SDL3/SDL_test_common.h b/include/SDL3/SDL_test_common.h index f2d66e51e..f194a42b5 100644 --- a/include/SDL3/SDL_test_common.h +++ b/include/SDL3/SDL_test_common.h @@ -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. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index c53c02adc..6ee8a813a 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -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: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index dc8596662..09c6dbbd4 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -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 diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index ac41615e9..868a35ec9 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -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) diff --git a/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c new file mode 100644 index 000000000..5d880066d --- /dev/null +++ b/src/main/SDL_main_callbacks.c @@ -0,0 +1,114 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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(); +} + diff --git a/src/main/SDL_main_callbacks.h b/src/main/SDL_main_callbacks.h new file mode 100644 index 000000000..9df171a99 --- /dev/null +++ b/src/main/SDL_main_callbacks.h @@ -0,0 +1,31 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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_ + + diff --git a/src/main/emscripten/SDL_sysmain_callbacks.c b/src/main/emscripten/SDL_sysmain_callbacks.c new file mode 100644 index 000000000..fc6f53e6c --- /dev/null +++ b/src/main/emscripten/SDL_sysmain_callbacks.c @@ -0,0 +1,47 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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 + +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; +} + diff --git a/src/main/generic/SDL_sysmain_callbacks.c b/src/main/generic/SDL_sysmain_callbacks.c new file mode 100644 index 000000000..3e47264e3 --- /dev/null +++ b/src/main/generic/SDL_sysmain_callbacks.c @@ -0,0 +1,80 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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; +} + diff --git a/src/main/ios/SDL_sysmain_callbacks.m b/src/main/ios/SDL_sysmain_callbacks.m new file mode 100644 index 000000000..63722630f --- /dev/null +++ b/src/main/ios/SDL_sysmain_callbacks.m @@ -0,0 +1,82 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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 + +@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 + + diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index e4b62709b..f18e94028 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -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; } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4ce431e73..ee4251a47 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 "$") + else() + list(APPEND EXTRA_SOURCES "$") + endif() + list(APPEND EXTRA_SOURCES - "$" "${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) diff --git a/test/loopwave.c b/test/loopwave.c index 1fa1b91e1..877fe8f39 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -17,10 +17,7 @@ */ #include -#ifdef __EMSCRIPTEN__ -#include -#endif - +#define SDL_MAIN_USE_CALLBACKS 1 #include #include #include @@ -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); +} + diff --git a/test/testaudio.c b/test/testaudio.c index e77901c94..d7b8b56ae 100644 --- a/test/testaudio.c +++ b/test/testaudio.c @@ -1,9 +1,4 @@ -#include - -#ifdef __EMSCRIPTEN__ -#include -#endif - +#define SDL_MAIN_USE_CALLBACKS 1 #include #include #include @@ -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); +} + diff --git a/test/testaudiocapture.c b/test/testaudiocapture.c index efc4be101..9b44b6573 100644 --- a/test/testaudiocapture.c +++ b/test/testaudiocapture.c @@ -10,94 +10,20 @@ freely. */ -#include - +#define SDL_MAIN_USE_CALLBACKS 1 #include #include #include -#ifdef __EMSCRIPTEN__ -#include -#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(); +} + + diff --git a/test/testsprite.c b/test/testsprite.c index 9afa6f0da..217dc181b 100644 --- a/test/testsprite.c +++ b/test/testsprite.c @@ -14,10 +14,7 @@ #include #include -#ifdef __EMSCRIPTEN__ -#include -#endif - +#define SDL_MAIN_USE_CALLBACKS 1 #include #include #include @@ -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; } +