Skip to content

Commit d8057da

Browse files
committed
[Web] Fix separate_debug_symbols=yes and debug experience
1 parent 60ce8ef commit d8057da

File tree

5 files changed

+140
-92
lines changed

5 files changed

+140
-92
lines changed

platform/web/SCsub

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,44 @@ for ext in sys_env["JS_EXTERNS"]:
6868
sys_env["ENV"]["EMCC_CLOSURE_ARGS"] += " --externs " + ext.abspath
6969
sys_env["ENV"]["EMCC_CLOSURE_ARGS"] = sys_env["ENV"]["EMCC_CLOSURE_ARGS"].strip()
7070

71+
# We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
72+
sys_env.Append(LIBS=["idbfs.js"])
73+
74+
# Actually build the program.
75+
main_js_file = sys_env.File("#bin/godot${PROGSUFFIX}.js")
76+
main_wasm_file = sys_env.File("#bin/godot${PROGSUFFIX}.wasm")
77+
side_wasm_file = None
78+
main_wasm_dwarf_file = None
79+
main_wasm_dwarf_package_file = None
80+
side_wasm_dwarf_file = None
81+
side_wasm_dwarf_package_file = None
82+
7183
build = []
72-
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
84+
build_targets = [main_js_file, main_wasm_file]
85+
86+
87+
def separate_debug_symbols(target_env, wasm_file):
88+
wasm_dwarf_file = target_env.File(f"{str(wasm_file).removesuffix('.wasm')}.dwarf.wasm")
89+
target_env.Append(LINKFLAGS=[f"-gseparate-dwarf={wasm_dwarf_file}"])
90+
target_env.Append(CCFLAGS=["-gsplit-dwarf"])
91+
target_env.Append(LINKFLAGS=["-gsplit-dwarf"])
92+
target_env.Append(LINKFLAGS=[f"-sSEPARATE_DWARF_URL=./{wasm_dwarf_file.name}"])
93+
wasm_dwarf_package_file = target_env.File(f"{wasm_dwarf_file}.dwp")
94+
target_env.Command(
95+
action=f"{env.Detect('emdwp')} -e {wasm_dwarf_file} -o {wasm_dwarf_package_file}",
96+
target=wasm_dwarf_package_file,
97+
source=wasm_dwarf_file,
98+
)
99+
return wasm_dwarf_file, wasm_dwarf_package_file
100+
101+
102+
if env["debug_symbols"] and env["separate_debug_symbols"]:
103+
main_wasm_dwarf_file, main_wasm_dwarf_package_file = separate_debug_symbols(sys_env, main_wasm_file)
104+
build_targets.append(main_wasm_dwarf_file)
105+
73106
if env["dlink_enabled"]:
74107
# Reset libraries. The main runtime will only link emscripten libraries, not godot ones.
75108
sys_env["LIBS"] = []
76-
# We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
77-
sys_env.Append(LIBS=["idbfs.js"])
78109
# Configure it as a main module (dynamic linking support).
79110
sys_env["CCFLAGS"].remove("-sSIDE_MODULE=2")
80111
sys_env["LINKFLAGS"].remove("-sSIDE_MODULE=2")
@@ -89,11 +120,14 @@ if env["dlink_enabled"]:
89120
sys = sys_env.add_program(build_targets, ["web_runtime.cpp"])
90121

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

99133
sys_env.Depends(build[0], sys_env["JS_LIBS"])
@@ -118,7 +152,12 @@ js_wrapped = env.NoCache(
118152
env.Textfile("#bin/godot", [env.File(f) for f in wrap_list], TEXTFILESUFFIX="${PROGSUFFIX}.wrapped.js")
119153
)
120154

121-
# 0 - unwrapped js file (use wrapped one instead)
122-
# 1 - wasm file
123-
# 2 - wasm side (when dlink is enabled).
124-
env.CreateTemplateZip(js_wrapped, build[1], build[2] if len(build) > 2 else None)
155+
env.CreateTemplateZip(
156+
js=js_wrapped,
157+
main_wasm=main_wasm_file,
158+
side_wasm=side_wasm_file,
159+
main_wasm_dwarf=main_wasm_dwarf_file,
160+
main_wasm_dwarf_package=main_wasm_dwarf_package_file,
161+
side_wasm_dwarf=side_wasm_dwarf_file,
162+
side_wasm_dwarf_package=side_wasm_dwarf_package_file,
163+
)

platform/web/emscripten_helpers.py

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,46 @@ def create_engine_file(env, target, source, externs, threads_enabled):
3030
return env.Substfile(target=target, source=[env.File(s) for s in source], SUBST_DICT=subst_dict)
3131

3232

33-
def create_template_zip(env, js, wasm, side):
33+
def create_template_zip(
34+
env,
35+
js,
36+
main_wasm,
37+
side_wasm=None,
38+
main_wasm_dwarf=None,
39+
main_wasm_dwarf_package=None,
40+
side_wasm_dwarf=None,
41+
side_wasm_dwarf_package=None,
42+
):
3443
binary_name = "godot.editor" if env.editor_build else "godot"
3544
zip_dir = env.Dir(env.GetTemplateZipPath())
36-
in_files = [
37-
js,
38-
wasm,
39-
"#platform/web/js/libs/audio.worklet.js",
40-
"#platform/web/js/libs/audio.position.worklet.js",
41-
]
42-
out_files = [
43-
zip_dir.File(binary_name + ".js"),
44-
zip_dir.File(binary_name + ".wasm"),
45-
zip_dir.File(binary_name + ".audio.worklet.js"),
46-
zip_dir.File(binary_name + ".audio.position.worklet.js"),
47-
]
45+
46+
in_files = []
47+
out_files = []
48+
49+
def add_to_template(in_file, zip_file):
50+
out_file = zip_dir.File(zip_file)
51+
in_files.append(in_file)
52+
out_files.append(out_file)
53+
54+
add_to_template(js, binary_name + ".js")
55+
add_to_template(main_wasm, binary_name + ".wasm")
56+
add_to_template("#platform/web/js/libs/audio.worklet.js", binary_name + ".audio.worklet.js")
57+
add_to_template("#platform/web/js/libs/audio.position.worklet.js", binary_name + ".audio.position.worklet.js")
58+
4859
# Dynamic linking (extensions) specific.
49-
if env["dlink_enabled"]:
50-
in_files.append(side) # Side wasm (contains the actual Godot code).
51-
out_files.append(zip_dir.File(binary_name + ".side.wasm"))
60+
if side_wasm is not None:
61+
add_to_template(side_wasm, binary_name + ".side.wasm")
62+
63+
# Those files cannot be renamed, as their relative .wasm file has their name baked in the binary.
64+
# They must also reside besides their original .wasm files.
65+
if main_wasm_dwarf is not None:
66+
add_to_template(main_wasm_dwarf, main_wasm_dwarf.name)
67+
if main_wasm_dwarf_package is not None:
68+
add_to_template(main_wasm_dwarf_package, main_wasm_dwarf_package.name)
69+
if side_wasm_dwarf is not None:
70+
add_to_template(side_wasm_dwarf, side_wasm_dwarf.name)
71+
if side_wasm_dwarf_package is not None:
72+
add_to_template(side_wasm_dwarf_package, side_wasm_dwarf_package.name)
5273

5374
service_worker = "#misc/dist/html/service-worker.js"
5475
if env.editor_build:
@@ -74,33 +95,24 @@ def create_template_zip(env, js, wasm, side):
7495
"___GODOT_ENSURE_CROSSORIGIN_ISOLATION_HEADERS___": "true",
7596
}
7697
html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict)
77-
in_files.append(html)
78-
out_files.append(zip_dir.File(binary_name + ".html"))
98+
add_to_template(html, binary_name + ".html")
7999
# And logo/favicon
80-
in_files.append("#misc/dist/html/logo.svg")
81-
out_files.append(zip_dir.File("logo.svg"))
82-
in_files.append("#icon.png")
83-
out_files.append(zip_dir.File("favicon.png"))
100+
add_to_template("#misc/dist/html/logo.svg", "logo.svg")
101+
add_to_template("#icon.png", "favicon.svg")
84102
# PWA
85103
service_worker = env.Substfile(
86104
target="#bin/godot${PROGSUFFIX}.service.worker.js",
87105
source=service_worker,
88106
SUBST_DICT=subst_dict,
89107
)
90-
in_files.append(service_worker)
91-
out_files.append(zip_dir.File("service.worker.js"))
92-
in_files.append("#misc/dist/html/manifest.json")
93-
out_files.append(zip_dir.File("manifest.json"))
94-
in_files.append("#misc/dist/html/offline.html")
95-
out_files.append(zip_dir.File("offline.html"))
108+
add_to_template(service_worker, "service.worker.js")
109+
add_to_template("#misc/dist/html/manifest.json", "manifest.json")
110+
add_to_template("#misc/dist/html/offline.html", "offline.html")
96111
else:
97112
# HTML
98-
in_files.append("#misc/dist/html/full-size.html")
99-
out_files.append(zip_dir.File(binary_name + ".html"))
100-
in_files.append(service_worker)
101-
out_files.append(zip_dir.File(binary_name + ".service.worker.js"))
102-
in_files.append("#misc/dist/html/offline-export.html")
103-
out_files.append(zip_dir.File("godot.offline.html"))
113+
add_to_template("#misc/dist/html/full-size.html", binary_name + ".html")
114+
add_to_template(service_worker, binary_name + ".service.worker.js")
115+
add_to_template("#misc/dist/html/offline-export.html", binary_name + ".offline.html")
104116

105117
zip_files = env.NoCache(env.InstallAs(out_files, in_files))
106118
env.NoCache(

platform/web/export/export_plugin.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,15 @@ Error EditorExportPlatformWeb::_extract_template(const String &p_template, const
8686
unzCloseCurrentFile(pkg);
8787

8888
//write
89-
String dst = p_dir.path_join(file.replace("godot", p_name));
89+
String dst;
90+
// We cannot rename some files.
91+
if (likely(!file.begins_with("godot") || (!file.ends_with(".dwarf.wasm") && !file.ends_with(".dwarf.wasm.dwp")))) {
92+
String new_file_name = file.replace("godot", p_name);
93+
dst = p_dir.path_join(file.replace("godot", p_name));
94+
} else {
95+
dst = p_dir.path_join(file);
96+
}
97+
9098
Ref<FileAccess> f = FileAccess::open(dst, FileAccess::WRITE);
9199
if (f.is_null()) {
92100
add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not write file: \"%s\"."), dst));

platform/web/js/engine/engine.js

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const Engine = (function () {
7474
* @param {string=} basePath Base path of the engine to load.
7575
* @return {Promise} A ``Promise`` that resolves once the engine is loaded and initialized.
7676
*/
77-
init: function (basePath) {
77+
init: async function (basePath) {
7878
if (initPromise) {
7979
return initPromise;
8080
}
@@ -86,28 +86,18 @@ const Engine = (function () {
8686
Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]);
8787
}
8888
const me = this;
89-
function doInit(promise) {
90-
// Care! Promise chaining is bogus with old emscripten versions.
91-
// This caused a regression with the Mono build (which uses an older emscripten version).
92-
// Make sure to test that when refactoring.
93-
return new Promise(function (resolve, reject) {
94-
promise.then(function (response) {
95-
const cloned = new Response(response.clone().body, { 'headers': [['content-type', 'application/wasm']] });
96-
Godot(me.config.getModuleConfig(loadPath, cloned)).then(function (module) {
97-
const paths = me.config.persistentPaths;
98-
module['initFS'](paths).then(function (err) {
99-
me.rtenv = module;
100-
if (me.config.unloadAfterInit) {
101-
Engine.unload();
102-
}
103-
resolve();
104-
});
105-
});
106-
});
107-
});
89+
async function doInit(response) {
90+
const module = await Godot(me.config.getModuleConfig(loadPath, response));
91+
const paths = me.config.persistentPaths;
92+
// TODO: Handle `err`
93+
const err = await module['initFS'](paths); // eslint-disable-line no-unused-vars
94+
me.rtenv = module; // eslint-disable-line require-atomic-updates
95+
if (me.config.unloadAfterInit) {
96+
Engine.unload();
97+
}
10898
}
10999
preloader.setProgressFunc(this.config.onProgress);
110-
initPromise = doInit(loadPromise);
100+
initPromise = doInit(await loadPromise); // eslint-disable-line require-atomic-updates
111101
return initPromise;
112102
},
113103

platform/web/js/engine/preloader.js

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
2-
function getTrackedResponse(response, load_status) {
3-
function onloadprogress(reader, controller) {
4-
return reader.read().then(function (result) {
5-
if (load_status.done) {
6-
return Promise.resolve();
7-
}
8-
if (result.value) {
9-
controller.enqueue(result.value);
10-
load_status.loaded += result.value.length;
11-
}
12-
if (!result.done) {
13-
return onloadprogress(reader, controller);
14-
}
15-
load_status.done = true;
2+
function trackResponse(response, load_status) {
3+
async function trackReader(reader) {
4+
const result = await reader.read();
5+
if (load_status.done) {
166
return Promise.resolve();
17-
});
7+
}
8+
if (result.value) {
9+
load_status.loaded += result.value.length;
10+
}
11+
if (!result.done) {
12+
return trackReader(reader);
13+
}
14+
load_status.done = true;
15+
return Promise.resolve();
1816
}
19-
const reader = response.body.getReader();
20-
return new Response(new ReadableStream({
21-
start: function (controller) {
22-
onloadprogress(reader, controller).then(function () {
23-
controller.close();
24-
});
25-
},
26-
}), { headers: response.headers });
17+
18+
// Start the reader in parallel of the main response.
19+
const reader = response.clone().body.getReader();
20+
trackReader(reader).catch((_err) => {
21+
// TODO: Handle `_err`
22+
});
23+
24+
// We need to keep the original response intact as much as possible.
25+
return response;
2726
}
2827

2928
function loadFetch(file, tracker, fileSize, raw) {
@@ -36,11 +35,11 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
3635
if (!response.ok) {
3736
return Promise.reject(new Error(`Failed loading file '${file}'`));
3837
}
39-
const tr = getTrackedResponse(response, tracker[file]);
38+
trackResponse(response, tracker[file]);
4039
if (raw) {
41-
return Promise.resolve(tr);
40+
return response;
4241
}
43-
return tr.arrayBuffer();
42+
return response.arrayBuffer();
4443
});
4544
}
4645

0 commit comments

Comments
 (0)