From 3e7d9812a363a7fcb9ad192ff6adb3dffd45085e Mon Sep 17 00:00:00 2001 From: Sebastian Krzyszkowiak Date: Mon, 10 Sep 2018 03:18:52 +0200 Subject: [PATCH] Refactor the engine's main loop. --- cmake/libsuperderpy.cmake | 14 +- src/CMakeLists.txt | 1 + src/gamestate.c | 4 - src/gamestate.h | 16 ++ src/internal.c | 25 +-- src/libsuperderpy.c | 334 ++++-------------------------------- src/libsuperderpy.h | 18 +- src/mainloop.c | 351 ++++++++++++++++++++++++++++++++++++++ src/mainloop.h | 28 +++ 9 files changed, 446 insertions(+), 345 deletions(-) create mode 100644 src/mainloop.c create mode 100644 src/mainloop.h diff --git a/cmake/libsuperderpy.cmake b/cmake/libsuperderpy.cmake index fcada08..8714fff 100644 --- a/cmake/libsuperderpy.cmake +++ b/cmake/libsuperderpy.cmake @@ -85,23 +85,25 @@ if(EMSCRIPTEN) set(CMAKE_EXECUTABLE_SUFFIX ".bc") set(CMAKE_SHARED_LIBRARY_SUFFIX ".so") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -s SIDE_MODULE=1") - set(EMSCRIPTEN_USE_FLAGS -s USE_SDL=2 -s USE_FREETYPE=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_OGG=1 -s USE_VORBIS=1 -s FULL_ES2=1) + set(EMSCRIPTEN_FLAGS -s USE_SDL=2 -s USE_FREETYPE=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_OGG=1 -s USE_VORBIS=1 -s FULL_ES2=1 -s ERROR_ON_UNDEFINED_SYMBOLS=1 -s NO_EXIT_RUNTIME=0 -s PRECISE_F32=1) + set(LIBSUPERDERPY_EMSCRIPTEN_MODE "asm.js" CACHE STRING "Emscripten compilation mode (JavaScript or WebAssembly)") set_property(CACHE LIBSUPERDERPY_EMSCRIPTEN_MODE PROPERTY STRINGS "asm.js;wasm") if("${LIBSUPERDERPY_EMSCRIPTEN_MODE}" STREQUAL "wasm") # https://github.com/kripken/emscripten/issues/5436 set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -s WASM=1") - set(EMSCRIPTEN_USE_FLAGS ${EMSCRIPTEN_USE_FLAGS} -s WASM=1) + set(EMSCRIPTEN_FLAGS ${EMSCRIPTEN_FLAGS} -s WASM=1) set(CMAKE_SHARED_MODULE_SUFFIX ".wasm") add_definitions(-DLIBSUPERDERPY_WASM=1) else() set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -s WASM=0") - set(EMSCRIPTEN_USE_FLAGS ${EMSCRIPTEN_USE_FLAGS} -s WASM=0) + set(EMSCRIPTEN_FLAGS ${EMSCRIPTEN_FLAGS} -s WASM=0) set(CMAKE_SHARED_MODULE_SUFFIX ".js") endif() - set(LIBSUPERDERPY_USE_WEBGL2 "no" CACHE STRING "Use WebGL 2 context") + + set(LIBSUPERDERPY_USE_WEBGL2 NO CACHE BOOL "Use WebGL 2 context") if(LIBSUPERDERPY_USE_WEBGL2) - set(EMSCRIPTEN_USE_FLAGS ${EMSCRIPTEN_USE_FLAGS} -s USE_WEBGL2=1) + set(EMSCRIPTEN_FLAGS ${EMSCRIPTEN_FLAGS} -s USE_WEBGL2=1) endif(LIBSUPERDERPY_USE_WEBGL2) if(CMAKE_INSTALL_PREFIX MATCHES "/usr/local") @@ -246,7 +248,7 @@ endif() add_custom_target(${LIBSUPERDERPY_GAMENAME}_js DEPENDS ${LIBSUPERDERPY_GAMENAME}_install ${LIBSUPERDERPY_GAMENAME}_flac_to_ogg WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/${LIBSUPERDERPY_GAMENAME}" - COMMAND "${CMAKE_C_COMPILER}" ${CFLAGS_LIST} ../${LIBSUPERDERPY_GAMENAME}${CMAKE_EXECUTABLE_SUFFIX} ../libsuperderpy${CMAKE_SHARED_LIBRARY_SUFFIX} ../libsuperderpy-${LIBSUPERDERPY_GAMENAME}${CMAKE_SHARED_LIBRARY_SUFFIX} ${ALLEGRO5_LIBRARIES} ${ALLEGRO5_FONT_LIBRARIES} ${ALLEGRO5_TTF_LIBRARIES} ${ALLEGRO5_PRIMITIVES_LIBRARIES} ${ALLEGRO5_AUDIO_LIBRARIES} ${ALLEGRO5_ACODEC_LIBRARIES} ${ALLEGRO5_VIDEO_LIBRARIES} ${ALLEGRO5_IMAGE_LIBRARIES} ${ALLEGRO5_COLOR_LIBRARIES} ${EMSCRIPTEN_USE_FLAGS} -s MAIN_MODULE=1 -s TOTAL_MEMORY=${EMSCRIPTEN_TOTAL_MEMORY_BYTES} -o ${LIBSUPERDERPY_GAMENAME}.html --preload-file data --preload-file gamestates@/ + COMMAND "${CMAKE_C_COMPILER}" ${CFLAGS_LIST} ../${LIBSUPERDERPY_GAMENAME}${CMAKE_EXECUTABLE_SUFFIX} ../libsuperderpy${CMAKE_SHARED_LIBRARY_SUFFIX} ../libsuperderpy-${LIBSUPERDERPY_GAMENAME}${CMAKE_SHARED_LIBRARY_SUFFIX} ${ALLEGRO5_LIBRARIES} ${ALLEGRO5_FONT_LIBRARIES} ${ALLEGRO5_TTF_LIBRARIES} ${ALLEGRO5_PRIMITIVES_LIBRARIES} ${ALLEGRO5_AUDIO_LIBRARIES} ${ALLEGRO5_ACODEC_LIBRARIES} ${ALLEGRO5_VIDEO_LIBRARIES} ${ALLEGRO5_IMAGE_LIBRARIES} ${ALLEGRO5_COLOR_LIBRARIES} ${EMSCRIPTEN_FLAGS} -s MAIN_MODULE=1 -s TOTAL_MEMORY=${EMSCRIPTEN_TOTAL_MEMORY_BYTES} -o ${LIBSUPERDERPY_GAMENAME}.html --preload-file data --preload-file gamestates@/ COMMAND rm -rf data gamestates VERBATIM ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 986f650..2f306b4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ SET(SRC_LIST timeline.c gamestate.c libsuperderpy.c + mainloop.c character.c internal.c tween.c diff --git a/src/gamestate.c b/src/gamestate.c index abec621..cf41910 100644 --- a/src/gamestate.c +++ b/src/gamestate.c @@ -76,7 +76,6 @@ SYMBOL_EXPORT void LoadGamestate(struct Game* game, const char* name) { gs->showLoading = true; } PrintConsole(game, "Gamestate \"%s\" marked to be LOADED.", name); - game->_priv.gamestate_scheduled = true; } SYMBOL_EXPORT void UnloadGamestate(struct Game* game, const char* name) { @@ -98,7 +97,6 @@ SYMBOL_EXPORT void UnloadGamestate(struct Game* game, const char* name) { PrintConsole(game, "Tried to unload nonexisitent gamestate \"%s\"", name); return; } - game->_priv.gamestate_scheduled = true; } SYMBOL_EXPORT void StartGamestate(struct Game* game, const char* name) { @@ -118,7 +116,6 @@ SYMBOL_EXPORT void StartGamestate(struct Game* game, const char* name) { LoadGamestate(game, name); return StartGamestate(game, name); } - game->_priv.gamestate_scheduled = true; } SYMBOL_EXPORT void StopGamestate(struct Game* game, const char* name) { @@ -139,7 +136,6 @@ SYMBOL_EXPORT void StopGamestate(struct Game* game, const char* name) { PrintConsole(game, "Tried to stop nonexisitent gamestate \"%s\"", name); return; } - game->_priv.gamestate_scheduled = true; } SYMBOL_EXPORT void PauseGamestate(struct Game* game, const char* name) { diff --git a/src/gamestate.h b/src/gamestate.h index 5410fb4..ec7537d 100644 --- a/src/gamestate.h +++ b/src/gamestate.h @@ -76,4 +76,20 @@ void StopCurrentGamestate(struct Game* game); void PauseCurrentGamestate(struct Game* game); void UnloadCurrentGamestate(struct Game* game); +// Gamestate API + +extern int Gamestate_ProgressCount; +void Gamestate_Draw(struct Game* game, struct GamestateResources* data); +void Gamestate_Logic(struct Game* game, struct GamestateResources* data, double delta); +void Gamestate_Tick(struct Game* game, struct GamestateResources* data); +void* Gamestate_Load(struct Game* game, void (*progress)(struct Game*)); +void Gamestate_PostLoad(struct Game* game, struct GamestateResources* data); +void Gamestate_Start(struct Game* game, struct GamestateResources* data); +void Gamestate_Pause(struct Game* game, struct GamestateResources* data); +void Gamestate_Resume(struct Game* game, struct GamestateResources* data); +void Gamestate_Stop(struct Game* game, struct GamestateResources* data); +void Gamestate_Unload(struct Game* game, struct GamestateResources* data); +void Gamestate_ProcessEvent(struct Game* game, struct GamestateResources* data, ALLEGRO_EVENT* ev); +void Gamestate_Reload(struct Game* game, struct GamestateResources* data); + #endif diff --git a/src/internal.c b/src/internal.c index e920a2b..3912603 100644 --- a/src/internal.c +++ b/src/internal.c @@ -542,15 +542,11 @@ SYMBOL_INTERNAL char* GetLibraryPath(struct Game* game, char* filename) { } SYMBOL_INTERNAL void PauseExecution(struct Game* game) { - struct Gamestate* tmp = game->_priv.gamestates; - while (tmp) { - if (!tmp->paused && tmp->loaded && tmp->started && tmp->api->Gamestate_Pause) { - tmp->api->Gamestate_Pause(game, tmp->data); - } - tmp = tmp->next; - } game->_priv.paused = true; - PrintConsole(game, "DEBUG: game execution paused."); + al_stop_timer(game->_priv.timer); + al_detach_voice(game->audio.v); + FreezeGamestates(game); + PrintConsole(game, "Engine halted."); } SYMBOL_INTERNAL void ReloadCode(struct Game* game) { @@ -576,15 +572,12 @@ SYMBOL_INTERNAL void ReloadCode(struct Game* game) { } SYMBOL_INTERNAL void ResumeExecution(struct Game* game) { - struct Gamestate* tmp = game->_priv.gamestates; - while (tmp) { - if (!tmp->paused && tmp->loaded && tmp->started && tmp->api->Gamestate_Resume) { - tmp->api->Gamestate_Resume(game, tmp->data); - } - tmp = tmp->next; - } + UnfreezeGamestates(game); + al_attach_mixer_to_voice(game->audio.mixer, game->audio.v); + al_resume_timer(game->_priv.timer); game->_priv.paused = false; - PrintConsole(game, "DEBUG: game execution resumed."); + game->_priv.timestamp = al_get_time(); + PrintConsole(game, "Engine resumed."); } SYMBOL_INTERNAL char* GetGameName(struct Game* game, const char* format) { diff --git a/src/libsuperderpy.c b/src/libsuperderpy.c index 3636d47..85687b0 100644 --- a/src/libsuperderpy.c +++ b/src/libsuperderpy.c @@ -167,14 +167,19 @@ SYMBOL_EXPORT struct Game* libsuperderpy_init(int argc, char** argv, const char* game->joystick = al_install_joystick(); } - int fullscreen = ALLEGRO_FULLSCREEN_WINDOW; + int windowMode = ALLEGRO_FULLSCREEN_WINDOW; #ifdef ALLEGRO_ANDROID - fullscreen |= ALLEGRO_FRAMELESS; + windowMode |= ALLEGRO_FRAMELESS; #endif - al_set_new_display_flags((game->config.fullscreen ? (fullscreen) : ALLEGRO_WINDOWED) | ALLEGRO_RESIZABLE | ALLEGRO_OPENGL | ALLEGRO_PROGRAMMABLE_PIPELINE); #ifdef __EMSCRIPTEN__ - al_set_new_display_flags((al_get_new_display_flags() | ALLEGRO_WINDOWED) ^ ALLEGRO_FULLSCREEN_WINDOW); + windowMode = ALLEGRO_WINDOWED; #endif + if (!game->config.fullscreen) { + windowMode = ALLEGRO_WINDOWED; + } + windowMode |= ALLEGRO_RESIZABLE; + + al_set_new_display_flags(windowMode | ALLEGRO_OPENGL | ALLEGRO_PROGRAMMABLE_PIPELINE); al_set_new_display_option(ALLEGRO_VSYNC, 2 - strtol(GetConfigOptionDefault(game, "SuperDerpy", "vsync", "1"), NULL, 10), ALLEGRO_SUGGEST); #ifdef LIBSUPERDERPY_ORIENTATION_LANDSCAPE @@ -244,7 +249,6 @@ SYMBOL_EXPORT struct Game* libsuperderpy_init(int argc, char** argv, const char* al_add_new_bitmap_flag(ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR); game->_priv.gamestates = NULL; - game->_priv.gamestate_scheduled = false; al_init_user_event_source(&(game->event_source)); @@ -295,7 +299,7 @@ SYMBOL_EXPORT struct Game* libsuperderpy_init(int argc, char** argv, const char* return game; } -SYMBOL_EXPORT int libsuperderpy_run(struct Game* game) { +SYMBOL_EXPORT int libsuperderpy_start(struct Game* game) { al_register_event_source(game->_priv.event_queue, al_get_display_event_source(game->display)); al_register_event_source(game->_priv.event_queue, al_get_keyboard_event_source()); if (game->mouse) { @@ -341,308 +345,32 @@ SYMBOL_EXPORT int libsuperderpy_run(struct Game* game) { PrintConsole(game, "Loading screen registered."); game->_priv.timestamp = al_get_time(); - game->_priv.draw = true; -#ifdef __EMSCRIPTEN__ - void libsuperderpy_mainloop(void* game); - emscripten_set_main_loop_arg(libsuperderpy_mainloop, game, 0, true); + game->_priv.paused = false; return 0; } -SYMBOL_INTERNAL void libsuperderpy_mainloop_exit(struct Game* game) { - libsuperderpy_destroy(game); - free(game); - printf("Halted.\n"); - emscripten_cancel_main_loop(); -} - -SYMBOL_INTERNAL void libsuperderpy_mainloop(void* g) { - struct Game* game = (struct Game*)g; -#endif - do { - ClearGarbage(game); - - // TODO: split mainloop to functions to make it readable - ALLEGRO_EVENT ev; - if (game->_priv.draw && ((al_is_event_queue_empty(game->_priv.event_queue)) || (game->_priv.gamestate_scheduled))) { - game->_priv.gamestate_scheduled = false; - struct Gamestate* tmp = game->_priv.gamestates; - - game->_priv.loading.toLoad = 0; - game->_priv.loading.loaded = 0; - game->loading_progress = 0; - - // TODO: support gamestate dependences/ordering - while (tmp) { - if (tmp->pending_stop) { - PrintConsole(game, "Stopping gamestate \"%s\"...", tmp->name); - game->_priv.current_gamestate = tmp; - (*tmp->api->Gamestate_Stop)(game, tmp->data); - tmp->started = false; - tmp->pending_stop = false; - PrintConsole(game, "Gamestate \"%s\" stopped successfully.", tmp->name); - } - - if (tmp->pending_load) { game->_priv.loading.toLoad++; } - tmp = tmp->next; - } - - tmp = game->_priv.gamestates; - - while (tmp) { - if (tmp->pending_unload) { - PrintConsole(game, "Unloading gamestate \"%s\"...", tmp->name); - al_stop_timer(game->_priv.timer); - tmp->loaded = false; - tmp->pending_unload = false; - game->_priv.current_gamestate = tmp; - (*tmp->api->Gamestate_Unload)(game, tmp->data); - al_resume_timer(game->_priv.timer); - PrintConsole(game, "Gamestate \"%s\" unloaded successfully.", tmp->name); - } - if (tmp->pending_load) { - al_stop_timer(game->_priv.timer); - if (tmp->showLoading) { - (*game->_priv.loading.gamestate->api->Gamestate_Start)(game, game->_priv.loading.gamestate->data); - } - - if (!tmp->api) { - if (!OpenGamestate(game, tmp) || !LinkGamestate(game, tmp)) { - tmp->pending_load = false; - tmp->pending_start = false; - tmp->next = tmp; - continue; - } - } - if (tmp->api) { - PrintConsole(game, "Loading gamestate \"%s\"...", tmp->name); - game->_priv.loading.progress = 0; - - game->_priv.loading.current = tmp; - game->_priv.current_gamestate = tmp; - - struct GamestateLoadingThreadData data = {.game = game, .gamestate = tmp, .bitmap_flags = al_get_new_bitmap_flags()}; - game->_priv.loading.inProgress = true; - double time = al_get_time(); - game->_priv.loading.time = time; - - CalculateProgress(game); -#ifndef LIBSUPERDERPY_SINGLE_THREAD - al_run_detached_thread(GamestateLoadingThread, &data); - while (game->_priv.loading.inProgress) { - DrawGamestates(game); - al_set_target_backbuffer(game->display); - double delta = al_get_time() - game->_priv.loading.time; - if (tmp->showLoading) { - (*game->_priv.loading.gamestate->api->Gamestate_Logic)(game, game->_priv.loading.gamestate->data, delta); - (*game->_priv.loading.gamestate->api->Gamestate_Draw)(game, game->_priv.loading.gamestate->data); - if (game->handlers.postdraw) { - game->handlers.postdraw(game); - } - } - game->_priv.loading.time += delta; - game->time += delta; // TODO: ability to disable passing time during loading - if (game->_priv.texture_sync) { - al_convert_memory_bitmaps(); - game->_priv.texture_sync = false; - al_signal_cond(game->_priv.texture_sync_cond); - game->_priv.loading.time = al_get_time(); - } - DrawConsole(game); - al_flip_display(); - } -#else - GamestateLoadingThread(&data); - al_convert_memory_bitmaps(); -#endif - - al_set_new_bitmap_flags(data.bitmap_flags); - - if (tmp->api->Gamestate_PostLoad) { - PrintConsole(game, "[%s] Post-loading...", tmp->name); - tmp->api->Gamestate_PostLoad(game, tmp->data); - } - - game->_priv.loading.progress++; - CalculateProgress(game); - PrintConsole(game, "Gamestate \"%s\" loaded successfully in %f seconds.", tmp->name, al_get_time() - time); - game->_priv.loading.loaded++; - - tmp->loaded = true; - tmp->pending_load = false; - } - if (tmp->showLoading) { - (*game->_priv.loading.gamestate->api->Gamestate_Stop)(game, game->_priv.loading.gamestate->data); - } - al_resume_timer(game->_priv.timer); - game->_priv.timestamp = al_get_time(); - } - - tmp = tmp->next; - } - - if (game->_priv.loading.loaded) { - ReloadShaders(game, false); - } - - bool gameActive = false; - tmp = game->_priv.gamestates; - - while (tmp) { - if ((tmp->pending_start) && (tmp->loaded)) { - PrintConsole(game, "Starting gamestate \"%s\"...", tmp->name); - al_stop_timer(game->_priv.timer); - game->_priv.current_gamestate = tmp; - tmp->started = true; - tmp->pending_start = false; - (*tmp->api->Gamestate_Start)(game, tmp->data); - al_resume_timer(game->_priv.timer); - game->_priv.timestamp = al_get_time(); - PrintConsole(game, "Gamestate \"%s\" started successfully.", tmp->name); - } - - if ((tmp->started) || (tmp->pending_start) || (tmp->pending_load)) { - gameActive = true; - } - tmp = tmp->next; - } - - if (!gameActive) { - PrintConsole(game, "No gamestates left, exiting..."); #ifdef __EMSCRIPTEN__ - libsuperderpy_mainloop_exit(game); -#endif - break; - } - - al_convert_memory_bitmaps(); - - double delta = al_get_time() - game->_priv.timestamp; - game->_priv.timestamp += delta; - delta *= ALLEGRO_BPS_TO_SECS(al_get_timer_speed(game->_priv.timer) / (1 / 60.f)); - game->time += delta; - if (!game->_priv.paused) { - LogicGamestates(game, delta); - DrawGamestates(game); - } - - DrawConsole(game); - al_flip_display(); - -#ifdef __EMSCRIPTEN__ - return; -#endif - - } else { -#ifdef __EMSCRIPTEN__ - if (al_is_event_queue_empty(game->_priv.event_queue)) { - return; - } -#endif - - al_wait_for_event(game->_priv.event_queue, &ev); - - if (game->handlers.event) { - if ((*game->handlers.event)(game, &ev)) { - continue; - } - } - - if ((ev.type == ALLEGRO_EVENT_TIMER) && (ev.timer.source == game->_priv.timer)) { - TickGamestates(game); - } else if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { -#ifdef __EMSCRIPTEN__ - libsuperderpy_mainloop_exit(game); -#endif - break; - } else if (ev.type == ALLEGRO_EVENT_DISPLAY_HALT_DRAWING) { - PrintConsole(game, "Engine halted."); - game->_priv.draw = false; - al_stop_timer(game->_priv.timer); - al_detach_voice(game->audio.v); - FreezeGamestates(game); - al_acknowledge_drawing_halt(game->display); - } else if (ev.type == ALLEGRO_EVENT_DISPLAY_RESUME_DRAWING) { - game->_priv.draw = true; - al_acknowledge_drawing_resume(game->display); - PrintConsole(game, "Engine resumed."); - ReloadGamestates(game); - UnfreezeGamestates(game); - al_attach_mixer_to_voice(game->audio.mixer, game->audio.v); - al_resume_timer(game->_priv.timer); - } else if (ev.type == ALLEGRO_EVENT_DISPLAY_RESIZE) { - al_acknowledge_resize(game->display); - SetupViewport(game, game->viewport_config); - } else if ((game->config.debug) && (game->_priv.debug.autopause) && (ev.type == ALLEGRO_EVENT_DISPLAY_SWITCH_OUT)) { - PauseExecution(game); - } else if ((game->config.debug) && (game->_priv.debug.autopause) && (ev.type == ALLEGRO_EVENT_DISPLAY_SWITCH_IN)) { - if (game->_priv.debug.livereload) { - ReloadCode(game); - } - ResumeExecution(game); - } -#ifdef ALLEGRO_ANDROID - else if ((ev.type == ALLEGRO_EVENT_KEY_CHAR) && ((ev.keyboard.keycode == ALLEGRO_KEY_MENU) || (ev.keyboard.keycode == ALLEGRO_KEY_TILDE) || (ev.keyboard.keycode == ALLEGRO_KEY_BACKQUOTE))) { -#else - else if ((ev.type == ALLEGRO_EVENT_KEY_CHAR) && ((ev.keyboard.keycode == ALLEGRO_KEY_TILDE) || (ev.keyboard.keycode == ALLEGRO_KEY_BACKQUOTE))) { -#endif - game->_priv.showconsole = !game->_priv.showconsole; - if ((ev.keyboard.modifiers & ALLEGRO_KEYMOD_CTRL) && (game->config.debug)) { - game->_priv.showtimeline = game->_priv.showconsole; - } - } else if ((ev.type == ALLEGRO_EVENT_KEY_DOWN) && (game->config.debug) && (ev.keyboard.keycode == ALLEGRO_KEY_F1)) { - if (!game->_priv.paused) { - PauseExecution(game); - } else { - ReloadCode(game); - ResumeExecution(game); - } - } else if ((ev.type == ALLEGRO_EVENT_KEY_DOWN) && (game->config.debug) && (ev.keyboard.keycode == ALLEGRO_KEY_F9)) { - al_set_timer_speed(game->_priv.timer, ALLEGRO_BPS_TO_SECS(60.0)); - game->_priv.showconsole = true; - PrintConsole(game, "DEBUG: Gameplay speed: 1.00x"); - } else if ((ev.type == ALLEGRO_EVENT_KEY_DOWN) && (game->config.debug) && (ev.keyboard.keycode == ALLEGRO_KEY_F10)) { - double speed = ALLEGRO_BPS_TO_SECS(al_get_timer_speed(game->_priv.timer)); // inverting - speed -= 10; - if (speed < 10) { speed = 10; } - al_set_timer_speed(game->_priv.timer, ALLEGRO_BPS_TO_SECS(speed)); - game->_priv.showconsole = true; - PrintConsole(game, "DEBUG: Gameplay speed: %.2fx", speed / 60.0); - } else if ((ev.type == ALLEGRO_EVENT_KEY_DOWN) && (game->config.debug) && (ev.keyboard.keycode == ALLEGRO_KEY_F11)) { - double speed = ALLEGRO_BPS_TO_SECS(al_get_timer_speed(game->_priv.timer)); // inverting - speed += 10; - if (speed > 600) { speed = 600; } - al_set_timer_speed(game->_priv.timer, ALLEGRO_BPS_TO_SECS(speed)); - game->_priv.showconsole = true; - PrintConsole(game, "DEBUG: Gameplay speed: %.2fx", speed / 60.0); - } else if ((ev.type == ALLEGRO_EVENT_KEY_DOWN) && (ev.keyboard.keycode == ALLEGRO_KEY_F12)) { - DrawGamestates(game); - int flags = al_get_new_bitmap_flags(); - al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP); - ALLEGRO_BITMAP* bitmap = al_create_bitmap(al_get_display_width(game->display), al_get_display_height(game->display)); - al_set_new_bitmap_flags(flags); - ALLEGRO_BITMAP* target = al_get_target_bitmap(); - al_set_target_bitmap(bitmap); - al_draw_bitmap(al_get_backbuffer(game->display), 0, 0, 0); - al_set_target_bitmap(target); - PrintConsole(game, "Screenshot made! Storing..."); - - struct ScreenshotThreadData* data = malloc(sizeof(struct ScreenshotThreadData)); - data->game = game; - data->bitmap = bitmap; -#ifndef LIBSUPERDERPY_SINGLE_THREAD - al_run_detached_thread(ScreenshotThread, data); -#else - ScreenshotThread(data); -#endif - } - EventGamestates(game, &ev); - } - } while (true); - -#ifndef __EMSCRIPTEN__ - if (game->handlers.destroy) { +SYMBOL_INTERNAL void libsuperderpy_emscripten_mainloop(void* game) { + if (!libsuperderpy_mainloop(game)) { libsuperderpy_destroy(game); + free(game); + printf("Halted.\n"); + emscripten_cancel_main_loop(); } +} +#endif + +SYMBOL_EXPORT int libsuperderpy_run(struct Game* game) { + int ret = libsuperderpy_start(game); + if (ret) { + return ret; + } +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop_arg(libsuperderpy_emscripten_mainloop, game, 0, true); + return 0; +#else + while (libsuperderpy_mainloop(game)) {}; + libsuperderpy_destroy(game); return 0; #endif } @@ -715,7 +443,7 @@ SYMBOL_EXPORT void libsuperderpy_destroy(struct Game* game) { al_destroy_mixer(game->audio.fx); al_destroy_mixer(game->audio.music); al_destroy_mixer(game->audio.mixer); - al_destroy_voice(game->audio.v); + al_destroy_voice(game->audio.v); // FIXME: doesn't seem to work in Chromium under Emscripten al_destroy_cond(game->_priv.texture_sync_cond); al_destroy_mutex(game->_priv.texture_sync_mutex); al_uninstall_audio(); diff --git a/src/libsuperderpy.h b/src/libsuperderpy.h index bf7e241..72ad492 100644 --- a/src/libsuperderpy.h +++ b/src/libsuperderpy.h @@ -49,6 +49,7 @@ struct GamestateResources; #include "character.h" #include "config.h" #include "gamestate.h" +#include "mainloop.h" #include "maths.h" #include "shader.h" #include "timeline.h" @@ -100,7 +101,6 @@ struct Game { struct { struct Gamestate* gamestates; /*!< List of known gamestates. */ - bool gamestate_scheduled; /*!< Whether there's some gamestate lifecycle management work to do. */ ALLEGRO_FONT* font_console; /*!< Font used in game console. */ ALLEGRO_FONT* font_bsod; /*!< Font used in Blue Screens of Derp. */ char console[5][1024]; @@ -135,8 +135,6 @@ struct Game { struct List *garbage, *timelines, *shaders, *bitmaps[LIBSUPERDERPY_BITMAP_HASHMAP_BUCKETS]; - bool draw; - double timestamp; struct { @@ -188,20 +186,8 @@ struct Game { }; struct Game* libsuperderpy_init(int argc, char** argv, const char* name, struct Viewport viewport); +int libsuperderpy_start(struct Game* game); int libsuperderpy_run(struct Game* game); void libsuperderpy_destroy(struct Game* game); -extern int Gamestate_ProgressCount; -void Gamestate_ProcessEvent(struct Game* game, struct GamestateResources* data, ALLEGRO_EVENT* ev); -void Gamestate_Logic(struct Game* game, struct GamestateResources* data, double delta); -void Gamestate_Draw(struct Game* game, struct GamestateResources* data); -void* Gamestate_Load(struct Game* game, void (*progress)(struct Game*)); -void Gamestate_PostLoad(struct Game* game, struct GamestateResources* data); -void Gamestate_Unload(struct Game* game, struct GamestateResources* data); -void Gamestate_Start(struct Game* game, struct GamestateResources* data); -void Gamestate_Stop(struct Game* game, struct GamestateResources* data); -void Gamestate_Reload(struct Game* game, struct GamestateResources* data); -void Gamestate_Pause(struct Game* game, struct GamestateResources* data); -void Gamestate_Resume(struct Game* game, struct GamestateResources* data); - #endif diff --git a/src/mainloop.c b/src/mainloop.c new file mode 100644 index 0000000..09d55d5 --- /dev/null +++ b/src/mainloop.c @@ -0,0 +1,351 @@ +/*! \file mainloop.c + * \brief Mainloop handling. + */ +/* + * Copyright (c) Sebastian Krzyszkowiak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Also, ponies. + */ + +#include "internal.h" +#include "libsuperderpy.h" + +static inline void HandleEvent(struct Game* game, ALLEGRO_EVENT* ev) { + switch (ev->type) { + case ALLEGRO_EVENT_TIMER: + if (ev->timer.source == game->_priv.timer) { + TickGamestates(game); + } + break; + + case ALLEGRO_EVENT_DISPLAY_HALT_DRAWING: + PauseExecution(game); + al_acknowledge_drawing_halt(game->display); + break; + + case ALLEGRO_EVENT_DISPLAY_RESUME_DRAWING: + al_acknowledge_drawing_resume(game->display); + ReloadGamestates(game); + ResumeExecution(game); + break; + + case ALLEGRO_EVENT_DISPLAY_RESIZE: + al_acknowledge_resize(game->display); + SetupViewport(game, game->viewport_config); + break; + + case ALLEGRO_EVENT_KEY_DOWN: +#ifdef ALLEGRO_ANDROID + if ((ev->keyboard.keycode == ALLEGRO_KEY_MENU) || (ev->keyboard.keycode == ALLEGRO_KEY_TILDE) || (ev->keyboard.keycode == ALLEGRO_KEY_BACKQUOTE)) { +#else + if ((ev->keyboard.keycode == ALLEGRO_KEY_TILDE) || (ev->keyboard.keycode == ALLEGRO_KEY_BACKQUOTE)) { +#endif + game->_priv.showconsole = !game->_priv.showconsole; + if ((ev->keyboard.modifiers & ALLEGRO_KEYMOD_CTRL) && (game->config.debug)) { + game->_priv.showtimeline = game->_priv.showconsole; + } + } + + if (ev->keyboard.keycode == ALLEGRO_KEY_F12) { + DrawGamestates(game); + int flags = al_get_new_bitmap_flags(); + al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP); + ALLEGRO_BITMAP* bitmap = al_create_bitmap(al_get_display_width(game->display), al_get_display_height(game->display)); + al_set_new_bitmap_flags(flags); + ALLEGRO_BITMAP* target = al_get_target_bitmap(); + al_set_target_bitmap(bitmap); + al_draw_bitmap(al_get_backbuffer(game->display), 0, 0, 0); + al_set_target_bitmap(target); + PrintConsole(game, "Screenshot made! Storing..."); + + struct ScreenshotThreadData* data = malloc(sizeof(struct ScreenshotThreadData)); + data->game = game; + data->bitmap = bitmap; +#ifndef LIBSUPERDERPY_SINGLE_THREAD + al_run_detached_thread(ScreenshotThread, data); +#else + ScreenshotThread(data); +#endif + } + + break; + default: + break; + } +} + +static inline void HandleDebugEvent(struct Game* game, ALLEGRO_EVENT* ev) { + switch (ev->type) { + case ALLEGRO_EVENT_DISPLAY_SWITCH_OUT: + if (game->_priv.debug.autopause) { + PrintConsole(game, "DEBUG: autopause"); + PauseExecution(game); + } + break; + + case ALLEGRO_EVENT_DISPLAY_SWITCH_IN: + if (game->_priv.debug.autopause) { + if (game->_priv.debug.livereload) { + ReloadCode(game); + } + ResumeExecution(game); + } + break; + + case ALLEGRO_EVENT_KEY_DOWN: + + switch (ev->keyboard.keycode) { + case ALLEGRO_KEY_F1: + if (!game->_priv.paused) { + PauseExecution(game); + } else { + ReloadCode(game); + ResumeExecution(game); + } + break; + case ALLEGRO_KEY_F9: + al_set_timer_speed(game->_priv.timer, ALLEGRO_BPS_TO_SECS(60.0)); + game->_priv.showconsole = true; + PrintConsole(game, "DEBUG: Gameplay speed: 1.00x"); + break; + case ALLEGRO_KEY_F10: { + double speed = ALLEGRO_BPS_TO_SECS(al_get_timer_speed(game->_priv.timer)); // inverting + speed -= 10; + if (speed < 10) { speed = 10; } + al_set_timer_speed(game->_priv.timer, ALLEGRO_BPS_TO_SECS(speed)); + game->_priv.showconsole = true; + PrintConsole(game, "DEBUG: Gameplay speed: %.2fx", speed / 60.0); + } break; + case ALLEGRO_KEY_F11: { + double speed = ALLEGRO_BPS_TO_SECS(al_get_timer_speed(game->_priv.timer)); // inverting + speed += 10; + if (speed > 600) { speed = 600; } + al_set_timer_speed(game->_priv.timer, ALLEGRO_BPS_TO_SECS(speed)); + game->_priv.showconsole = true; + PrintConsole(game, "DEBUG: Gameplay speed: %.2fx", speed / 60.0); + } break; + } + + break; + default: + break; + } +} + +static inline bool MainloopTick(struct Game* game) { + if (game->_priv.paused) { + return true; + } + + struct Gamestate* tmp = game->_priv.gamestates; + + game->_priv.loading.toLoad = 0; + game->_priv.loading.loaded = 0; + game->loading_progress = 0; + + // TODO: support gamestate dependences/ordering + while (tmp) { + if (tmp->pending_stop) { + PrintConsole(game, "Stopping gamestate \"%s\"...", tmp->name); + game->_priv.current_gamestate = tmp; + (*tmp->api->Gamestate_Stop)(game, tmp->data); + tmp->started = false; + tmp->pending_stop = false; + PrintConsole(game, "Gamestate \"%s\" stopped successfully.", tmp->name); + } + + if (tmp->pending_load) { game->_priv.loading.toLoad++; } + tmp = tmp->next; + } + + tmp = game->_priv.gamestates; + + while (tmp) { + if (tmp->pending_unload) { + PrintConsole(game, "Unloading gamestate \"%s\"...", tmp->name); + al_stop_timer(game->_priv.timer); + tmp->loaded = false; + tmp->pending_unload = false; + game->_priv.current_gamestate = tmp; + (*tmp->api->Gamestate_Unload)(game, tmp->data); + al_resume_timer(game->_priv.timer); + PrintConsole(game, "Gamestate \"%s\" unloaded successfully.", tmp->name); + } + if (tmp->pending_load) { + al_stop_timer(game->_priv.timer); + if (tmp->showLoading) { + (*game->_priv.loading.gamestate->api->Gamestate_Start)(game, game->_priv.loading.gamestate->data); + } + + if (!tmp->api) { + if (!OpenGamestate(game, tmp) || !LinkGamestate(game, tmp)) { + tmp->pending_load = false; + tmp->pending_start = false; + tmp->next = tmp; + continue; + } + } + if (tmp->api) { + PrintConsole(game, "Loading gamestate \"%s\"...", tmp->name); + game->_priv.loading.progress = 0; + + game->_priv.loading.current = tmp; + game->_priv.current_gamestate = tmp; + + struct GamestateLoadingThreadData data = {.game = game, .gamestate = tmp, .bitmap_flags = al_get_new_bitmap_flags()}; + game->_priv.loading.inProgress = true; + double time = al_get_time(); + game->_priv.loading.time = time; + + CalculateProgress(game); +#ifndef LIBSUPERDERPY_SINGLE_THREAD + al_run_detached_thread(GamestateLoadingThread, &data); + while (game->_priv.loading.inProgress) { + DrawGamestates(game); + al_set_target_backbuffer(game->display); + double delta = al_get_time() - game->_priv.loading.time; + if (tmp->showLoading) { + (*game->_priv.loading.gamestate->api->Gamestate_Logic)(game, game->_priv.loading.gamestate->data, delta); + (*game->_priv.loading.gamestate->api->Gamestate_Draw)(game, game->_priv.loading.gamestate->data); + if (game->handlers.postdraw) { + game->handlers.postdraw(game); + } + } + game->_priv.loading.time += delta; + game->time += delta; // TODO: ability to disable passing time during loading + if (game->_priv.texture_sync) { + al_convert_memory_bitmaps(); + game->_priv.texture_sync = false; + al_signal_cond(game->_priv.texture_sync_cond); + game->_priv.loading.time = al_get_time(); + } + DrawConsole(game); + al_flip_display(); + } +#else + GamestateLoadingThread(&data); + al_convert_memory_bitmaps(); +#endif + + al_set_new_bitmap_flags(data.bitmap_flags); + + if (tmp->api->Gamestate_PostLoad) { + PrintConsole(game, "[%s] Post-loading...", tmp->name); + tmp->api->Gamestate_PostLoad(game, tmp->data); + } + + game->_priv.loading.progress++; + CalculateProgress(game); + PrintConsole(game, "Gamestate \"%s\" loaded successfully in %f seconds.", tmp->name, al_get_time() - time); + game->_priv.loading.loaded++; + + tmp->loaded = true; + tmp->pending_load = false; + } + if (tmp->showLoading) { + (*game->_priv.loading.gamestate->api->Gamestate_Stop)(game, game->_priv.loading.gamestate->data); + } + al_resume_timer(game->_priv.timer); + game->_priv.timestamp = al_get_time(); + } + + tmp = tmp->next; + } + + if (game->_priv.loading.loaded) { + ReloadShaders(game, false); + } + + bool gameActive = false; + tmp = game->_priv.gamestates; + + while (tmp) { + if ((tmp->pending_start) && (tmp->loaded)) { + PrintConsole(game, "Starting gamestate \"%s\"...", tmp->name); + al_stop_timer(game->_priv.timer); + game->_priv.current_gamestate = tmp; + tmp->started = true; + tmp->pending_start = false; + (*tmp->api->Gamestate_Start)(game, tmp->data); + al_resume_timer(game->_priv.timer); + game->_priv.timestamp = al_get_time(); + PrintConsole(game, "Gamestate \"%s\" started successfully.", tmp->name); + } + + if ((tmp->started) || (tmp->pending_start) || (tmp->pending_load)) { + gameActive = true; + } + tmp = tmp->next; + } + + if (!gameActive) { + PrintConsole(game, "No gamestates left, exiting..."); + return false; + } + + al_convert_memory_bitmaps(); + + double delta = al_get_time() - game->_priv.timestamp; + game->_priv.timestamp += delta; + delta *= ALLEGRO_BPS_TO_SECS(al_get_timer_speed(game->_priv.timer) / (1 / 60.f)); + game->time += delta; + + LogicGamestates(game, delta); + DrawGamestates(game); + + DrawConsole(game); + al_flip_display(); + return true; +} + +static inline bool MainloopEvents(struct Game* game) { + do { + ALLEGRO_EVENT ev; + + if (game->_priv.paused) { + // there's no frame flipping when paused, so avoid pointless busylooping + al_wait_for_event(game->_priv.event_queue, &ev); + } else if (!al_get_next_event(game->_priv.event_queue, &ev)) { + break; + } + + if (game->handlers.event) { + if ((*game->handlers.event)(game, &ev)) { + continue; + } + } + + if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { + EventGamestates(game, &ev); + return false; + } + + HandleEvent(game, &ev); + + if (game->config.debug) { + HandleDebugEvent(game, &ev); + } + + EventGamestates(game, &ev); + + } while (!al_is_event_queue_empty(game->_priv.event_queue)); + return true; +} + +SYMBOL_EXPORT bool libsuperderpy_mainloop(struct Game* game) { + ClearGarbage(game); + return MainloopEvents(game) && MainloopTick(game); +} diff --git a/src/mainloop.h b/src/mainloop.h new file mode 100644 index 0000000..5bfdfab --- /dev/null +++ b/src/mainloop.h @@ -0,0 +1,28 @@ +/*! \file mainloop.h + * \brief Mainloop handling. + */ +/* + * Copyright (c) Sebastian Krzyszkowiak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSUPERDERPY_MAINLOOP_H +#define LIBSUPERDERPY_MAINLOOP_H + +#include "libsuperderpy.h" + +bool libsuperderpy_mainloop(struct Game* game); + +#endif