Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions platform/web/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,44 @@ if len(env["EXPORTED_FUNCTIONS"]):
if len(env["EXPORTED_RUNTIME_METHODS"]):
sys_env.Append(LINKFLAGS=["-sEXPORTED_RUNTIME_METHODS=" + repr(sorted(list(set(env["EXPORTED_RUNTIME_METHODS"]))))])

# We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
sys_env.Append(LIBS=["idbfs.js"])

# Actually build the program.
main_js_file = sys_env.File("#bin/godot${PROGSUFFIX}.js")
main_wasm_file = sys_env.File("#bin/godot${PROGSUFFIX}.wasm")
side_wasm_file = None
main_wasm_dwarf_file = None
main_wasm_dwarf_package_file = None
side_wasm_dwarf_file = None
side_wasm_dwarf_package_file = None

build = []
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
build_targets = [main_js_file, main_wasm_file]


def separate_debug_symbols(target_env, wasm_file):
wasm_dwarf_file = target_env.File(f"{str(wasm_file).removesuffix('.wasm')}.dwarf.wasm")
target_env.Append(LINKFLAGS=[f"-gseparate-dwarf={wasm_dwarf_file}"])
target_env.Append(CCFLAGS=["-gsplit-dwarf"])
target_env.Append(LINKFLAGS=["-gsplit-dwarf"])
target_env.Append(LINKFLAGS=[f"-sSEPARATE_DWARF_URL=./{wasm_dwarf_file.name}"])
wasm_dwarf_package_file = target_env.File(f"{wasm_dwarf_file}.dwp")
target_env.Command(
action=f"{env.Detect('emdwp')} -e {wasm_dwarf_file} -o {wasm_dwarf_package_file}",
target=wasm_dwarf_package_file,
source=wasm_dwarf_file,
)
return wasm_dwarf_file, wasm_dwarf_package_file


if env["debug_symbols"] and env["separate_debug_symbols"]:
main_wasm_dwarf_file, main_wasm_dwarf_package_file = separate_debug_symbols(sys_env, main_wasm_file)
build_targets.append(main_wasm_dwarf_file)

if env["dlink_enabled"]:
# Reset libraries. The main runtime will only link emscripten libraries, not godot ones.
sys_env["LIBS"] = []
# We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
sys_env.Append(LIBS=["idbfs.js"])
# Configure it as a main module (dynamic linking support).
sys_env["CCFLAGS"].remove("-sSIDE_MODULE=2")
sys_env["LINKFLAGS"].remove("-sSIDE_MODULE=2")
Expand All @@ -103,11 +134,14 @@ if env["dlink_enabled"]:
sys = sys_env.add_program(build_targets, ["web_runtime.cpp"])

# The side library, containing all Godot code.
wasm = env.add_program("#bin/godot.side${PROGSUFFIX}.wasm", web_files)
side_wasm_file = env.File("#bin/godot.side${PROGSUFFIX}.wasm")
side_wasm_build = [side_wasm_file]
if env["debug_symbols"] and env["separate_debug_symbols"]:
side_wasm_dwarf_file, side_wasm_dwarf_package_file = separate_debug_symbols(env, side_wasm_file)
side_wasm_build.append(side_wasm_dwarf_file)
wasm = env.add_program(side_wasm_build, web_files)
build = sys + [wasm[0]]
else:
# We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
sys_env.Append(LIBS=["idbfs.js"])
build = sys_env.add_program(build_targets, web_files + ["web_runtime.cpp"])

sys_env.Depends(build[0], sys_env["JS_LIBS"])
Expand All @@ -133,7 +167,12 @@ js_wrapped = env.NoCache(
env.Textfile("#bin/godot", [env.File(f) for f in wrap_list], TEXTFILESUFFIX="${PROGSUFFIX}.wrapped.js")
)

# 0 - unwrapped js file (use wrapped one instead)
# 1 - wasm file
# 2 - wasm side (when dlink is enabled).
env.CreateTemplateZip(js_wrapped, build[1], build[2] if len(build) > 2 else None)
env.CreateTemplateZip(
js=js_wrapped,
main_wasm=main_wasm_file,
side_wasm=side_wasm_file,
main_wasm_dwarf=main_wasm_dwarf_file,
main_wasm_dwarf_package=main_wasm_dwarf_package_file,
side_wasm_dwarf=side_wasm_dwarf_file,
side_wasm_dwarf_package=side_wasm_dwarf_package_file,
)
80 changes: 46 additions & 34 deletions platform/web/emscripten_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,46 @@ def create_engine_file(env, target, source, externs, threads_enabled):
return env.Substfile(target=target, source=[env.File(s) for s in source], SUBST_DICT=subst_dict)


def create_template_zip(env, js, wasm, side):
def create_template_zip(
env,
js,
main_wasm,
side_wasm=None,
main_wasm_dwarf=None,
main_wasm_dwarf_package=None,
side_wasm_dwarf=None,
side_wasm_dwarf_package=None,
):
binary_name = "godot.editor" if env.editor_build else "godot"
zip_dir = env.Dir(env.GetTemplateZipPath())
in_files = [
js,
wasm,
"#platform/web/js/libs/audio.worklet.js",
"#platform/web/js/libs/audio.position.worklet.js",
]
out_files = [
zip_dir.File(binary_name + ".js"),
zip_dir.File(binary_name + ".wasm"),
zip_dir.File(binary_name + ".audio.worklet.js"),
zip_dir.File(binary_name + ".audio.position.worklet.js"),
]

in_files = []
out_files = []

def add_to_template(in_file, zip_file):
out_file = zip_dir.File(zip_file)
in_files.append(in_file)
out_files.append(out_file)

add_to_template(js, binary_name + ".js")
add_to_template(main_wasm, binary_name + ".wasm")
add_to_template("#platform/web/js/libs/audio.worklet.js", binary_name + ".audio.worklet.js")
add_to_template("#platform/web/js/libs/audio.position.worklet.js", binary_name + ".audio.position.worklet.js")

# Dynamic linking (extensions) specific.
if env["dlink_enabled"]:
in_files.append(side) # Side wasm (contains the actual Godot code).
out_files.append(zip_dir.File(binary_name + ".side.wasm"))
if side_wasm is not None:
add_to_template(side_wasm, binary_name + ".side.wasm")

# Those files cannot be renamed, as their relative .wasm file has their name baked in the binary.
# They must also reside besides their original .wasm files.
if main_wasm_dwarf is not None:
add_to_template(main_wasm_dwarf, main_wasm_dwarf.name)
if main_wasm_dwarf_package is not None:
add_to_template(main_wasm_dwarf_package, main_wasm_dwarf_package.name)
if side_wasm_dwarf is not None:
add_to_template(side_wasm_dwarf, side_wasm_dwarf.name)
if side_wasm_dwarf_package is not None:
add_to_template(side_wasm_dwarf_package, side_wasm_dwarf_package.name)

service_worker = "#misc/dist/html/service-worker.js"
if env.editor_build:
Expand All @@ -74,33 +95,24 @@ def create_template_zip(env, js, wasm, side):
"___GODOT_ENSURE_CROSSORIGIN_ISOLATION_HEADERS___": "true",
}
html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict)
in_files.append(html)
out_files.append(zip_dir.File(binary_name + ".html"))
add_to_template(html, binary_name + ".html")
# And logo/favicon
in_files.append("#misc/dist/html/logo.svg")
out_files.append(zip_dir.File("logo.svg"))
in_files.append("#icon.png")
out_files.append(zip_dir.File("favicon.png"))
add_to_template("#misc/dist/html/logo.svg", "logo.svg")
add_to_template("#icon.png", "favicon.svg")
# PWA
service_worker = env.Substfile(
target="#bin/godot${PROGSUFFIX}.service.worker.js",
source=service_worker,
SUBST_DICT=subst_dict,
)
in_files.append(service_worker)
out_files.append(zip_dir.File("service.worker.js"))
in_files.append("#misc/dist/html/manifest.json")
out_files.append(zip_dir.File("manifest.json"))
in_files.append("#misc/dist/html/offline.html")
out_files.append(zip_dir.File("offline.html"))
add_to_template(service_worker, "service.worker.js")
add_to_template("#misc/dist/html/manifest.json", "manifest.json")
add_to_template("#misc/dist/html/offline.html", "offline.html")
else:
# HTML
in_files.append("#misc/dist/html/full-size.html")
out_files.append(zip_dir.File(binary_name + ".html"))
in_files.append(service_worker)
out_files.append(zip_dir.File(binary_name + ".service.worker.js"))
in_files.append("#misc/dist/html/offline-export.html")
out_files.append(zip_dir.File("godot.offline.html"))
add_to_template("#misc/dist/html/full-size.html", binary_name + ".html")
add_to_template(service_worker, binary_name + ".service.worker.js")
add_to_template("#misc/dist/html/offline-export.html", binary_name + ".offline.html")

zip_files = env.NoCache(env.InstallAs(out_files, in_files))
env.NoCache(
Expand Down
10 changes: 9 additions & 1 deletion platform/web/export/export_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,15 @@ Error EditorExportPlatformWeb::_extract_template(const String &p_template, const
unzCloseCurrentFile(pkg);

//write
String dst = p_dir.path_join(file.replace("godot", p_name));
String dst;
// We cannot rename some files.
if (likely(!file.begins_with("godot") || (!file.ends_with(".dwarf.wasm") && !file.ends_with(".dwarf.wasm.dwp")))) {
String new_file_name = file.replace("godot", p_name);
dst = p_dir.path_join(file.replace("godot", p_name));
} else {
dst = p_dir.path_join(file);
}

Ref<FileAccess> f = FileAccess::open(dst, FileAccess::WRITE);
if (f.is_null()) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not write file: \"%s\"."), dst));
Expand Down
32 changes: 11 additions & 21 deletions platform/web/js/engine/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const Engine = (function () {
* @param {string=} basePath Base path of the engine to load.
* @return {Promise} A ``Promise`` that resolves once the engine is loaded and initialized.
*/
init: function (basePath) {
init: async function (basePath) {
if (initPromise) {
return initPromise;
}
Expand All @@ -86,28 +86,18 @@ const Engine = (function () {
Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]);
}
const me = this;
function doInit(promise) {
// Care! Promise chaining is bogus with old emscripten versions.
// This caused a regression with the Mono build (which uses an older emscripten version).
// Make sure to test that when refactoring.
return new Promise(function (resolve, reject) {
promise.then(function (response) {
const cloned = new Response(response.clone().body, { 'headers': [['content-type', 'application/wasm']] });
Godot(me.config.getModuleConfig(loadPath, cloned)).then(function (module) {
const paths = me.config.persistentPaths;
module['initFS'](paths).then(function (err) {
me.rtenv = module;
if (me.config.unloadAfterInit) {
Engine.unload();
}
resolve();
});
});
});
});
async function doInit(response) {
const module = await Godot(me.config.getModuleConfig(loadPath, response));
const paths = me.config.persistentPaths;
// TODO: Handle `err`
const err = await module['initFS'](paths); // eslint-disable-line no-unused-vars
me.rtenv = module; // eslint-disable-line require-atomic-updates
if (me.config.unloadAfterInit) {
Engine.unload();
}
}
preloader.setProgressFunc(this.config.onProgress);
initPromise = doInit(loadPromise);
initPromise = doInit(await loadPromise); // eslint-disable-line require-atomic-updates
return initPromise;
},

Expand Down
51 changes: 25 additions & 26 deletions platform/web/js/engine/preloader.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
function getTrackedResponse(response, load_status) {
function onloadprogress(reader, controller) {
return reader.read().then(function (result) {
if (load_status.done) {
return Promise.resolve();
}
if (result.value) {
controller.enqueue(result.value);
load_status.loaded += result.value.length;
}
if (!result.done) {
return onloadprogress(reader, controller);
}
load_status.done = true;
function trackResponse(response, load_status) {
async function trackReader(reader) {
const result = await reader.read();
if (load_status.done) {
return Promise.resolve();
});
}
if (result.value) {
load_status.loaded += result.value.length;
}
if (!result.done) {
return trackReader(reader);
}
load_status.done = true;
return Promise.resolve();
}
const reader = response.body.getReader();
return new Response(new ReadableStream({
start: function (controller) {
onloadprogress(reader, controller).then(function () {
controller.close();
});
},
}), { headers: response.headers });

// Start the reader in parallel of the main response.
const reader = response.clone().body.getReader();
trackReader(reader).catch((_err) => {
// TODO: Handle `_err`
});

// We need to keep the original response intact as much as possible.
return response;
}

function loadFetch(file, tracker, fileSize, raw) {
Expand All @@ -36,11 +35,11 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
if (!response.ok) {
return Promise.reject(new Error(`Failed loading file '${file}'`));
}
const tr = getTrackedResponse(response, tracker[file]);
trackResponse(response, tracker[file]);
if (raw) {
return Promise.resolve(tr);
return response;
}
return tr.arrayBuffer();
return response.arrayBuffer();
});
}

Expand Down
Loading