Open Bromeon opened 1 year ago
Hello, thanks for opening this issue! Hopefully other contributors can see this and feel motivated to help :smile:
(EDIT: results in this comment are outdated; see https://github.com/godot-rust/gdext/issues/438#issuecomment-1822177121)
I'd like to share here some of the progress we have so far. In particular, I tried to summarize our current progress in a gist, after attempting to compile a Gdext project to WASM from scratch: https://gist.github.com/PgBiel/ffa695a479ef4466cb24755db983950b
The most important section in the link above is the gdext-wasm-min-report.md
file, at the top, where I summarize what I found, also based on the most recent discussions from the Discord thread:
Setup: Tools and steps needed to try to compile Gdext to WASM (at least, to our best knowledge so far), through the wasm32-unknown-emscripten
Rust target. Basically, you'll need a nightly Rust toolchain in order to recompile the Rust standard library (currently needed due to build flags related to threading in WASM), along with custom emscripten flags (more specifically --no-entry -sSIDE_MODULE=2 -sUSE_PTHREADS=1
- the required ones that we know of so far). You'll also need at least -g -sASSERTIONS=2 -sSTACK_OVERFLOW_CHECK=2 -sDEMANGLE_SUPPORT=1
(maybe a few others) to enable debug assertions and symbols on the browser (mapping errors/compiled WASM to source code requires this Chrome extension, though, with those debug flags enabled).
Results (and updates): Errors I found which are currently stopping the gdext web export from working (those errors show up when trying to run, on the browser (Firefox/Chromium), the project exported to the web through the Godot editor). The two main ones are load count too large
, which seems to be related to some inefficiency somewhere in gdext codegen (as that error disappears when compiling with --release
, or with default-features = false
(which apparently disables a good chunk of codegen), and thus is not an unavoidable error), and memory access out of bounds
(which translates to a Stack Overflow/invalid pointer address error when enabling debug assertions) - this last one has been the larger blocker so far (although different errors have been obtained by experimenting with different sets of emscripten flags).
I've been playing with this last error but haven't found much so far. Curiously, with the right settings (larger stack size etc.), I managed to receive an index out of bounds
message on Firefox instead, which apparently occurred in some internal emscripten function cull_zombies
; I couldn't decompile the WASM in firefox (/map to source code), but I suspect it was related to this line, which suggests there's something related to threading going on:
In the Discord thread, there were other theories such as problems with function pointer tables being too large, but also a theory that function pointers in WASM aren't "legit" function pointers due to JavaScript interop in WASM, so using them may require adaptation somehow.
Overall, we need some more investigation on this matter before determining what we can do in gdext to solve this. (It's possible that there are upstream problems as well, from Godot and/or from emscripten, but we just don't know yet if that's the case.) Let's hope other interested contributors - and/or WASM experts :eyes: - drop by and give their opinions as well :wink:
As of writing, this is what I know needs to be done. There's still some gdext work to be done, it seems like, but if anyone gets involved this should hopefully serve as a recap. If anyone wants to run things by me I'm happy to help at least get something going. I'm usually easy to reach on discord. But please read this first!
You'll need to manually compile the template. Godot doesn't ship with a web template that has gdextension support. It's important to note this template will be SPECIFIC to the version of emscripten that it was compiled with. This probably means you'll need to install emsdk along with a few other tools. Follow the official docs for a guide on what to do, and where to place the template zip.
You do not need to recompile the entire editor. ONLY the template. If compiling from git, don't forget to make use of tags prior to compiling anything so it matches the editor version you are using.
You need to be able to use unstable features. So, a nightly build is probably the best choice.
rustup toolchain install nightly
rustup default nightly
rustup target add wasm32-unknown-emscripten
At the root directory of your project, you'll need to make a .cargo/config.toml
file.
The big thing is needing to compile your extension with SHARED_MEMORY. This requires a few flags and rebuilding std (this is because the std included from rustup was not compiled with it enabled).
[unstable]
build-std = ["std"]
[target.wasm32-unknown-emscripten]
rustflags = [
"-C", "link-args=-sSIDE_MODULE=2 -sEXPORT_ALL=1",
"-Zlink-native-libraries=no",
"-C", "link-args=-pthread",
"-C", "target-feature=+bulk-memory",
"-C", "target-feature=+atomics",
"-C", "target-feature=+mutable-globals",
"-C", "link-args=-sSHARED_MEMORY=1",
]
You'll then have to either compile with --target wasm32-unknown-emscripten
or add
[build]
target = ["wasm32-unknown-emscripten"]
to the .cargo/config.toml
file
Don't forget to add a web section to your extension's .gdextension
file.
Something like
web.debug = "res://path/to/debug/project.wasm"
web.release = "res://path/to/release/project.wasm"
You need to have HTTPS and enable some cross-origin headers. Either use certbot or a self-signed cert.
For nginx, enabling the necessary headers is quite simple by just adding
add_header 'Cross-Origin-Opener-Policy' 'same-origin';
add_header 'Cross-Origin-Embedder-Policy' 'require-corp';
inside the location {}
section of your site. Look for something similar for other web servers.
Use chrome and install this extension. It'll basically allow the devtools (F12) to act like a worse LLDB. Breakpoints, memory inspector, etc.
-sEXPORT_ALL=1
is probably not necessary. Only exporting the gdext entry symbol is (probably?) necessary. But I would leave that there until you know it works.Newer info:
Thanks to @zecozephyr for figuring out how to work around some of the rust+emscripten limitations, we currently have gdext on wasm working with the dodge-the-creeps example.
We're hoping to get more people to test with their projects to jump in and confirm/deny this. This is coming with a big DISCLAIMER that this patch is probably buggy and not production ready. On top of that, we literally found two bugs in emscripten in the process of this, so... yeah. Bugs!
Godot 4.1.3+ or 4.2-dev is necessary. The only browser supported appears to be Chrome (Firefox and Safari don't work with GDExtension yet, discussion here).
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
rustup target add wasm32-unknown-emscripten --toolchain nightly
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install 3.1.39
./emsdk activate 3.1.39
source ./emsdk.sh (or ./emsdk.bat on windows)
source ./emsdk.sh
/ ./emsdk.bat
for every new shell..cargo/config.toml
[target.wasm32-unknown-emscripten]
rustflags = [
"-C", "link-args=-sSIDE_MODULE=2",
"-C", "link-args=-sUSE_PTHREADS=1",
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
"-Zlink-native-libraries=no"
]
Cargo.toml
to add the "experimental-wasm" feature.
godot = { git = "https://github.com/godot-rust/gdext", branch = "master", features = ["experimental-wasm", "lazy-function-tables"] }
NOTE: May need to enable the "lazy-function-tables" feature for a successful runtime and a SIGNIFICANTLY shorter compile time.
.gdextension
file. Will be similar to the others in that file, roughly something like this
web.debug.wasm32 = "res://../target/wasm32-unknown-emscripten/debug/EXTENSIONNAME.wasm"
web.release.wasm32 = "res://../target/wasm32-unknown-emscripten/release/EXTENSIONNAME.wasm"
emcc --version
and then compile your code. Debug builds seem to take a while longer to load in the browser, but release builds take a very long time to compile, as well as a lot of RAM. Compile with:
cargo +nightly build -Zbuild-std --target wasm32-unknown-emscripten
From here you have two choices to run your game, as you need to spawn a server to serve the HTML file (just double-clicking probably won't work).
The first and easiest choice is through the Godot editor itself: from the main scene view godot, press "Remote Debug > Run in Browser"
NOTE 1: The above will always run a debug build, so make sure to compile with debug (or change the path to the debug build in the NAME.gdextension
file) if you'd like to use the "Run in Browser" button.
NOTE 2: If your default browser isn't Chrome, you'll need to copy the URL and paste it into Chrome (e.g. manually head to localhost:8060
and click the HTML file), as your game will not work on Firefox or Safari (as of the time of writing).
Alternatively, run the web export process fully and point a web-server at the exported files. If this is what you'd like to do, you're going to need to have a web-server with some specific cross-origin headers to get this part to work. A quick way to do this is through simple-http-server
:
cargo install simple-http-server
simple-http-server --coop --coep --nocache ./path/to/export/
And then open up a Chrome/Chromium browser at localhost:8000/EXPORTNAME.html
. It may take a while to load, especially if you built with debug instead of release.
Either post here or post in the discord thread where most of this has been taking place.
Preliminary support has now been merged into master by https://github.com/godot-rust/gdext/pull/493.
Instructions remain largely unchanged except that one now needs to enable the feature experimental-wasm
to explicitly opt-in to wasm builds.
Since I know that there's quite a few people looking to run gdext on wasm, but are afraid to use it due to instability here's a few words:
If you haven't made anything yet within godot and are able to switch engines the bevy game engine is pretty good if you want to use rust and have web-assembly/full multiplatform support. Bevy is in much earlier stages than godot, but so is gdext and it's understandable that each of them have their own challenges. Bevy having no editor could be preferrable as I've seen people ditch the editor for pure-code scene creation in some cases.
As someone who had experience now with both - gdext and bevy are good enough to make work with few days of research and hands-on fixes, as a bonus - bevy has webgpu support.
@DeprecatedLuke please don't hijack the issue tracker only to advertise other projects. People are aware that there are many choices in the Rust ecosystem.
Godot itself works absolutely fine with web (there are tons of game jam entries for it, and I have used it myself). The main issue specific to GDExtension is that it's not yet fully supported in non-Chromium based browsers; something that will likely improve.
If you want to discuss the issue further, please bring it up on Discord, here is not the right place.
I wonder if https://github.com/godotengine/godot-cpp/pull/1489 has relevance for us (panics might use the same mechanism as exceptions) 🤔
Perhaps, but this will depend on Rust and LLVM support for wasm exceptions, which seems to be fairly early days still. However, as seen here https://github.com/rust-lang/rust/issues/118168 , since last year both the language and stdlib got support for building with panic=unwind
(it's still opt-in and unstable, so an explicit -Z
flag is needed), so maybe it's worth experimenting with it now? Seems like LLVM might still have some problems with it, but I don't know enough about the current situation to tell without testing.
A new problem seems to be appearing when exporting gdext to Wasm on Godot 4.3 (commit 26d1577f3985363faab48a65e9a0d9eed0e26d86), even with Emscripten 3.1.62, which supposedly brings fixes for dynamic linking (something we depend on). Here are my observations so far:
Aborted(Assertion failed: undefined symbol 'invoke_v'. perhaps a side module was not linked in? if this global was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment)
, where invoke_v
varies depending on which function call triggered it (sometimes it's invoke_vii
, sometimes invoke_iii
). It occurs inside gdext_rust_init
generated by the #[gdextension]
macro (see below).threads=yes
or threads=no
(and disabling -pthread
). In other words, this is unrelated to thread support.-g
and using the WASM Debugger extension, I traced the error back to this specific call: https://github.com/godot-rust/gdext/blob/79edae358e224225248f0f6f7ca3727130d22fd5/godot-macros/src/gdextension.rs#L90call $invoke_v
to call the gdext_registration
function, but invoke_v
is not available, so that fails.invoke_(returned wasm type)(parameters' wasm types)
and crash.
gdext_registration
function instead of calling it, but it would still fail at CString::new
and .expect
. Replacing with a CStr literal (c"the JS code here"
) postponed the error to the function call the_cstr.as_ptr()
.let script = b"JS code here \0"
and later replacing the call script.as_ptr()
with a simple pointer cast: script as *const _ as *const std::ffi::c_char
.invoke_signaturehere
inside it. Additionally, this script has some logic to fix missing symbols before calling gdext's auto-generated registration functions for godot classes. In particular, it tries to fix missing dynCall_v
(for example) functions (we were getting errors related to missing dynCall_signaturehere
functions before). So, it seemed logical that we could just improve upon this logic to also fix missing invoke_v
(and similar) symbols to make calls to Rust functions work, so I tried to replace line 62 (below) with if (sym.startsWith("dynCall_") || sym.startsWith("invoke_")) {
:
https://github.com/godot-rust/gdext/blob/79edae358e224225248f0f6f7ca3727130d22fd5/godot-macros/src/gdextension.rs#L62console.log
statements to debug this, in particular to print every single sym
checked in this for loop (not only dynCall
symbols), and it seems that, while the dynCall
symbols are correctly checked and patched, the invoke
symbols are simply not checked by the for loop at all. They don't appear to be present in dso_exports
.I haven't been able to proceed from here. I'm not really sure of the semantics behind dso_exports
, or in particular what determines the symbols that go in it, so I can't tell, at this moment, if this is Godot's fault, Emscripten's fault, or just some specific flag we're missing. (Worth mentioning that -sEMULATE_FUNCTION_POINTERS
was tested but did not fix it.) I'm not sure how we could make the invoke
symbols appear in dso_exports
. But I'll send updates once I get more information.
Paging @zecozephyr for further ideas.
Emscripten 3.1.62 gdext 79edae358e224225248f0f6f7ca3727130d22fd5 rustc 1.81.0-nightly (6be96e386 2024-07-09) Godot 4.3-master 26d1577f3985363faab48a65e9a0d9eed0e26d86
Full logs from my latest attempt: run.log
My modified lib.rs
code (I inlined #[gdextension]
to test this):
Note that emscripten_preregistration
isn't used anymore since I manually inlined its body. (#[inline(always)]
helped but wasn't very reliable.) Also note that pkgName
is set to hello_gdext
in the script - this should be changed to the name of your library crate.
```rs
mod player;
use godot::prelude::*;
struct MyExtension;
// #[gdextension]
// unsafe impl ExtensionLibrary for MyExtension {}
unsafe impl ExtensionLibrary for MyExtension {}
// #[cfg(target_os = "emscripten")]
#[inline(always)]
fn emscripten_preregistration() {
let script = b"var pkgName = \'hello_gdext\';\n console.log(\"[DEBUG] Reached point A.\");\n var libName = pkgName.replaceAll(\'-\', \'_\') + \'.wasm\';\n console.log(\"[DEBUG] Reached point B.\");\n var dso = LDSO.loadedLibsByName[libName];\n // This property was renamed as of emscripten 3.1.34\n var dso_exports = \"module\" in dso ? dso[\"module\"] : dso[\"exports\"];\n var registrants = [];\n console.log(\"[DEBUG] Reached point C.\");\n for (sym in dso_exports) {\n console.log(`[DEBUG] Let\'s check this symbol \'${sym}\'...`);\n if (sym.startsWith(\"dynCall_\") || sym.startsWith(\"invoke_\")) {\n console.log(\"[DEBUG] It is special...\");\n if (!(sym in Module)) {\n console.log(`Patching Module with ${sym}`);\n Module[sym] = dso_exports[sym];\n }\n } else if (sym.startsWith(\"rust_gdext_registrant_\")) {\n registrants.push(sym);\n console.log(\"[DEBUG] Pushed one.\");\n }\n }\n for (sym of registrants) {\n console.log(`Running registrant ${sym}`);\n dso_exports[sym]();\n }\n console.log(\"Added\", registrants.length, \"plugins to registry!\");\n \0";
extern "C" {
fn emscripten_run_script(script: *const std::ffi::c_char);
}
unsafe {
emscripten_run_script(script as *const _ as *const std::ffi::c_char);
}
}
#[no_mangle]
unsafe extern "C" fn gdext_rust_init(
interface_or_get_proc_address: ::godot::sys::InitCompat,
library: ::godot::sys::GDExtensionClassLibraryPtr,
init: *mut ::godot::sys::GDExtensionInitialization,
) -> ::godot::sys::GDExtensionBool {
#[cfg(target_os = "emscripten")]
{
let script = b"var pkgName = \'hello_gdext\';\n console.log(\"[DEBUG] Reached point A.\");\n var libName = pkgName.replaceAll(\'-\', \'_\') + \'.wasm\';\n console.log(\"[DEBUG] Reached point B.\");\n var dso = LDSO.loadedLibsByName[libName];\n // This property was renamed as of emscripten 3.1.34\n var dso_exports = \"module\" in dso ? dso[\"module\"] : dso[\"exports\"];\n var registrants = [];\n console.log(\"[DEBUG] Reached point C.\");\n for (sym in dso_exports) {\n console.log(`[DEBUG] Let\'s check this symbol \'${sym}\'...`);\n if (sym.startsWith(\"dynCall_\") || sym.startsWith(\"invoke_\")) {\n console.log(\"[DEBUG] It is special...\");\n if (!(sym in Module)) {\n console.log(`Patching Module with ${sym}`);\n Module[sym] = dso_exports[sym];\n }\n } else if (sym.startsWith(\"rust_gdext_registrant_\")) {\n registrants.push(sym);\n console.log(\"[DEBUG] Pushed one.\");\n }\n }\n for (sym of registrants) {\n console.log(`Running registrant ${sym}`);\n dso_exports[sym]();\n }\n console.log(\"Added\", registrants.length, \"plugins to registry!\");\n \0";
extern "C" {
fn emscripten_run_script(script: *const std::ffi::c_char);
}
unsafe {
emscripten_run_script(script as *const _ as *const std::ffi::c_char);
}
}
// emscripten_preregistration();
::godot::init::__gdext_load_library::
Here's the script above in a more readable form:
var pkgName = {env!("CARGO_PKG_NAME")};
console.log("[DEBUG] Reached point A.");
var libName = pkgName.replaceAll('-', '_') + '.wasm';
console.log("[DEBUG] Reached point B.");
var dso = LDSO.loadedLibsByName[libName];
// This property was renamed as of emscripten 3.1.34
var dso_exports = "module" in dso ? dso["module"] : dso["exports"];
var registrants = [];
console.log("[DEBUG] Reached point C.");
for (sym in dso_exports) {
console.log(`[DEBUG] Let's check this symbol '${sym}'...`);
if (sym.startsWith("dynCall_") || sym.startsWith("invoke_")) {
console.log("[DEBUG] It is special...");
if (!(sym in Module)) {
console.log(`Patching Module with ${sym}`);
Module[sym] = dso_exports[sym];
}
} else if (sym.startsWith("rust_gdext_registrant_")) {
registrants.push(sym);
console.log("[DEBUG] Pushed one.");
}
}
for (sym of registrants) {
console.log(`Running registrant ${sym}`);
dso_exports[sym]();
}
console.log("Added", registrants.length, "plugins to registry!");
.cargo/config.toml (added some flags to enable debugging):
[target.wasm32-unknown-emscripten]
rustflags = [
"-C",
"link-args=-sSIDE_MODULE=2",
# "-C",
# "link-args=-pthread", # was -sUSE_PTHREADS=1 in earlier emscripten versions
"-C",
"target-feature=+atomics,+bulk-memory,+mutable-globals",
"-Clink-args=-sEXPORT_ALL=1",
# Trying out stuff
"-Clink-arg=-O0",
"-Clink-arg=-g",
# "-Clink-arg=-sASSERTIONS=2",
"-Clink-arg=-sDEMANGLE_SUPPORT=1",
# "-Clink-arg=-sEMULATE_FUNCTION_POINTER_CASTS",
# ---
"-Zlink-native-libraries=no",
]
Test project (adapted from https://github.com/PgBiel/hello-gdext-wasm, but updated for compatibility with gdext 0.1): wasm-test-project.zip
Compiled web export templates (with and without threads):
godot.web.template_debug.wasm32.dlink.zip godot.web.template_debug.wasm32.nothreads.dlink.zip
TL;DR: Change your .cargo/config.toml
to
[target.wasm32-unknown-emscripten]
rustflags = [
"-C",
"link-args=-sSIDE_MODULE=2",
"-C",
"link-args=-pthread", # was -sUSE_PTHREADS=1 in earlier emscripten versions
"-C",
"target-feature=+atomics,+bulk-memory,+mutable-globals",
"-Cllvm-args=-enable-emscripten-cxx-exceptions=0",
"-Zlink-native-libraries=no",
]
That is, add "-Cllvm-args=-enable-emscripten-cxx-exceptions=0"
to it.
This will work with the official Godot web export templates for 4.3-beta2 - recompiling Godot is not needed (at least, for this sample project I'm smoke-testing with).
If the flag above doesn't fix it (it should), try adding the rustflags below as well (please ping me if you happen to need to use those flags):
[target.wasm32-unknown-emscripten]
rustflags = [
# ... other flags ...
"-Clink-arg=-fwasm-exceptions",
"-C",
"link-args=-sSUPPORT_LONGJMP=wasm",
"-Cllvm-args=-enable-emscripten-cxx-exceptions=0",
"-Cllvm-args=-wasm-enable-sjlj",
"-C",
"link-args=-sDISABLE_EXCEPTION_CATCHING=1",
]
As a result, gdext is also running on Firefox (didn't work there before)! :tada:
Basically, since https://github.com/godotengine/godot/pull/93143 (which was merged in time for Godot 4.3-beta2), Godot's web export templates are compiled with -sSUPPORT_LONGJMP=wasm
, which changes how exceptions work in wasm. GDExtension users need to use that flag with emscripten as well for full compatibility with Godot's web build, otherwise Bad Things can happen. I noticed this requirement from the fact that godot-cpp (the library for GDExtension in C++) had to add this flag too: https://github.com/godotengine/godot-cpp/pull/1489
I tried to add that linker flag directly, but that didn't fix it. After some googling, I got here https://github.com/rust-lang/rust/issues/112195#issuecomment-1573060218 . Adding all of these flags as well fixed it!
Eventually, after some testing, the only flag necessary turned out to be "-Cllvm-args=-enable-emscripten-cxx-exceptions=0"
.
After doing some research regarding the invoke_...
functions mentioned in the comment above, as well as looking and searching through Emscripten's codebase, I eventually found this:
Which suggested that invoke functions are only exported when -sSUPPORT_LONGJMP=emscripten
and/or -sDISABLE_EXCEPTION_CATCHING=0
(two settings which seem to be linked) are set.
I then remembered seeing Bromeon's comment above https://github.com/godot-rust/gdext/issues/438#issuecomment-2167555284 , about Godot switching to -sSUPPORT_LONGJMP=wasm
, which made me have an "aha!" moment: now that Godot is using this flag, it might not be exporting the invoke_...
functions anymore. Further, this might imply that setting -sSUPPORT_LONGJMP=wasm
on our side would fix it by having emscripten not generate calls to invoke_...
at all.
With the testing above, this turned out to be the case! (With a few adjustments...)
Godot 4.3's web export template can be compiled with threads=no
to disable multi-threading (since https://github.com/godotengine/godot/pull/85939). This can be useful to improve web export compatibility in some contexts.
To support this, one has to remove the -pthread
flag while compiling the GDExtension (that is, remove the lines containing -Clink-args=-pthread
from .cargo/config.toml
).
(Also, based on https://github.com/godotengine/godot-cpp/pull/1451 , we should eventually warn gdext users that you should have separate threaded - with web.debug/release.threads.wasm32
- and non-threaded - with web.debug/release.wasm32
- wasm builds.)
However, while the fix in the above comment works to remove the invoke_
-related errors, it appears gdext itself doesn't support a threads=no
build yet, as a panic occurs during init, specifically on this call to std::thread::current()
:
```log [C/C++ DevTools Support (DWARF)] Loading debug symbols for wasm://wasm/hello_gdext.wasm-0b61c0d6... VM74:18 Running registrant rust_gdext_registrant___gensym_abc20a22af3d4ce596c2106f05dd70f8 VM74:18 Running registrant rust_gdext_registrant___gensym_5c516572276c4221889ba4ff9b4c2425 VM74:21 Added 2 plugins to registry! Hello Gdext.js:46812 Aborted(undefined) onPrintError @ Hello Gdext.js:46812 abort @ Hello Gdext.js:561 ___cxa_throw @ Hello Gdext.js:15564 $panic_unwind::imp::panic::h2f129ac708c2ef5d @ emcc.rs:110 $__rust_start_panic @ lib.rs:100 $rust_panic @ panicking.rs:857 $std::panicking::rust_panic_with_hook::h0a73500366cd9d48 @ panicking.rs:821 $std::panicking::begin_panic_handler::_$u7b$$u7b$closure$u7d$$u7d$::h074888851122b07b @ hello_gdext.wasm-0b61c0d6:0x15f7f9 $std::sys::backtrace::__rust_end_short_backtrace::hfd00b625b158fbd6 @ backtrace.rs:171 $rust_begin_unwind @ panicking.rs:661 $core::panicking::panic_fmt::h3181722ab72257cd @ panicking.rs:74 $core::panicking::panic_display::h90455b1a7f394b21 @ panicking.rs:264 $core::option::expect_failed::h15de83ddbb89ea02 @ option.rs:2023 $core::option::Option$LT$T$GT$::expect::h01f3b96be4d69a4b @ option.rs:926 $std::thread::current::h3db11b9453b88171 @ mod.rs:747 $godot_ffi::binding::single_threaded::BindingStorage::initialize::hca50a5aad300095a @ single_threaded.rs:54 $godot_ffi::binding::initialize_binding::h693d3b5b51932c57 @ mod.rs:229 $godot_ffi::initialize::hb56eb1a096004668 @ lib.rs:191 $godot_core::init::__gdext_load_library::_$u7b$$u7b$closure$u7d$$u7d$::h9d6d1c2377ac11a1 @ mod.rs:44 $core::ops::function::FnOnce::call_once::hfe3b5d72725fcec9 @ function.rs:250 $std::panicking::try::do_call::h670e33ec316b1f8a @ panicking.rs:553 $__rust_try @ hello_gdext.wasm-0b61c0d6:0x1a032 $std::panicking::try::h841d2c637343daa1 @ panicking.rs:517 $std::panic::catch_unwind::h19be830f5594ac2c @ panic.rs:350 $godot_core::private::handle_panic_with_print::h8f822c92e6964dc4 @ private.rs:281 $godot_core::private::handle_panic::hfedd59d33aaf8511 @ private.rs:218 $godot_core::init::__gdext_load_library::h22feb0dba4d03d9b @ mod.rs:63 $gdext_rust_init @ lib.rs:8 $func120802 @ 0c895ea6:0x2aff928 $func120821 @ 0c895ea6:0x2b028e5 $func120828 @ 0c895ea6:0x2b0334d $func108540 @ 0c895ea6:0x2806e32 $func108544 @ 0c895ea6:0x28077ff $func108547 @ 0c895ea6:0x2808783 $func108554 @ 0c895ea6:0x280a218 $func121195 @ 0c895ea6:0x2b26455 $func121210 @ 0c895ea6:0x2b27373 $func103419 @ 0c895ea6:0x262c14d $func1392 @ 0c895ea6:0x2de17a $_Z14godot_web_mainiPPc @ 0c895ea6:0x2d6c71 __Z14godot_web_mainiPPc @ Hello Gdext.js:2030 $__main_argc_argv @ 0071a6a6:0x8fa0d callMain @ Hello Gdext.js:46235 (anonymous) @ Hello Gdext.js:47139 (anonymous) @ Hello Gdext.js:47134 Promise.then (async) start @ Hello Gdext.js:47113 (anonymous) @ Hello Gdext.js:47172 Promise.then (async) startGame @ Hello Gdext.js:47171 (anonymous) @ Hello Gdext.html:181 (anonymous) @ Hello Gdext.html:195 Hello Gdext.html:139 RuntimeError: Aborted(undefined) at abort (Hello Gdext.js:580:11) at ___cxa_throw (Hello Gdext.js:15564:29) at panic_unwind::imp::panic::h2f129ac708c2ef5d (emcc.rs:110) at __rust_start_panic (lib.rs:100) at rust_panic (panicking.rs:857) at std::panicking::rust_panic_with_hook::h0a73500366cd9d48 (panicking.rs:821) at std::panicking::begin_panic_handler::_$u7b$$u7b$closure$u7d$$u7d$::h074888851122b07b (hello_gdext.wasm-0b61c0d6:0x15f7f9) at std::sys::backtrace::__rust_end_short_backtrace::hfd00b625b158fbd6 (backtrace.rs:171) at rust_begin_unwind (panicking.rs:661) at core::panicking::panic_fmt::h3181722ab72257cd (panicking.rs:74) displayFailureNotice @ Hello Gdext.html:139 Promise.then (async) (anonymous) @ Hello Gdext.html:191 (anonymous) @ Hello Gdext.html:195 [C/C++ DevTools Support (DWARF)] Loaded debug symbols for wasm://wasm/hello_gdext.wasm-0b61c0d6, found 1374 source file(s) ```
Seems like we could work around this somehow by avoiding calling this function, but perhaps there is some other flag missing which would make it work. Either way, non-threaded builds do not work at the moment because of this.
One issue found by @Ughuuu (posting for awareness, but also as a reminder so we can make a fix later): the current workaround for emscripten support assumes that the wasm binary is named YOUR_CRATE_HERE.wasm
, but it might be renamed, resulting in cryptic errors related to attempting to access "undefined" (the lib wasn't found). We can improve this by first throwing a more helpful error if the library with the crate's name wasn't found ("please don't rename the binary" but a bit more formal). Ideally, we'd find some other way to detect our own lib name, noting that we have access to the Module
object; maybe it has some insight. Some compile-time parameter/env var could also work, but wouldn't be optimal for extensions made to be distributed, since end users might still accidentally rename the binaries - but I guess a more helpful error would already be enough in this case, since a smoke test would fail and immediately indicate the reason.
Note also some changes in flags like https://github.com/godotengine/godot-cpp/pull/1566.
We currently elaborate in the book how to compile WASM, but I wonder if there's a better way than copy-pasting a .cargo/config.toml
file, which will also become outdated if flags change?
In principle it seems inevitable that we'll be playing a game of "cat and mouse" here, having to manually keep up with any new flags introduced by Godot, though it'd be nice if:
Getting a bizarre issue that I can't find a solution for. I've tried every single flag combination in this thread, but I keep getting "resolved is not a function".
I've tested with both Firefox and Chromium on ArchLinux.
emcc: 3.1.39
rustc: 1.81.0
godot: v4.3.stable.arch_linux
It seems like something called __handle_stack_overflow
is not getting resolved.
Getting a bizarre issue that I can't find a solution for. I've tried every single flag combination in this thread, but I keep getting "resolved is not a function".
Try disabling -Clink-arg=-sASSERTIONS=2
.
However I also got some different problems, most notably
Too much recursion
(Chromium-based)Maximum call stack size exceeded
(Firefox)which I couldn't resolve. There's some discussion around it on Discord.
Disabling -Clink-arg=-sASSERTIONS=2
resulted in getting:
Chromium:
Firefox:
Figured out a solution for my problem, documenting it here for anyone else who encounters a similar problem.
Following the book for setting up a project for web export, the .carg/config.toml
contains this flag "-C", "link-args=-pthread"
, but with this flag enabled I couldn't get my project to load in either Firefox or Chromium. So I tried adding in every flag I found in this issue thread, but no combinations ended up fixing my issue. So I thought to remove the flag "-C", "link-args=-pthread"
, this ended up with me getting the errors found from my previous comments.
After going through the 5 stages of grief over the course of 5 days, I decided to try everything I could think of, skipping past all of the attempts that didn't work, here's what did work:
Followed the docs for exporting to web
Using this for my .cargo/config.toml
[target.wasm32-unknown-emscripten]
rustflags = [
"-C", "link-args=-sSIDE_MODULE=2",
"-C", "link-args=-pthread", # was -sUSE_PTHREADS=1 in earlier emscripten versions
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
"-Z", "link-native-libraries=no",
"-Z", "link-native-libraries=no",
"-C", "link-arg=-fwasm-exceptions",
"-C", "link-args=-sSUPPORT_LONGJMP=wasm",
"-C", "llvm-args=-enable-emscripten-cxx-exceptions=0",
"-C", "llvm-args=-wasm-enable-sjlj",
"-C", "link-args=-sDISABLE_EXCEPTION_CATCHING=1",
]
Used emcc 3.1.66
Enabled Thread Support in the web export
Now I can load my project in Firefox and Chromium. I don't know what specifically ended up fixing my problems, but everything works for me now.
Thanks a lot! What is really not great at the moment is that the .cargo/config.toml
lives completely outside the godot-rust knowledge, however it really impacts how the emscripten compilation works. It would be much better if we could generate this (so that users don't have to) or at least validate some of the flags and see if they're compatible with our library.
For example, gdext has two features: experimental-wasm
and experimental-wasm-nothreads
, the latter runs in "no threads" mode and requires matching configuration in Godot's export settings as well as the .cargo/config.toml
flags.
I'm not sure what's the best way to keep library options in sync with .cargo/config.toml
and export settings... ideally better than looking for certain files in certain directories and trying to parse them?
Maybe a hacky solution could be to have a macro or function that is called from a user created build.rs
file, that parses and modifies the .cargo/config.toml
.
For now though the docs should be updated to indicate the experimental-wasm
and experimental-wasm-nothreads
features and the required export options.
Figured out a solution for my problem, documenting it here for anyone else who encounters a similar problem.
Following the book for setting up a project for web export, the
.carg/config.toml
contains this flag"-C", "link-args=-pthread"
, but with this flag enabled I couldn't get my project to load in either Firefox or Chromium. So I tried adding in every flag I found in this issue thread, but no combinations ended up fixing my issue. So I thought to remove the flag"-C", "link-args=-pthread"
, this ended up with me getting the errors found from my previous comments.After going through the 5 stages of grief over the course of 5 days, I decided to try everything I could think of, skipping past all of the attempts that didn't work, here's what did work:
- Followed the docs for exporting to web
- Using this for my
.cargo/config.toml
[target.wasm32-unknown-emscripten] rustflags = [ "-C", "link-args=-sSIDE_MODULE=2", "-C", "link-args=-pthread", # was -sUSE_PTHREADS=1 in earlier emscripten versions "-C", "target-feature=+atomics,+bulk-memory,+mutable-globals", "-Z", "link-native-libraries=no", "-Z", "link-native-libraries=no", "-C", "link-arg=-fwasm-exceptions", "-C", "link-args=-sSUPPORT_LONGJMP=wasm", "-C", "llvm-args=-enable-emscripten-cxx-exceptions=0", "-C", "llvm-args=-wasm-enable-sjlj", "-C", "link-args=-sDISABLE_EXCEPTION_CATCHING=1", ]
- Used
emcc 3.1.66
- Enabled Thread Support in the web export
Now I can load my project in Firefox and Chromium. I don't know what specifically ended up fixing my problems, but everything works for me now.
thank you! I was having a whole raft of issue up to this point. Checking the "Thread Support" checkbox in Godot and using your cargo.toml make everything work all of a sudden!
@Rune580
Thanks a lot, i was having the same issue resolved is not a function
with the same error
tmp_js_export.html:139 TypeError: resolved is not a function
at stubs.<computed> (tmp_js_export.js:9:30850)
at gdext_rust_init
and your solution worked for me , using emcc 3.1.39 and Godot 4.3.stable
Just to clarify, you don't have to enable Thread support if you remove the -pthread
flag and add the experimental-wasm-nothreads
feature to gdext. In fact, you can have two separate builds (one with and one without thread support) for the same extension so that the Thread support toggle in the editor works properly (which is the recommended approach, other than only building your extension for threaded environments). We will be adding this to the docs; see my comment in the relevant PR for instructions in the meantime: https://github.com/godot-rust/book/pull/56#discussion_r1782111773
This issue serves as a knowledge base for approching WASM builds. Ideally it should have more consolidated pieces of information. Please edit your responses to update anything outdated.
For free-form discussion, check out the Discord thread.