vfs,build,emscripten: new resource delivery system for Emscripten
Resource files are no longer packaged and preloaded all in advance. Instead, they are downloaded while the game is running. The implementation is less than ideal, but it works. Resources are requested on demand and cached into IDBFS. Unfortunately, since the resource system was not designed with web-style asynchrony in mind and we aren't ready for threads either, we have no way to do any useful work while a required file is downloading. To somewhat offset that, we also pre-fetch files that were not requested yet. The pre-fetching is limited to 4 files at a time, which seems to be a reasonable compromise between throughput and cold-cache load latency on slow connections. Also unfortunately, it's dumb as rocks: we have no way of knowing which files we will actually be needing soon, so the pre-fetching is done in whatever order the files were indexed. Possibly the easiest way to improve this system would be to bundle (and compress) all of the tiny text files together to alleviate the overhead of hundreds of HTTP requests.
This commit is contained in:
parent
915285a24e
commit
e2c0282a30
10 changed files with 270 additions and 208 deletions
|
@ -92,7 +92,7 @@ Module = {
|
|||
progressElement.value = parseInt(m[2])*100;
|
||||
progressElement.max = parseInt(m[4])*100;
|
||||
progressElement.hidden = false;
|
||||
spinnerElement.hidden = false;
|
||||
spinnerElement.hidden = !canvasElement.hidden;
|
||||
} else {
|
||||
progressElement.value = null;
|
||||
progressElement.max = null;
|
||||
|
@ -118,7 +118,8 @@ function SyncFS(is_load, ccptr) {
|
|||
Module['ccall'](
|
||||
'vfs_sync_callback',
|
||||
null, ["boolean", "string", "number"],
|
||||
[is_load, err, ccptr]
|
||||
[is_load, err, ccptr],
|
||||
{ async: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ packages = [
|
|||
'00-taisei',
|
||||
]
|
||||
|
||||
use_static_res_index = false
|
||||
use_static_res_index = (host_machine.system() == 'emscripten')
|
||||
|
||||
foreach pkg : packages
|
||||
pkg_pkgdir = '@0@.pkgdir'.format(pkg)
|
||||
|
@ -55,79 +55,12 @@ if use_static_res_index
|
|||
subdir_done()
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
em_data_prefix = '/@0@'.format(config.get_unquoted('TAISEI_BUILDCONF_DATA_PATH'))
|
||||
em_bundles = ['gfx', 'misc']
|
||||
|
||||
if get_option('a_default') != 'null'
|
||||
em_bundles += ['bgm', 'sfx']
|
||||
endif
|
||||
|
||||
em_bundle_gfx_patterns = [
|
||||
'gfx/*.png',
|
||||
'gfx/*.webp',
|
||||
'gfx/*.basis',
|
||||
'gfx/*.basis.zst',
|
||||
'fonts/*.ttf',
|
||||
'fonts/*.otf',
|
||||
]
|
||||
|
||||
em_bundle_misc_patterns = [
|
||||
'gfx/*.ani',
|
||||
'gfx/*.spr',
|
||||
'gfx/*.tex',
|
||||
'gfx/*.material',
|
||||
'fonts/*.font',
|
||||
'models/*.iqm',
|
||||
|
||||
# We don't want to include the shader sources here, we're going to translate them first.
|
||||
'shader/*.prog'
|
||||
]
|
||||
|
||||
em_bundle_bgm_patterns = [
|
||||
'bgm/*',
|
||||
]
|
||||
|
||||
em_bundle_sfx_patterns = [
|
||||
'sfx/*',
|
||||
]
|
||||
|
||||
foreach bundle : em_bundles
|
||||
set_variable('em_bundle_@0@_deps'.format(bundle), [])
|
||||
set_variable('em_bundle_@0@_files'.format(bundle), [])
|
||||
set_variable('em_bundle_@0@_packer_args'.format(bundle), [])
|
||||
endforeach
|
||||
|
||||
# These are all text files that compress well
|
||||
em_bundle_misc_packer_args += ['--lz4']
|
||||
endif
|
||||
|
||||
foreach pkg : packages
|
||||
pkg_pkgdir = '@0@.pkgdir'.format(pkg)
|
||||
pkg_zip = '@0@.zip'.format(pkg)
|
||||
pkg_path = join_paths(meson.current_source_dir(), pkg_pkgdir)
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
foreach bundle : em_bundles
|
||||
var_patterns = 'em_bundle_@0@_patterns'.format(bundle)
|
||||
var_files = 'em_bundle_@0@_files'.format(bundle)
|
||||
var_packer_args = 'em_bundle_@0@_packer_args'.format(bundle)
|
||||
|
||||
glob_result = run_command(glob_command, pkg_path, get_variable(var_patterns), check : true)
|
||||
|
||||
foreach file : glob_result.stdout().strip().split('\n')
|
||||
if file != ''
|
||||
fpath = join_paths(meson.current_source_dir(), pkg_pkgdir, file)
|
||||
|
||||
set_variable(var_files, get_variable(var_files) + [fpath])
|
||||
|
||||
set_variable(var_packer_args, get_variable(var_packer_args) + [
|
||||
'--preload', '@0@@@1@/@2@'.format(fpath, em_data_prefix, file)
|
||||
])
|
||||
endif
|
||||
endforeach
|
||||
endforeach
|
||||
elif package_data
|
||||
if package_data
|
||||
bindist_deps += custom_target(pkg_zip,
|
||||
command : [pack_command,
|
||||
pkg_path,
|
||||
|
@ -172,64 +105,3 @@ if host_machine.system() == 'nx'
|
|||
install_subdir(shaders_build_dir, install_dir : data_path, exclude_files : glob_result.stdout().split('\n'))
|
||||
endif
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
# First add some stuff that isn't sourced from resources/
|
||||
|
||||
foreach shader : essl_targets
|
||||
# This one is especially dirty!
|
||||
abs_path = shader.full_path()
|
||||
assert(abs_path.startswith(shaders_build_dir), 'Assumption about shader output location violated')
|
||||
rel_path = '@0@/shader@1@'.format(em_data_prefix, abs_path.split(shaders_build_dir)[1])
|
||||
|
||||
em_bundle_misc_deps += shader
|
||||
em_bundle_misc_packer_args += [
|
||||
'--preload', '@0@@@1@'.format(abs_path, rel_path)
|
||||
]
|
||||
endforeach
|
||||
|
||||
packer = find_program('file_packager') # should be provided by cross file
|
||||
em_bundle_link_args = [] # We'll pass these to the final emcc linking step
|
||||
|
||||
# Finally, set up build targets for our bundles
|
||||
|
||||
foreach bundle : em_bundles
|
||||
var_files = 'em_bundle_@0@_files'.format(bundle)
|
||||
var_deps = 'em_bundle_@0@_deps'.format(bundle)
|
||||
var_packer_args = 'em_bundle_@0@_packer_args'.format(bundle)
|
||||
|
||||
data_name = 'bundle_@0@.data'.format(bundle)
|
||||
loader_name = 'bundle_@0@.js'.format(bundle)
|
||||
out_loader = join_paths(meson.current_build_dir(), '@0@.raw'.format(loader_name))
|
||||
|
||||
bundle_data = custom_target(data_name,
|
||||
command : [
|
||||
packer,
|
||||
'@OUTPUT0@',
|
||||
'--js-output=@0@'.format(out_loader), # No, this one does not accept "--js-output foobar.js"
|
||||
'--use-preload-cache',
|
||||
'--from-emcc',
|
||||
'--no-node',
|
||||
get_variable(var_packer_args)
|
||||
],
|
||||
output : data_name,
|
||||
depends : get_variable(var_deps),
|
||||
depend_files : get_variable(var_files),
|
||||
install : true,
|
||||
install_dir : bindir,
|
||||
)
|
||||
|
||||
bundle_loader = custom_target(loader_name,
|
||||
command : [
|
||||
em_set_bundle_uuid_command,
|
||||
out_loader,
|
||||
'--sha1', bundle_data,
|
||||
'--output', '@OUTPUT@',
|
||||
],
|
||||
output : loader_name,
|
||||
install : false,
|
||||
)
|
||||
|
||||
em_bundle_link_args += ['--pre-js', bundle_loader]
|
||||
endforeach
|
||||
endif
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from taiseilib.common import (
|
||||
run_main,
|
||||
update_text_file,
|
||||
)
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
meta_re = re.compile(r'(.*loadPackage\()({.*?})(\);.*)', re.DOTALL)
|
||||
|
||||
|
||||
def main(args):
|
||||
parser = argparse.ArgumentParser(description='Change package UUID in JavaScript loader generated by Emscripten\'s file_packager.py', prog=args[0])
|
||||
|
||||
parser.add_argument('loader',
|
||||
help='the .js loader file',
|
||||
metavar='FILE',
|
||||
type=Path,
|
||||
)
|
||||
|
||||
parser.add_argument('--output', '-o',
|
||||
help='write result to FILE (default: overwrite input)',
|
||||
metavar='FILE',
|
||||
type=Path,
|
||||
)
|
||||
|
||||
g = parser.add_mutually_exclusive_group(required=True)
|
||||
|
||||
g.add_argument('--uuid',
|
||||
help='manually specify an UUID',
|
||||
metavar='UUID',
|
||||
type=str,
|
||||
)
|
||||
|
||||
g.add_argument('--sha1',
|
||||
help='take SHA1 of FILE and use that as an UUID',
|
||||
metavar='FILE',
|
||||
type=Path,
|
||||
)
|
||||
|
||||
args = parser.parse_args(args[1:])
|
||||
|
||||
if args.uuid is None:
|
||||
args.uuid = hashlib.sha1(args.sha1.read_bytes()).hexdigest()
|
||||
|
||||
if args.output is None:
|
||||
args.output = args.loader
|
||||
|
||||
pre, meta, post = meta_re.match(args.loader.read_text()).groups()
|
||||
|
||||
meta = json.loads(meta)
|
||||
meta['package_uuid'] = args.uuid
|
||||
meta = json.dumps(meta, separators=(',', ':'), check_circular=False, ensure_ascii=False)
|
||||
|
||||
update_text_file(args.output, pre + meta + post)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_main(main)
|
|
@ -125,9 +125,6 @@ glob_command = [glob_script]
|
|||
check_submodules_script = find_program(files('check-submodules.py'))
|
||||
check_submodules_command = [check_submodules_script]
|
||||
|
||||
em_set_bundle_uuid_script = find_program(files('em-set-bundle-uuid.py'))
|
||||
em_set_bundle_uuid_command = [em_set_bundle_uuid_script]
|
||||
|
||||
fix_path_script = find_program(files('unfuck-path.py'))
|
||||
fix_path_command = [fix_path_script]
|
||||
|
||||
|
|
|
@ -309,6 +309,8 @@ if host_machine.system() == 'emscripten'
|
|||
'-s', 'EXIT_RUNTIME=0',
|
||||
'-s', 'EXPORTED_FUNCTIONS=["_main", "_vfs_sync_callback"]',
|
||||
'-s', 'EXPORTED_RUNTIME_METHODS=["ccall"]',
|
||||
'-s', 'FETCH',
|
||||
'-s', 'FETCH_SUPPORT_INDEXEDDB=0',
|
||||
'-s', 'FILESYSTEM=1',
|
||||
'-s', 'FORCE_FILESYSTEM=1',
|
||||
'-s', 'GL_POOL_TEMP_BUFFERS=0',
|
||||
|
@ -318,7 +320,6 @@ if host_machine.system() == 'emscripten'
|
|||
'-s', 'IGNORE_MISSING_MAIN=0',
|
||||
'-s', 'INITIAL_MEMORY=268435456',
|
||||
'-s', 'LLD_REPORT_UNDEFINED',
|
||||
'-s', 'LZ4=1',
|
||||
'-s', 'MAX_WEBGL_VERSION=2',
|
||||
'-s', 'MIN_WEBGL_VERSION=2',
|
||||
'-s', 'MODULARIZE=0',
|
||||
|
@ -378,7 +379,6 @@ if host_machine.system() == 'emscripten'
|
|||
meson.get_compiler('cpp').cmd_array(),
|
||||
taisei,
|
||||
'--pre-js', em_preamble,
|
||||
em_bundle_link_args,
|
||||
'--shell-file', em_shell,
|
||||
get_option('c_args'),
|
||||
get_option('c_link_args'),
|
||||
|
|
222
src/vfs/emscripten_fetch.c
Normal file
222
src/vfs/emscripten_fetch.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT License.
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "emscripten_fetch.h"
|
||||
#include "emscripten_fetch_public.h"
|
||||
#include "resindex.h"
|
||||
#include "rwops/rwops_dummy.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <emscripten/fetch.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define CACHE_PATH "/persistent/res-cache"
|
||||
#define MAX_PREFETCH_REQUESTS 4
|
||||
|
||||
#define MAKE_CACHED_PATH(_var_name, _content_id) \
|
||||
char _var_name[sizeof(CACHE_PATH) + strlen(_content_id) + 1]; \
|
||||
snprintf(_var_name, sizeof(_var_name), CACHE_PATH "/%s", (_content_id))
|
||||
|
||||
#define FETCH_CONTENT_ID(_fetch) \
|
||||
((_fetch)->url + sizeof(TAISEI_BUILDCONF_DATA_PATH))
|
||||
|
||||
typedef struct FetchFSContext {
|
||||
VFSResIndexFSContext resindex_ctx;
|
||||
ht_str2ptr_t requests;
|
||||
uint prefetch_iter;
|
||||
} FetchFSContext;
|
||||
|
||||
static emscripten_fetch_t *fetch_begin_request(FetchFSContext *ctx, const char *content_id);
|
||||
|
||||
EM_JS(void, update_dl_status, (int done, int total), {
|
||||
Module["setStatus"](
|
||||
done >= total
|
||||
? ""
|
||||
: "Prefetching resources… (" + done + "/" + total + ")",
|
||||
true);
|
||||
});
|
||||
|
||||
static void prefetch_next(FetchFSContext *ctx) {
|
||||
while(ctx->requests.num_elements_occupied < MAX_PREFETCH_REQUESTS) {
|
||||
update_dl_status(ctx->prefetch_iter, resindex_num_file_entries());
|
||||
|
||||
if(ctx->prefetch_iter >= resindex_num_file_entries()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char *content_id = resindex_get_file_entry(ctx->prefetch_iter++)->content_id;
|
||||
MAKE_CACHED_PATH(cached_path, content_id);
|
||||
|
||||
struct stat statbuf;
|
||||
if(stat(cached_path, &statbuf) >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fetch_begin_request(ctx, content_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void fetch_finalize(emscripten_fetch_t *fetch) {
|
||||
FetchFSContext *ctx = fetch->userData;
|
||||
assert(ht_get(&ctx->requests, fetch->url, NULL) == fetch);
|
||||
ht_unset(&ctx->requests, fetch->url);
|
||||
prefetch_next(ctx);
|
||||
}
|
||||
|
||||
static void fetch_onsuccess(emscripten_fetch_t *fetch) {
|
||||
fetch_finalize(fetch);
|
||||
|
||||
MAKE_CACHED_PATH(path, FETCH_CONTENT_ID(fetch));
|
||||
|
||||
SDL_RWops *rw = SDL_RWFromFile(path, "w");
|
||||
SDL_RWwrite(rw, fetch->data, fetch->numBytes, 1);
|
||||
SDL_RWclose(rw);
|
||||
|
||||
log_info("Cached %s as %s", fetch->url, path);
|
||||
}
|
||||
|
||||
static void fetch_onerror(emscripten_fetch_t *fetch) {
|
||||
log_error(
|
||||
"Failed to download %s (status %i: %s)",
|
||||
fetch->url, fetch->status, fetch->statusText
|
||||
);
|
||||
fetch_finalize(fetch);
|
||||
}
|
||||
|
||||
static emscripten_fetch_t *fetch_begin_request(FetchFSContext *ctx, const char *content_id) {
|
||||
emscripten_fetch_t *fetch;
|
||||
|
||||
char url[sizeof(TAISEI_BUILDCONF_DATA_PATH) + strlen(content_id) + 1];
|
||||
snprintf(url, sizeof(url), TAISEI_BUILDCONF_DATA_PATH "/%s", content_id);
|
||||
|
||||
if(ht_lookup(&ctx->requests, url, (void**)&fetch)) {
|
||||
return fetch;
|
||||
}
|
||||
|
||||
emscripten_fetch_attr_t attr;
|
||||
emscripten_fetch_attr_init(&attr);
|
||||
strcpy(attr.requestMethod, "GET");
|
||||
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
|
||||
attr.destinationPath = content_id;
|
||||
attr.overriddenMimeType = "application/octet-stream";
|
||||
attr.userData = ctx;
|
||||
attr.onsuccess = fetch_onsuccess;
|
||||
attr.onerror = fetch_onerror;
|
||||
|
||||
fetch = emscripten_fetch(&attr, url);
|
||||
ht_set(&ctx->requests, url, fetch);
|
||||
|
||||
log_info("Downloading %s", url);
|
||||
return fetch;
|
||||
}
|
||||
|
||||
static void fetch_wait(emscripten_fetch_t *fetch) {
|
||||
// cursed
|
||||
while(fetch->readyState < 4 /* DONE */) {
|
||||
emscripten_sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
static int fetch_rwops_close(SDL_RWops *rw) {
|
||||
// HACK: mega-jank, heavily relies on rwops_dummy implementation details
|
||||
int r = SDL_RWclose(rw->hidden.unknown.data1);
|
||||
emscripten_fetch_close(rw->hidden.unknown.data2);
|
||||
return r;
|
||||
}
|
||||
|
||||
static SDL_RWops *vfs_fetch_open(VFSResIndexFSContext *resindex_ctx, const char *content_id, VFSOpenMode mode) {
|
||||
assert(!(mode & VFS_MODE_WRITE));
|
||||
|
||||
FetchFSContext *ctx = UNION_CAST(VFSResIndexFSContext*, FetchFSContext*, resindex_ctx);
|
||||
emscripten_fetch_t *fetch;
|
||||
|
||||
MAKE_CACHED_PATH(cached_path, content_id);
|
||||
|
||||
SDL_RWops *cached_rw = SDL_RWFromFile(cached_path, "r");
|
||||
if(cached_rw) {
|
||||
return cached_rw;
|
||||
}
|
||||
|
||||
for(;;) {
|
||||
fetch = fetch_begin_request(ctx, content_id);
|
||||
fetch_wait(fetch);
|
||||
|
||||
if(fetch->status == 200) {
|
||||
break;
|
||||
}
|
||||
|
||||
emscripten_fetch_close(fetch);
|
||||
}
|
||||
|
||||
SDL_RWops *memrw = NOT_NULL(SDL_RWFromConstMem(fetch->data, fetch->numBytes));
|
||||
SDL_RWops *wraprw = SDL_RWWrapDummy(memrw, true);
|
||||
wraprw->close = fetch_rwops_close;
|
||||
wraprw->hidden.unknown.data2 = fetch;
|
||||
|
||||
return wraprw;
|
||||
}
|
||||
|
||||
static void vfs_fetch_free(VFSResIndexFSContext *resindex_ctx) {
|
||||
FetchFSContext *ctx = UNION_CAST(VFSResIndexFSContext*, FetchFSContext*, resindex_ctx);
|
||||
ht_destroy(&ctx->requests);
|
||||
mem_free(ctx);
|
||||
}
|
||||
|
||||
static void evict_stale_cache(void) {
|
||||
ht_str2int_t idset;
|
||||
ht_create(&idset);
|
||||
|
||||
int num_files = resindex_num_file_entries();
|
||||
for(int i = 0; i < num_files; ++i) {
|
||||
const RIdxFileEntry *fentry = NOT_NULL(resindex_get_file_entry(i));
|
||||
ht_set(&idset, fentry->content_id, 1);
|
||||
}
|
||||
|
||||
char path[PATH_MAX] = CACHE_PATH "/";
|
||||
char *fname = path + sizeof(CACHE_PATH);
|
||||
|
||||
DIR *dp = opendir(CACHE_PATH);
|
||||
|
||||
for(struct dirent *d; (d = readdir(dp));) {
|
||||
if(*d->d_name == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!ht_lookup(&idset, d->d_name, NULL)) {
|
||||
strcpy(fname, d->d_name);
|
||||
log_info("Removing stale cache entry %s", path);
|
||||
unlink(path);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dp);
|
||||
ht_destroy(&idset);
|
||||
}
|
||||
|
||||
VFSNode *vfs_fetch_create(void) {
|
||||
auto ctx = ALLOC(FetchFSContext, {
|
||||
.resindex_ctx.procs = {
|
||||
.open = vfs_fetch_open,
|
||||
.free = vfs_fetch_free,
|
||||
},
|
||||
});
|
||||
ht_create(&ctx->requests);
|
||||
mkdir(CACHE_PATH, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
VFSNode *n = vfs_resindex_create(&ctx->resindex_ctx);
|
||||
evict_stale_cache();
|
||||
prefetch_next(ctx);
|
||||
return n;
|
||||
}
|
||||
|
||||
bool vfs_mount_fetchfs(const char *mountpoint) {
|
||||
return vfs_mount_or_decref(vfs_root, mountpoint, vfs_fetch_create());
|
||||
}
|
14
src/vfs/emscripten_fetch.h
Normal file
14
src/vfs/emscripten_fetch.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT License.
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "taisei.h"
|
||||
|
||||
#include "private.h"
|
||||
|
||||
VFSNode *vfs_fetch_create(void);
|
13
src/vfs/emscripten_fetch_public.h
Normal file
13
src/vfs/emscripten_fetch_public.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT License.
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "taisei.h"
|
||||
|
||||
bool vfs_mount_fetchfs(const char *mountpoint)
|
||||
attr_nonnull(1) attr_nodiscard;
|
|
@ -35,6 +35,7 @@ endif
|
|||
|
||||
if host_machine.system() == 'emscripten'
|
||||
vfs_src += files(
|
||||
'emscripten_fetch.c',
|
||||
'setup_emscripten.c',
|
||||
'sync_emscripten.c',
|
||||
)
|
||||
|
|
|
@ -10,13 +10,21 @@
|
|||
#include "taisei.h"
|
||||
|
||||
#include "setup.h"
|
||||
#include "emscripten_fetch_public.h"
|
||||
#include "decompress_wrapper_public.h"
|
||||
|
||||
static void vfs_setup_onsync(CallChainResult ccr) {
|
||||
vfs_setup_fixedpaths(&(VfsSetupFixedPaths) {
|
||||
.res_path = "/" TAISEI_BUILDCONF_DATA_PATH,
|
||||
.storage_path = "/persistent/storage",
|
||||
.cache_path = "/persistent/cache",
|
||||
});
|
||||
vfs_setup_storage_syspath("/persistent/storage");
|
||||
vfs_setup_cache_syspath("/persistent/cache");
|
||||
|
||||
vfs_create_union_mountpoint("res");
|
||||
attr_unused bool mount_ok = vfs_mount_fetchfs("res-dir");
|
||||
assert(mount_ok);
|
||||
vfs_make_decompress_view("res-dir");
|
||||
vfs_load_packages("res-dir", "res");
|
||||
vfs_mount_alias("res", "res-dir");
|
||||
vfs_unmount("res-dir");
|
||||
|
||||
vfs_setup_onsync_done(ccr);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue