mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-05-28 03:26:15 +08: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.
This commit is contained in:
@@ -393,6 +393,10 @@ set_option(SDL_CCACHE "Use Ccache to speed up build" OFF)
|
|||||||
set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF)
|
set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF)
|
||||||
dep_option(SDL_GPU_OPENXR "Build SDL_GPU with OpenXR support" ON "SDL_GPU;NOT RISCOS" OFF)
|
dep_option(SDL_GPU_OPENXR "Build SDL_GPU with OpenXR support" ON "SDL_GPU;NOT RISCOS" 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")
|
set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION")
|
||||||
|
|
||||||
if(DEFINED CACHE{SDL_SHARED} OR DEFINED CACHE{SDL_STATIC})
|
if(DEFINED CACHE{SDL_SHARED} OR DEFINED CACHE{SDL_STATIC})
|
||||||
@@ -1668,6 +1672,11 @@ elseif(EMSCRIPTEN)
|
|||||||
# project. Uncomment at will for verbose cross-compiling -I/../ path info.
|
# project. Uncomment at will for verbose cross-compiling -I/../ path info.
|
||||||
sdl_compile_options(PRIVATE "-Wno-warn-absolute-paths")
|
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(
|
sdl_glob_sources(
|
||||||
"${SDL3_SOURCE_DIR}/src/main/emscripten/*.c"
|
"${SDL3_SOURCE_DIR}/src/main/emscripten/*.c"
|
||||||
"${SDL3_SOURCE_DIR}/src/main/emscripten/*.h"
|
"${SDL3_SOURCE_DIR}/src/main/emscripten/*.h"
|
||||||
@@ -4001,6 +4010,7 @@ if(SDL_SHARED)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(SDL3-shared PRIVATE ${SDL_CMAKE_DEPENDS})
|
target_link_libraries(SDL3-shared PRIVATE ${SDL_CMAKE_DEPENDS})
|
||||||
target_include_directories(SDL3-shared
|
target_include_directories(SDL3-shared
|
||||||
PRIVATE
|
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)
|
[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.
|
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
|
## Customizing index.html
|
||||||
|
|
||||||
|
|||||||
@@ -574,6 +574,8 @@
|
|||||||
#cmakedefine SDL_VIDEO_VITA_PVR 1
|
#cmakedefine SDL_VIDEO_VITA_PVR 1
|
||||||
#cmakedefine SDL_VIDEO_VITA_PVR_OGL 1
|
#cmakedefine SDL_VIDEO_VITA_PVR_OGL 1
|
||||||
|
|
||||||
|
#cmakedefine SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING "@SDL_EMSCRIPTEN_PERSISTENT_PATH_STRING@"
|
||||||
|
|
||||||
/* xkbcommon version info */
|
/* xkbcommon version info */
|
||||||
#define SDL_XKBCOMMON_VERSION_MAJOR @SDL_XKBCOMMON_VERSION_MAJOR@
|
#define SDL_XKBCOMMON_VERSION_MAJOR @SDL_XKBCOMMON_VERSION_MAJOR@
|
||||||
#define SDL_XKBCOMMON_VERSION_MINOR @SDL_XKBCOMMON_VERSION_MINOR@
|
#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)
|
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 *result;
|
||||||
char *ptr = NULL;
|
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);
|
result = (char *)SDL_malloc(len);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*org) {
|
if (*org) {
|
||||||
SDL_snprintf(result, len, "%s%s/%s/", append, org, app);
|
SDL_snprintf(result, len, "%s/%s/%s/", append, org, app);
|
||||||
} else {
|
} else {
|
||||||
SDL_snprintf(result, len, "%s%s/", append, app);
|
SDL_snprintf(result, len, "%s/%s/", append, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ptr = result + 1; *ptr; ptr++) {
|
for (ptr = result + 1; *ptr; ptr++) {
|
||||||
|
|||||||
@@ -28,6 +28,11 @@
|
|||||||
|
|
||||||
EM_JS_DEPS(sdlrunapp, "$dynCall,$stringToNewUTF8");
|
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)
|
int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved)
|
||||||
{
|
{
|
||||||
(void)reserved;
|
(void)reserved;
|
||||||
@@ -52,7 +57,30 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv
|
|||||||
}
|
}
|
||||||
}, SDL_setenv_unsafe);
|
}, 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
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user