From dddec707e294ec0c7fc035db0fb9d817caccaa86 Mon Sep 17 00:00:00 2001 From: Semphriss <66701383+Semphriss@users.noreply.github.com> Date: Sat, 6 Nov 2021 14:44:02 -0400 Subject: [PATCH] WASM now uses IndexedDB and persistence (#1869) Co-authored-by: Semphris --- CMakeLists.txt | 6 ++-- mk/emscripten/template.html.in | 61 +++++++++++++++++++++++++++------ src/editor/editor.cpp | 11 +++++- src/supertux/main.cpp | 11 ++++++ src/supertux/screen_manager.cpp | 6 ++++ 5 files changed, 81 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b53038a2142..60c2cbb208d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,9 +83,9 @@ if(EMSCRIPTEN) if(CMAKE_BUILD_TYPE MATCHES Debug) set(USE_FLAGS "${USE_FLAGS} -fsanitize=undefined -sSAFE_HEAP=1 -sASSERTIONS=1 -sDEMANGLE_SUPPORT=1") endif() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${USE_FLAGS} -sERROR_ON_UNDEFINED_SYMBOLS=0") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${USE_FLAGS} -sERROR_ON_UNDEFINED_SYMBOLS=0") - set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} ${USE_FLAGS} -sFULL_ES2=1 -sERROR_ON_UNDEFINED_SYMBOLS=0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${USE_FLAGS} -sERROR_ON_UNDEFINED_SYMBOLS=0 -lidbfs.js") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${USE_FLAGS} -sERROR_ON_UNDEFINED_SYMBOLS=0 -lidbfs.js") + set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} ${USE_FLAGS} -sFULL_ES2=1 -sERROR_ON_UNDEFINED_SYMBOLS=0 -lidbfs.js") endif() # TODO: Add " OR ANDROID OR IOS" to this diff --git a/mk/emscripten/template.html.in b/mk/emscripten/template.html.in index a18816528e4..2a44d8735ef 100644 --- a/mk/emscripten/template.html.in +++ b/mk/emscripten/template.html.in @@ -192,11 +192,28 @@ document.getElementById("output").style.display = "none"; } + var data_persistent = false; + if (navigator.storage && navigator.storage.persist) { + navigator.storage.persist().then((persists) => { + if (!persists) { + alert("Your browser denied persistent storage. That means your data could be cleared next time you open the game.\n\nIf you just received a prompt asking you if you want data to persist, you may ignore this message.\n\nChrome and Chromium-based browsers (Edge, Opera, Brave...) will not ask the user; instead, the browser will choose by itself based on if it considers the site important. It detemines which sites are important based on certain data, such as how often you visit the site, what you do, etc. If you want to choose whether or not to save your progress, you may use Firefox instead.\n\nYou can read more at https://web.dev/persistent-storage/#how-is-permission-granted\n\nIf you want to choose to save your data, please use Firefox."); + } else { + data_persistent = true; + } + }); + } else { + alert("Your browser does not support persistent storage!\n\nYour browser may delete your progress anytime. Make sure to backup your important files!"); + } + + window.supertux2_ispersistent = function() { + return data_persistent ? 1 : 0; + } + var statusElement = document.getElementById("status"), - progressElement = document.getElementById("progress"), - progressDescElement = document.getElementById("progress_desc"), - overlayElement = document.getElementById("overlay"), - spinnerElement = document.getElementById("spinner"); + progressElement = document.getElementById("progress"), + progressDescElement = document.getElementById("progress_desc"), + overlayElement = document.getElementById("overlay"), + spinnerElement = document.getElementById("spinner"); var lastUpdate = Date.now() / 1000; var lastStep = 0; @@ -349,26 +366,33 @@ const root = "/home/web_user/.local/share/supertux2/"; window.supertux_loadFiles = function() { + // Loading the config file from localStorage is needed, even though the + // rest of the files are stored in IndexedDB, managed by Emscripten. + // Check the supertux_saveFiles function below for details. for (var key of Object.keys(localStorage)) { - if (key !== "config" && !key.match("^profile[0-9]+/")) + if (key !== "supertux2_config" /*&& !key.match("^profile[0-9]+/")*/) continue; - if (key.indexOf("/") !== -1) { + keyfilename = key.replace(/^supertux2_/, ""); + + if (keyfilename.indexOf("/") !== -1) { try { - FS.mkdir(root + key.substr(0, key.indexOf("/"))); + FS.mkdir(root + keyfilename.substr(0, keyfilename.indexOf("/"))); } catch { // Folder probably already exists } } - FS.writeFile(root + key, localStorage.getItem(key)); + FS.writeFile(root + keyfilename, localStorage.getItem(key)); } } window.supertux_saveFiles = function() { + FS.syncfs((err) => { console.log(err); }); + function save(file) { try { - localStorage.setItem(file, FS.readFile(root + file, { encoding: "utf8" })); + localStorage.setItem("supertux2_" + file, FS.readFile(root + file, { encoding: "utf8" })); return true; } catch(e) { console.error(e); @@ -377,8 +401,13 @@ } } + // IndexedDB can't save fast enough to be called when the window is closed, + // so save the config file in localStorage just in case it wasn't flushed + // to disk save("config"); + // Other files use IndexedDB instead of localStorage + /* var data_folder = FS.lookupPath(root); for (var folder_name in data_folder.node.contents) { @@ -389,7 +418,7 @@ save(folder_name + "/" + file_name); } } - + */ } window.supertux_download = function(path) { @@ -477,6 +506,18 @@ } }); + var saving = false; + window.supertux2_syncfs = function() { + if (saving) return; + saving = true; + FS.syncfs((err) => { + if (err) + console.log(err); + + saving = false; + }); + } + window.onresize = tryResize; window.onunload = () => { Module.ccall("save_config", "void", [], []); diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index 94a2900e4f4..2bf2d18953e 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -19,6 +19,11 @@ #include #include +#ifdef EMSCRIPTEN +#include +#include +#endif + #include "audio/sound_manager.hpp" #include "control/input_manager.hpp" #include "editor/button_widget.hpp" @@ -542,7 +547,11 @@ Editor::quit_editor() Tile::draw_editor_images = false; ScreenManager::current()->pop_screen(); #ifdef __EMSCRIPTEN__ - Dialog::show_message(_("Don't forget that your levels and assets\naren't saved between sessions!\nIf you want to keep your levels, download them\nfrom the \"Manage Assets\" menu.")); + int persistent = EM_ASM_INT({ + return supertux2_ispersistent(); + }); + if (!persistent) + Dialog::show_message(_("Don't forget that your levels and assets\naren't saved between sessions!\nIf you want to keep your levels, download them\nfrom the \"Manage Assets\" menu.")); #endif }; diff --git a/src/supertux/main.cpp b/src/supertux/main.cpp index 3a1353d153b..40a6117c8a4 100644 --- a/src/supertux/main.cpp +++ b/src/supertux/main.cpp @@ -301,12 +301,23 @@ if (FileSystem::is_directory(olduserdir)) { } #endif +#ifdef EMSCRIPTEN + userdir = "/home/web_user/.local/share/supertux2/"; +#endif + if (!FileSystem::is_directory(userdir)) { FileSystem::mkdir(userdir); log_info << "Created SuperTux userdir: " << userdir << std::endl; } +#ifdef EMSCRIPTEN + EM_ASM({ + FS.mount(IDBFS, {}, "/home/web_user/.local/share/supertux2/"); + FS.syncfs(true, (err) => { console.log(err); }); + }); +#endif + if (!PHYSFS_setWriteDir(userdir.c_str())) { std::ostringstream msg; diff --git a/src/supertux/screen_manager.cpp b/src/supertux/screen_manager.cpp index ac5deb3692a..a83dba6f5da 100644 --- a/src/supertux/screen_manager.cpp +++ b/src/supertux/screen_manager.cpp @@ -609,6 +609,12 @@ void ScreenManager::loop_iter() SoundManager::current()->update(); handle_screen_switch(); + +#ifdef EMSCRIPTEN + EM_ASM({ + supertux2_syncfs(); + }); +#endif } #ifdef __EMSCRIPTEN__