mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-03-30 05:41:05 +02:00
emscripten: Add support for automounting persistent storage before SDL_main.
Now apps can have persistent files available during SDL_main()/SDL_AppInit()
and don't have to mess with Emscripten-specific code to prepare the filesystem
for use.
(cherry picked from commit dcc177faa4)
This commit is contained in:
committed by
Sam Lantinga
parent
c546c5d335
commit
1fc5001f77
@@ -388,6 +388,10 @@ set_option(SDL_ASAN "Use AddressSanitizer to detect memory errors
|
||||
set_option(SDL_CCACHE "Use Ccache to speed up build" OFF)
|
||||
set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF)
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
option_string(SDL_EMSCRIPTEN_PERSISTENT_PATH "Path to mount Emscripten IDBFS at startup or '' to disable" "")
|
||||
endif()
|
||||
|
||||
set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION")
|
||||
|
||||
cmake_dependent_option(SDL_SHARED "Build a shared version of the library" ${SDL_SHARED_DEFAULT} ${SDL_SHARED_AVAILABLE} OFF)
|
||||
@@ -1652,6 +1656,11 @@ elseif(EMSCRIPTEN)
|
||||
# project. Uncomment at will for verbose cross-compiling -I/../ path info.
|
||||
sdl_compile_options(PRIVATE "-Wno-warn-absolute-paths")
|
||||
|
||||
if(NOT SDL_EMSCRIPTEN_PERSISTENT_PATH STREQUAL "")
|
||||
set(SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING "${SDL_EMSCRIPTEN_PERSISTENT_PATH}")
|
||||
sdl_link_dependency(idbfs LIBS idbfs.js)
|
||||
endif()
|
||||
|
||||
sdl_glob_sources(
|
||||
"${SDL3_SOURCE_DIR}/src/main/emscripten/*.c"
|
||||
"${SDL3_SOURCE_DIR}/src/main/emscripten/*.h"
|
||||
@@ -3982,6 +3991,7 @@ if(SDL_SHARED)
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_libraries(SDL3-shared PRIVATE ${SDL_CMAKE_DEPENDS})
|
||||
target_include_directories(SDL3-shared
|
||||
PRIVATE
|
||||
|
||||
@@ -346,6 +346,64 @@ all has to live in memory at runtime.
|
||||
[Emscripten's documentation on the matter](https://emscripten.org/docs/porting/files/packaging_files.html)
|
||||
gives other options and details, and is worth a read.
|
||||
|
||||
Please also read the next section on persistent storage, for a little help
|
||||
from SDL.
|
||||
|
||||
|
||||
## Automount persistent storage
|
||||
|
||||
The file tree in Emscripten is provided by MEMFS by default, which stores all
|
||||
files in RAM. This is often what you want, because it's fast and can be
|
||||
accessed with the usual synchronous i/o functions like fopen or SDL_IOFromFile.
|
||||
You can also write files to MEMFS, but when the browser tab goes away, so do
|
||||
the files. But we want things like high scores, save games, etc, to still
|
||||
exist if we reload the game later.
|
||||
|
||||
For this, Emscripten offers IDBFS, which backs files with the browser's
|
||||
[IndexedDB](https://en.wikipedia.org/wiki/IndexedDB) functionality.
|
||||
|
||||
To use this, the app has to mount the IDBFS filesystem somewhere in the
|
||||
virtual file tree, and then wait for it to sync up. This needs to be done in
|
||||
Javascript code. The sync will not complete until at least one (but possibly
|
||||
several) iterations of the mainloop have passed, which means you can not
|
||||
access any saved files during main() or SDL_AppInit() by default.
|
||||
|
||||
SDL can solve this problem for you: it can be built to automatically mount the
|
||||
persistent files from IDBFS to a specific place in the file tree and wait
|
||||
until the sync has completed before calling main() or SDL_AppInit(), so to
|
||||
your C code, it looks like the files were always available.
|
||||
|
||||
To use this functionality, set the CMake variable
|
||||
`SDL_EMSCRIPTEN_PERSISTENT_PATH` to a path in the filetree where persistent
|
||||
storage should be mounted:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
emcmake cmake -DSDL_EMSCRIPTEN_PERSISTENT_PATH=/storage ..
|
||||
```
|
||||
|
||||
You should also link your app with `-lidbfs.js`. If your project links to SDL
|
||||
using CMake's find_package(SDL3), or uses `pkg-config sdl3 --libs`, this will
|
||||
be handled for you when used with an SDL built with
|
||||
`-DSDL_EMSCRIPTEN_PERSISTENT_PATH`.
|
||||
|
||||
Now `/storage` will be prepared when your program runs, and SDL_GetPrefPath()
|
||||
will return a directory under that path. The storage is mounted with the
|
||||
`autoPersist: true` option, so when you write to that tree, whether with
|
||||
SDL APIs or other functions like fopen(), Emscripten will know it needs to
|
||||
sync that data back to the persistent database, and will do so automatically
|
||||
within the next few iterations of the mainloop.
|
||||
|
||||
It's best to assume the sync will take a few frames to complete, and the
|
||||
data is not safe until it does.
|
||||
|
||||
To summarize how to automate this:
|
||||
|
||||
- Build with `emcmake cmake -DSDL_EMSCRIPTEN_PERSISTENT_PATH=/storage`
|
||||
- Link your app with `-lidbfs.js` if not handled automatically.
|
||||
- Write under `/storage`, or use SDL_GetPrefPath()
|
||||
|
||||
|
||||
## Customizing index.html
|
||||
|
||||
|
||||
@@ -573,6 +573,8 @@
|
||||
#cmakedefine SDL_VIDEO_VITA_PVR 1
|
||||
#cmakedefine SDL_VIDEO_VITA_PVR_OGL 1
|
||||
|
||||
#cmakedefine SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING "@SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING@"
|
||||
|
||||
/* xkbcommon version info */
|
||||
#define SDL_XKBCOMMON_VERSION_MAJOR @SDL_XKBCOMMON_VERSION_MAJOR@
|
||||
#define SDL_XKBCOMMON_VERSION_MINOR @SDL_XKBCOMMON_VERSION_MINOR@
|
||||
|
||||
@@ -39,19 +39,23 @@ char *SDL_SYS_GetBasePath(void)
|
||||
|
||||
char *SDL_SYS_GetPrefPath(const char *org, const char *app)
|
||||
{
|
||||
const char *append = "/libsdl/";
|
||||
#ifdef SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING
|
||||
const char *append = SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING;
|
||||
#else
|
||||
const char *append = "/libsdl";
|
||||
#endif
|
||||
char *result;
|
||||
char *ptr = NULL;
|
||||
const size_t len = SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 3;
|
||||
const size_t len = SDL_strlen(append) + SDL_strlen(org) + SDL_strlen(app) + 4;
|
||||
result = (char *)SDL_malloc(len);
|
||||
if (!result) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (*org) {
|
||||
SDL_snprintf(result, len, "%s%s/%s/", append, org, app);
|
||||
SDL_snprintf(result, len, "%s/%s/%s/", append, org, app);
|
||||
} else {
|
||||
SDL_snprintf(result, len, "%s%s/", append, app);
|
||||
SDL_snprintf(result, len, "%s/%s/", append, app);
|
||||
}
|
||||
|
||||
for (ptr = result + 1; *ptr; ptr++) {
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
|
||||
EM_JS_DEPS(sdlrunapp, "$dynCall,$stringToNewUTF8");
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int CallSDLEmscriptenMainFunction(int argc, char *argv[], SDL_main_func mainFunction)
|
||||
{
|
||||
return SDL_CallMainFunction(argc, argv, mainFunction);
|
||||
}
|
||||
|
||||
int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved)
|
||||
{
|
||||
(void)reserved;
|
||||
@@ -52,7 +57,30 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv
|
||||
}
|
||||
}, SDL_setenv_unsafe);
|
||||
|
||||
return SDL_CallMainFunction(argc, argv, mainFunction);
|
||||
#ifdef SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING
|
||||
MAIN_THREAD_EM_ASM({
|
||||
const persistent_path = UTF8ToString($0);
|
||||
const argc = $1;
|
||||
const argv = $2;
|
||||
const mainFunction = $3;
|
||||
//console.log("SDL is automounting persistent storage to '" + persistent_path + "' ...please wait.");
|
||||
FS.mkdirTree(persistent_path);
|
||||
FS.mount(IDBFS, { autoPersist: true }, persistent_path);
|
||||
FS.syncfs(true, function(err) {
|
||||
if (err) {
|
||||
console.error(`WARNING: Failed to populate persistent store at '${persistent_path}' (${err.name}: ${err.message}). Save games likely lost?`);
|
||||
}
|
||||
_CallSDLEmscriptenMainFunction(argc, argv, mainFunction); // error or not, start the actual SDL_main().
|
||||
});
|
||||
}, SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING, argc, argv, mainFunction);
|
||||
|
||||
// we need to stop running code until FS.syncfs() finishes, but we need the runtime to not clean up.
|
||||
// The actual SDL_main/SDL_AppInit() will be called when the sync is done and things will pick back up where they were.
|
||||
emscripten_exit_with_live_runtime();
|
||||
return 0;
|
||||
#else
|
||||
return CallSDLEmscriptenMainFunction(argc, argv, mainFunction);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user