Skip to content

Commit d91f667

Browse files
committed
allow custom wasm binary name
1 parent 62a7381 commit d91f667

File tree

2 files changed

+71
-19
lines changed

2 files changed

+71
-19
lines changed

godot-core/src/init/mod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,38 @@ fn gdext_on_level_deinit(level: InitLevel) {
244244
/// Note that this only changes the name. You cannot provide your own function -- use the [`on_level_init()`][ExtensionLibrary::on_level_init]
245245
/// hook for custom startup logic.
246246
///
247+
/// # Custom Wasm binary filename
248+
/// Upon exporting a game to the web, the library has to know at runtime the exact name of the `.wasm` binary file being used to load
249+
/// each GDExtension. By default, Rust exports the binary as `cratename.wasm`, so that is the name checked by godot-rust by default.
250+
///
251+
/// However, if you need to rename that binary, you can make the library aware of the new binary name with
252+
/// `#[gdextension(wasm_binary = "newname.wasm")]` (don't forget to include the `.wasm` extension).
253+
///
254+
/// For example, to have two simultaneous versions, one supporting multi-threading and the other not, you could add a suffix to the
255+
/// filename of the Wasm binary of the multi-threaded version in your build process. If you choose the suffix `.threads.wasm`, you're
256+
/// in luck as godot-rust already accepts this suffix by default. But let's say you want to use a different suffix, such as
257+
/// `-with-threads.wasm`. For this, you can have a `"nothreads"` feature which, when absent, should produce a suffixed binary, which
258+
/// can be informed to gdext as follows:
259+
///
260+
/// ```no_run
261+
/// # use godot::init::*;
262+
/// struct MyExtension;
263+
///
264+
/// // Tell gdext we add a custom suffix to the binary with thread support
265+
/// // Please note that this is not needed if "mycrate.threads.wasm" is used
266+
/// #[cfg(not(feature = "nothreads"))]
267+
/// #[gdextension(wasm_binary = "mycrate-with-threads.wasm")]
268+
/// unsafe impl ExtensionLibrary for MyExtension {}
269+
///
270+
/// // Binary name unchanged (mycrate.wasm) without thread support
271+
/// #[cfg(feature = "nothreads")]
272+
/// #[gdextension]
273+
/// unsafe impl ExtensionLibrary for MyExtension {}
274+
/// ```
275+
/// Note that simply specifying that option won't change the name of the Wasm binary produced by Rust automatically: you'll still have
276+
/// to rename it by yourself in your build process, as well as specify the updated binary name in your `.gdextension` file. This is
277+
/// just to ensure gdext is aware of the new name given to the binary, avoiding runtime errors.
278+
///
247279
/// # Safety
248280
/// The library cannot enforce any safety guarantees outside Rust code, which means that **you as a user** are
249281
/// responsible to uphold them: namely in GDScript code or other GDExtension bindings loaded by the engine.

godot-macros/src/gdextension.rs

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub fn attribute_gdextension(item: venial::Item) -> ParseResult<TokenStream> {
2929
let mut parser = KvParser::parse_required(&drained_attributes, "gdextension", &impl_decl)?;
3030
let entry_point = parser.handle_ident("entry_point")?;
3131
let entry_symbol = parser.handle_ident("entry_symbol")?;
32+
let wasm_binary = parser.handle_expr("wasm_binary")?;
3233
parser.finish()?;
3334

3435
if entry_point.is_some() && entry_symbol.is_some() {
@@ -48,6 +49,19 @@ pub fn attribute_gdextension(item: venial::Item) -> ParseResult<TokenStream> {
4849
.or(entry_point)
4950
.unwrap_or_else(|| ident("gdext_rust_init"));
5051

52+
let wasm_binary = match wasm_binary {
53+
Some(wasm_binary) => {
54+
quote! {
55+
{
56+
// Ensure a string-like expression is passed
57+
let wasm_binary: &str = &#wasm_binary;
58+
format!("'{}'", wasm_binary.replace("\\", "\\\\").replace("'", "\\'"))
59+
}
60+
}
61+
}
62+
None => quote! { "snakePkgName + '.wasm'" },
63+
};
64+
5165
let impl_ty = &impl_decl.self_ty;
5266

5367
Ok(quote! {
@@ -58,6 +72,9 @@ pub fn attribute_gdextension(item: venial::Item) -> ParseResult<TokenStream> {
5872
// host. See: https://github.com/rust-lang/rust/issues/42587
5973
#[cfg(target_os = "emscripten")]
6074
fn emscripten_preregistration() {
75+
let pkg_name = env!("CARGO_PKG_NAME");
76+
let wasm_binary = #wasm_binary;
77+
6178
// Module is documented here[1] by emscripten, so perhaps we can consider it a part
6279
// of its public API? In any case for now we mutate global state directly in order
6380
// to get things working.
@@ -69,34 +86,37 @@ pub fn attribute_gdextension(item: venial::Item) -> ParseResult<TokenStream> {
6986
// involved, but I don't know what guarantees we have here.
7087
//
7188
// We should keep an eye out for these sorts of failures!
72-
let script = std::ffi::CString::new(concat!(
73-
"var pkgName = '", env!("CARGO_PKG_NAME"), "';", r#"
74-
var libName = pkgName.replaceAll('-', '_') + '.wasm';
75-
if (!(libName in LDSO.loadedLibsByName)) {
89+
let script = format!(
90+
r#"var pkgName = '{pkg_name}';
91+
var snakePkgName = pkgName.replaceAll('-', '_');
92+
var libName = {wasm_binary};
93+
if (!(libName in LDSO.loadedLibsByName)) {{
7694
// Always print to console, even if the error is suppressed.
77-
console.error(`godot-rust could not find the Wasm module '${libName}', needed to load the '${pkgName}' crate. Please ensure a file named '${libName}' exists in the game's web export files. This may require updating Wasm paths in the crate's corresponding '.gdextension' file, or just renaming the Wasm file to the correct name otherwise.`);
78-
throw new Error(`Wasm module '${libName}' not found. Check the console for more information.`);
79-
}
95+
console.error(`godot-rust could not find the Wasm module '${{libName}}', needed to load the '${{pkgName}}' crate. Please ensure a file named '${{libName}}' exists in the game's web export files. This may require updating Wasm paths in the crate's corresponding '.gdextension' file, or just renaming the Wasm file to the correct name otherwise.`);
96+
throw new Error(`Wasm module '${{libName}}' not found. Check the console for more information.`);
97+
}}
8098
var dso = LDSO.loadedLibsByName[libName];
8199
// This property was renamed as of emscripten 3.1.34
82100
var dso_exports = "module" in dso ? dso["module"] : dso["exports"];
83101
var registrants = [];
84-
for (sym in dso_exports) {
85-
if (sym.startsWith("dynCall_")) {
86-
if (!(sym in Module)) {
87-
console.log(`Patching Module with ${sym}`);
102+
for (sym in dso_exports) {{
103+
if (sym.startsWith("dynCall_")) {{
104+
if (!(sym in Module)) {{
105+
console.log(`Patching Module with ${{sym}}`);
88106
Module[sym] = dso_exports[sym];
89-
}
90-
} else if (sym.startsWith("__godot_rust_registrant_")) {
107+
}}
108+
}} else if (sym.startsWith("__godot_rust_registrant_")) {{
91109
registrants.push(sym);
92-
}
93-
}
94-
for (sym of registrants) {
95-
console.log(`Running registrant ${sym}`);
110+
}}
111+
}}
112+
for (sym of registrants) {{
113+
console.log(`Running registrant ${{sym}}`);
96114
dso_exports[sym]();
97-
}
115+
}}
98116
console.log("Added", registrants.length, "plugins to registry!");
99-
"#)).expect("Unable to create CString from script");
117+
"#);
118+
119+
let script = std::ffi::CString::new(script).expect("Unable to create CString from script");
100120

101121
extern "C" { fn emscripten_run_script(script: *const std::ffi::c_char); }
102122
unsafe { emscripten_run_script(script.as_ptr()); }

0 commit comments

Comments
 (0)