diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e60427772d..4fedc4cf515 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1042,7 +1042,7 @@ if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") set(PLAYER_JS_POSTJS "${CMAKE_CURRENT_SOURCE_DIR}/resources/emscripten/emscripten-post.js") set(PLAYER_JS_SHELL "${CMAKE_CURRENT_SOURCE_DIR}/resources/emscripten/emscripten-shell.html") - set_property(TARGET ${EXE_NAME} PROPERTY LINK_FLAGS "-s ALLOW_MEMORY_GROWTH -s ASYNCIFY -s ASYNCIFY_IGNORE_INDIRECT -s MINIFY_HTML=0 -s MODULARIZE -s EXPORT_NAME='createEasyRpgPlayer' --bind --pre-js ${PLAYER_JS_PREJS} --post-js ${PLAYER_JS_POSTJS} -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['$autoResumeAudioContext','$dynCall']") + set_property(TARGET ${EXE_NAME} PROPERTY LINK_FLAGS "-s ALLOW_MEMORY_GROWTH -s MINIFY_HTML=0 -s MODULARIZE -s EXIT_RUNTIME -s EXPORT_NAME='createEasyRpgPlayer' --bind --pre-js ${PLAYER_JS_PREJS} --post-js ${PLAYER_JS_POSTJS} -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['$autoResumeAudioContext','$dynCall']") set_source_files_properties("src/platform/sdl/main.cpp" PROPERTIES OBJECT_DEPENDS "${PLAYER_JS_PREJS};${PLAYER_JS_POSTJS};${PLAYER_JS_SHELL}") @@ -1054,6 +1054,7 @@ if(${PLAYER_BUILD_EXECUTABLE} AND ${PLAYER_TARGET_PLATFORM} STREQUAL "SDL2") target_compile_options(${PROJECT_NAME} PUBLIC -fno-exceptions) target_sources(${PROJECT_NAME} PRIVATE + src/platform/emscripten/clock.h src/platform/emscripten/interface.cpp src/platform/emscripten/interface.h) diff --git a/Makefile.am b/Makefile.am index 7b638936e79..33e0c8eaf24 100644 --- a/Makefile.am +++ b/Makefile.am @@ -480,6 +480,9 @@ EXTRA_DIST += \ src/platform/android/filesystem_saf.h \ src/platform/android/org_easyrpg_player_player_EasyRpgPlayerActivity.cpp \ src/platform/android/org_easyrpg_player_player_EasyRpgPlayerActivity.h \ + src/platform/emscripten/clock.h \ + src/platform/emscripten/interface.cpp \ + src/platform/emscripten/interface.h \ src/platform/libretro/audio.cpp \ src/platform/libretro/audio.h \ src/platform/libretro/clock.cpp \ diff --git a/resources/emscripten/emscripten-post.js b/resources/emscripten/emscripten-post.js index f7c79754592..53a90053d77 100644 --- a/resources/emscripten/emscripten-post.js +++ b/resources/emscripten/emscripten-post.js @@ -40,10 +40,9 @@ Module.initApi = function() { const result = new Uint8Array(file.currentTarget.result); var buf = Module._malloc(result.length); Module.HEAPU8.set(result, buf); - Module.api_private.uploadSavegameStep2(slot, buf, result.length).then(function() { - Module._free(buf); - Module.api.refreshScene(); - }); + Module.api_private.uploadSavegameStep2(slot, buf, result.length); + Module._free(buf); + Module.api.refreshScene(); }; reader.readAsArrayBuffer(save); }); diff --git a/resources/emscripten/emscripten-pre.js b/resources/emscripten/emscripten-pre.js index fa337511a19..3ffbd3a9c02 100644 --- a/resources/emscripten/emscripten-pre.js +++ b/resources/emscripten/emscripten-pre.js @@ -1,6 +1,7 @@ // Note: The `Module` context is already initialized as an // empty object by emscripten even before the pre script -Module = { ...Module, +// Do not replace Object.assign with "...Module". Breaks the Acorn optimizer +Module = Object.assign({}, Module, { preRun: [], postRun: [], @@ -42,7 +43,7 @@ Module = { ...Module, Module.totalDependencies = Math.max(Module.totalDependencies, left); Module.setStatus(left ? `Preparing... (${Module.totalDependencies - left}/${Module.totalDependencies})` : 'Downloading game data...'); } -}; +}); /** * Parses the current location query to setup a specific game diff --git a/src/platform/clock.h b/src/platform/clock.h index 2a43fa1b394..160523ec0dd 100644 --- a/src/platform/clock.h +++ b/src/platform/clock.h @@ -34,6 +34,9 @@ using Platform_Clock = NxClock; #elif defined(__vita__) #include "platform/psvita/clock.h" using Platform_Clock = Psp2Clock; +#elif defined(EMSCRIPTEN) +#include "platform/emscripten/clock.h" +using Platform_Clock = EmscriptenClock; #elif defined(USE_LIBRETRO) // Only use libretro clock on platforms with no custom clock #include "platform/libretro/clock.h" diff --git a/src/platform/emscripten/clock.h b/src/platform/emscripten/clock.h new file mode 100644 index 00000000000..3e787266cf3 --- /dev/null +++ b/src/platform/emscripten/clock.h @@ -0,0 +1,56 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player 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. + * + * EasyRPG Player 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 EasyRPG Player. If not, see . + */ + +#ifndef EP_EMSCRIPTEN_CLOCK_H +#define EP_EMSCRIPTEN_CLOCK_H + +#include + +struct EmscriptenClock { + using clock = std::chrono::steady_clock; + + using rep = clock::rep; + using period = clock::period; + using duration = clock::duration; + using time_point = clock::time_point; + + static constexpr bool is_steady = clock::is_steady; + + /** Get current time */ + static time_point now(); + + /** Sleep for the specified duration */ + template + static void SleepFor(std::chrono::duration dt); + + static constexpr const char* Name(); +}; + +inline EmscriptenClock::time_point EmscriptenClock::now() { + return clock::now(); +} + +template +inline void EmscriptenClock::SleepFor(std::chrono::duration dt) { + // Browser handles sleep +} + +constexpr const char* EmscriptenClock::Name() { + return "Emscripten"; +} + +#endif diff --git a/src/platform/emscripten/interface.cpp b/src/platform/emscripten/interface.cpp index 4de70bce8a3..ec9a154efea 100644 --- a/src/platform/emscripten/interface.cpp +++ b/src/platform/emscripten/interface.cpp @@ -58,7 +58,7 @@ bool Emscripten_Interface_Private::UploadSavegameStep2(int slot, int buffer_addr auto fs = FileFinder::Save(); std::string name = Scene_Save::GetSaveFilename(fs, slot); - std::istream is(new Filesystem_Stream::InputMemoryStreamBuf(lcf::Span(reinterpret_cast(buffer_addr), size))); + std::istream is(new Filesystem_Stream::InputMemoryStreamBufView(lcf::Span(reinterpret_cast(buffer_addr), size))); if (!lcf::LSD_Reader::Load(is)) { Output::Warning("Selected file is not a valid savegame"); diff --git a/src/player.cpp b/src/player.cpp index 773cc6794d1..b3b57169e89 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -206,8 +206,11 @@ void Player::Run() { Game_Clock::ResetFrame(Game_Clock::now()); // Main loop +#ifdef EMSCRIPTEN + emscripten_set_main_loop(Player::MainLoop, 0, 0); +#elif defined(USE_LIBRETRO) // libretro invokes the MainLoop through a retro_run-callback -#ifndef USE_LIBRETRO +#else while (Transition::instance().IsActive() || (Scene::instance && Scene::instance->type != Scene::Null)) { MainLoop(); } @@ -249,9 +252,6 @@ void Player::MainLoop() { auto frame_limit = DisplayUi->GetFrameLimit(); if (frame_limit == Game_Clock::duration()) { -#ifdef EMSCRIPTEN - emscripten_sleep(0); -#endif return; } @@ -261,11 +261,6 @@ void Player::MainLoop() { if (Game_Clock::now() < next) { iframe.End(); Game_Clock::SleepFor(next - now); - } else { -#ifdef EMSCRIPTEN - // Yield back to browser once per frame - emscripten_sleep(0); -#endif } } @@ -381,6 +376,8 @@ int Player::GetFrames() { void Player::Exit() { Graphics::UpdateSceneCallback(); #ifdef EMSCRIPTEN + emscripten_cancel_main_loop(); + BitmapRef surface = DisplayUi->GetDisplaySurface(); std::string message = "It's now safe to turn off\n your browser."; Text::Draw(*surface, 84, DisplayUi->GetHeight() / 2 - 30, *Font::DefaultBitmapFont(), Color(221, 123, 64, 255), message);