From 58229f49b22dc1a13fc47d08f738805c696d5f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20B=C3=A1lint=20Misius?= Date: Sun, 18 Jun 2023 17:52:03 +0200 Subject: [PATCH] Emscripten: Support persistent storage --- meson.build | 5 +- src/PowderToy.cpp | 19 ++-- src/PowderToySDLEmscripten.cpp | 16 +++- src/client/http/requestmanager/Emscripten.cpp | 2 +- src/client/http/requestmanager/meson.build | 4 - src/common/platform/Android.cpp | 4 + src/common/platform/Darwin.cpp | 4 + src/common/platform/DdirCommon.cpp | 14 +++ src/common/platform/Emscripten.cpp | 92 +++++++++++++++++++ src/common/platform/Linux.cpp | 4 + src/common/platform/MainCommon.cpp | 9 ++ src/common/platform/Null.cpp | 4 + src/common/platform/Platform.h | 6 ++ src/common/platform/Posix.cpp | 4 - src/common/platform/meson.build | 20 ++++ 15 files changed, 188 insertions(+), 19 deletions(-) create mode 100644 src/common/platform/DdirCommon.cpp create mode 100644 src/common/platform/MainCommon.cpp diff --git a/meson.build b/meson.build index da54f1dfb..c2443be45 100644 --- a/meson.build +++ b/meson.build @@ -209,7 +209,10 @@ if host_platform == 'emscripten' '-s', 'WASM=1', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'FORCE_FILESYSTEM=1', - '-s', 'EXIT_RUNTIME=1', + '-s', 'EXIT_RUNTIME=0', + '-s', 'EXPORTED_RUNTIME_METHODS=ccall,cwrap', + '-s', 'FS_DEBUG', + '-lidbfs.js', ] emcc_args = [ '-s', 'USE_SDL=2', diff --git a/src/PowderToy.cpp b/src/PowderToy.cpp index a1183009e..2e991bcd0 100644 --- a/src/PowderToy.cpp +++ b/src/PowderToy.cpp @@ -179,9 +179,14 @@ struct ExplicitSingletons }; static std::unique_ptr explicitSingletons; -int main(int argc, char * argv[]) +int main(int argc, char *argv[]) { Platform::SetupCrt(); + return Platform::InvokeMain(argc, argv); +} + +int Main(int argc, char *argv[]) +{ Platform::Atexit([]() { SaveWindowPosition(); // Unregister dodgy error handlers so they don't try to show the blue screen when the window is closed @@ -254,22 +259,22 @@ int main(int argc, char * argv[]) } else { - auto ddir = std::unique_ptr(SDL_GetPrefPath(NULL, APPDATA), SDL_free); + auto ddir = Platform::DefaultDdir(); if (!Platform::FileExists("powder.pref")) { - if (ddir) + if (ddir.size()) { - if (!Platform::ChangeDir(ddir.get())) + if (!Platform::ChangeDir(ddir)) { perror("failed to chdir to default ddir"); - ddir.reset(); + ddir = {}; } } } - if (ddir) + if (ddir.size()) { - Platform::sharedCwd = ddir.get(); + Platform::sharedCwd = ddir; } } // We're now in the correct directory, time to get prefs. diff --git a/src/PowderToySDLEmscripten.cpp b/src/PowderToySDLEmscripten.cpp index d76dbc2c9..37a94fb7f 100644 --- a/src/PowderToySDLEmscripten.cpp +++ b/src/PowderToySDLEmscripten.cpp @@ -3,12 +3,23 @@ #include #include +namespace Platform +{ + void MaybeTriggerSyncFs(); +} + +static void MainLoopBody() +{ + EngineProcess(); + Platform::MaybeTriggerSyncFs(); +} + void SetFpsLimit(FpsLimit newFpsLimit) { static bool mainLoopSet = false; if (!mainLoopSet) { - emscripten_set_main_loop(EngineProcess, 0, 0); + emscripten_set_main_loop(MainLoopBody, 0, 0); mainLoopSet = true; } if (auto *fpsLimitVsync = std::get_if(&newFpsLimit)) @@ -28,8 +39,9 @@ void SetFpsLimit(FpsLimit newFpsLimit) } } +// Is actually only called once at startup, the real main loop body is MainLoopBody. void MainLoop() { SetFpsLimit(ui::Engine::Ref().GetFpsLimit()); - EngineProcess(); + MainLoopBody(); } diff --git a/src/client/http/requestmanager/Emscripten.cpp b/src/client/http/requestmanager/Emscripten.cpp index 1ef7d8ead..ad25be373 100644 --- a/src/client/http/requestmanager/Emscripten.cpp +++ b/src/client/http/requestmanager/Emscripten.cpp @@ -17,7 +17,7 @@ namespace http }; } -extern "C" void RequestManager_UpdateRequestStatusThunk(http::RequestHandleHttp *handle); +EMSCRIPTEN_KEEPALIVE extern "C" void RequestManager_UpdateRequestStatusThunk(http::RequestHandleHttp *handle); namespace http { diff --git a/src/client/http/requestmanager/meson.build b/src/client/http/requestmanager/meson.build index 3fd3287c7..84aa00aa0 100644 --- a/src/client/http/requestmanager/meson.build +++ b/src/client/http/requestmanager/meson.build @@ -6,10 +6,6 @@ if not enable_http client_files += files('Null.cpp') elif host_platform == 'emscripten' client_files += files('Emscripten.cpp') - project_link_args += [ - '-s', 'EXPORTED_FUNCTIONS=_main,_RequestManager_UpdateRequestStatusThunk', - '-s', 'EXPORTED_RUNTIME_METHODS=cwrap', - ] else client_files += files('Libcurl.cpp') endif diff --git a/src/common/platform/Android.cpp b/src/common/platform/Android.cpp index e2599ceff..d29c4d7f9 100644 --- a/src/common/platform/Android.cpp +++ b/src/common/platform/Android.cpp @@ -24,4 +24,8 @@ bool CanUpdate() { return false; } + +void SetupCrt() +{ +} } diff --git a/src/common/platform/Darwin.cpp b/src/common/platform/Darwin.cpp index b73a675aa..53cbb27d2 100644 --- a/src/common/platform/Darwin.cpp +++ b/src/common/platform/Darwin.cpp @@ -48,4 +48,8 @@ bool CanUpdate() { return false; } + +void SetupCrt() +{ +} } diff --git a/src/common/platform/DdirCommon.cpp b/src/common/platform/DdirCommon.cpp new file mode 100644 index 000000000..3895c6588 --- /dev/null +++ b/src/common/platform/DdirCommon.cpp @@ -0,0 +1,14 @@ +#include "Platform.h" +#include "common/String.h" +#include "Config.h" +#include +#include + +namespace Platform +{ +ByteString DefaultDdir() +{ + auto ddir = std::unique_ptr(SDL_GetPrefPath(NULL, APPDATA), SDL_free); + return ddir.get(); +} +} diff --git a/src/common/platform/Emscripten.cpp b/src/common/platform/Emscripten.cpp index 8965f6082..492f45186 100644 --- a/src/common/platform/Emscripten.cpp +++ b/src/common/platform/Emscripten.cpp @@ -1,5 +1,17 @@ #include "Platform.h" #include +#include +#include +#include +#include + +static std::atomic shouldSyncFs = false; +static bool syncFsInFlight = false; + +EMSCRIPTEN_KEEPALIVE extern "C" void Platform_SyncFsDone() +{ + syncFsInFlight = false; +} namespace Platform { @@ -37,4 +49,84 @@ void Atexit(ExitFunc exitFunc) void Exit(int code) { } + +ByteString DefaultDdir() +{ + return "/powder"; +} + +int InvokeMain(int argc, char *argv[]) +{ + EM_ASM({ + FS.syncfs(true, () => { + Module.ccall('MainJs', 'number', [ 'number', 'number' ], [ $0, $1 ]); + }); + }, argc, argv); + return 0; +} + +void MaybeTriggerSyncFs() +{ + if (!syncFsInFlight && shouldSyncFs.exchange(false, std::memory_order_relaxed)) + { + std::cerr << "invoking FS.syncfs" << std::endl; + syncFsInFlight = true; + EM_ASM({ + FS.syncfs(false, err => { + if (err) { + console.error(err); + } + Module.ccall('Platform_SyncFsDone', null, [], []); + }); + }); + } +} +} + +EMSCRIPTEN_KEEPALIVE extern "C" int MainJs(int argc, char *argv[]) +{ + return Main(argc, argv); +} + +EMSCRIPTEN_KEEPALIVE extern "C" void Platform_ShouldSyncFs() +{ + shouldSyncFs.store(true, std::memory_order_relaxed); +} + +namespace Platform +{ + void SetupCrt() + { + EM_ASM({ + let ddir = UTF8ToString($0); + let prefix = ddir + '/'; + let shouldSyncFs = Module.cwrap( + 'Platform_ShouldSyncFs', + null, + [] + ); + FS.trackingDelegate['onMovePath'] = function(oldpath, newpath) { + if (oldpath.startsWith(prefix) || newpath.startsWith(prefix)) { + shouldSyncFs(); + } + }; + FS.trackingDelegate['onDeletePath'] = function(path) { + if (path.startsWith(prefix)) { + shouldSyncFs(); + } + }; + FS.trackingDelegate['onWriteToFile'] = function(path, bytesWritten) { + if (path.startsWith(prefix)) { + shouldSyncFs(); + } + }; + FS.trackingDelegate['onMakeDirectory'] = function(path, mode) { + if (path.startsWith(prefix)) { + shouldSyncFs(); + } + }; + FS.mkdir(ddir); + FS.mount(IDBFS, {}, ddir); + }, DefaultDdir().c_str()); + } } diff --git a/src/common/platform/Linux.cpp b/src/common/platform/Linux.cpp index b2e87f7fe..0fe6f762e 100644 --- a/src/common/platform/Linux.cpp +++ b/src/common/platform/Linux.cpp @@ -109,4 +109,8 @@ bool Install() } return ok; } + +void SetupCrt() +{ +} } diff --git a/src/common/platform/MainCommon.cpp b/src/common/platform/MainCommon.cpp new file mode 100644 index 000000000..0fbb12859 --- /dev/null +++ b/src/common/platform/MainCommon.cpp @@ -0,0 +1,9 @@ +#include "Platform.h" + +namespace Platform +{ +int InvokeMain(int argc, char *argv[]) +{ + return Main(argc, argv); +} +} diff --git a/src/common/platform/Null.cpp b/src/common/platform/Null.cpp index 96cd3a4cd..3b62604d7 100644 --- a/src/common/platform/Null.cpp +++ b/src/common/platform/Null.cpp @@ -11,4 +11,8 @@ bool CanUpdate() { return false; } + +void SetupCrt() +{ +} } diff --git a/src/common/platform/Platform.h b/src/common/platform/Platform.h index 272555242..0daf779f9 100644 --- a/src/common/platform/Platform.h +++ b/src/common/platform/Platform.h @@ -62,4 +62,10 @@ namespace Platform using ExitFunc = void (*)(); void Atexit(ExitFunc exitFunc); void Exit(int code); + + ByteString DefaultDdir(); + + int InvokeMain(int argc, char *argv[]); } + +extern "C" int Main(int argc, char *argv[]); diff --git a/src/common/platform/Posix.cpp b/src/common/platform/Posix.cpp index b514ce991..2f7c80c3d 100644 --- a/src/common/platform/Posix.cpp +++ b/src/common/platform/Posix.cpp @@ -218,8 +218,4 @@ bool UpdateFinish() void UpdateCleanup() { } - -void SetupCrt() -{ -} } diff --git a/src/common/platform/meson.build b/src/common/platform/meson.build index 546a403a7..48cb92330 100644 --- a/src/common/platform/meson.build +++ b/src/common/platform/meson.build @@ -12,6 +12,10 @@ if host_platform == 'windows' 'Windows.cpp', 'ExitCommon.cpp', ) + powder_files += files( + 'MainCommon.cpp', + 'DdirCommon.cpp', + ) elif host_platform == 'darwin' can_install_enforce_no = true common_files += files( @@ -19,6 +23,10 @@ elif host_platform == 'darwin' 'Posix.cpp', 'ExitCommon.cpp', ) + powder_files += files( + 'MainCommon.cpp', + 'DdirCommon.cpp', + ) elif host_platform == 'android' can_install_enforce_no = true common_files += files( @@ -26,6 +34,10 @@ elif host_platform == 'android' 'Posix.cpp', 'ExitCommon.cpp', ) + powder_files += files( + 'MainCommon.cpp', + 'DdirCommon.cpp', + ) elif host_platform == 'emscripten' use_bluescreen = false can_install_enforce_no = true @@ -41,6 +53,10 @@ elif host_platform == 'linux' 'Posix.cpp', 'ExitCommon.cpp', ) + powder_files += files( + 'MainCommon.cpp', + 'DdirCommon.cpp', + ) else can_install_enforce_no = true common_files += files( @@ -48,6 +64,10 @@ else 'Posix.cpp', 'ExitCommon.cpp', ) + powder_files += files( + 'MainCommon.cpp', + 'DdirCommon.cpp', + ) endif conf_data.set('SET_WINDOW_ICON', set_window_icon ? 'true' : 'false') conf_data.set('PATH_SEP_CHAR', path_sep_char)