Skip to content

Commit 9cca3e0

Browse files
authored
Merge pull request #493 from zecozephyr/wasm-mvp-for-merge
Enable experimental wasm support
2 parents bc01b35 + 816c232 commit 9cca3e0

File tree

11 files changed

+114
-1
lines changed

11 files changed

+114
-1
lines changed

examples/dodge-the-creeps/godot/DodgeTheCreeps.gdextension

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ macos.debug = "res://../../../target/debug/libdodge_the_creeps.dylib"
1111
macos.release = "res://../../../target/release/libdodge_the_creeps.dylib"
1212
macos.debug.arm64 = "res://../../../target/debug/libdodge_the_creeps.dylib"
1313
macos.release.arm64 = "res://../../../target/release/libdodge_the_creeps.dylib"
14+
web.debug.wasm32 = "res://../../../target/wasm32-unknown-emscripten/debug/dodge_the_creeps.wasm"
15+
web.release.wasm32 = "res://../../../target/wasm32-unknown-emscripten/release/dodge_the_creeps.wasm"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# The cargo flag "-Zbuild-std" is also required but this cannot yet be specified for specific
2+
# targets: https://github.com/rust-lang/cargo/issues/8733
3+
[target.wasm32-unknown-emscripten]
4+
rustflags = [
5+
"-C", "link-args=-sSIDE_MODULE=2",
6+
"-C", "link-args=-sUSE_PTHREADS=1",
7+
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
8+
"-Zlink-native-libraries=no",
9+
]

examples/dodge-the-creeps/rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ publish = false
99
crate-type = ["cdylib"]
1010

1111
[dependencies]
12-
godot = { path = "../../../godot", default-features = false }
12+
godot = { path = "../../../godot", default-features = false, features = ["experimental-wasm"] }
1313
rand = "0.8"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
3+
# Must be in dodge-the-creep's rust directory in order to pick up the .cargo/config
4+
cd `dirname "$0"`
5+
6+
# We build the host gdextension first so that the godot editor doesn't complain.
7+
cargo +nightly build --package dodge-the-creeps &&
8+
cargo +nightly build --package dodge-the-creeps --target wasm32-unknown-emscripten -Zbuild-std $@

godot-ffi/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ trace = []
1717
[dependencies]
1818
paste = "1"
1919

20+
[target.'cfg(target_family = "wasm")'.dependencies]
21+
gensym = "0.1.1"
22+
2023
[build-dependencies]
2124
godot-bindings = { path = "../godot-bindings" }
2225
godot-codegen = { path = "../godot-codegen" }

godot-ffi/src/compat/compat_4_1.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::compat::BindingCompat;
1616

1717
pub type InitCompat = sys::GDExtensionInterfaceGetProcAddress;
1818

19+
#[cfg(not(target_family = "wasm"))]
1920
#[repr(C)]
2021
struct LegacyLayout {
2122
version_major: u32,
@@ -25,6 +26,13 @@ struct LegacyLayout {
2526
}
2627

2728
impl BindingCompat for sys::GDExtensionInterfaceGetProcAddress {
29+
// Fundamentally in wasm function references and data pointers live in different memory
30+
// spaces so trying to read the "memory" at a function pointer (an index into a table) to
31+
// heuristically determine which API we have (as is done below) is not quite going to work.
32+
#[cfg(target_family = "wasm")]
33+
fn ensure_static_runtime_compatibility(&self) {}
34+
35+
#[cfg(not(target_family = "wasm"))]
2836
fn ensure_static_runtime_compatibility(&self) {
2937
// In Godot 4.0.x, before the new GetProcAddress mechanism, the init function looked as follows.
3038
// In place of the `get_proc_address` function pointer, the `p_interface` data pointer was passed.

godot-ffi/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ use std::ffi::CStr;
4747
#[doc(hidden)]
4848
pub use paste;
4949

50+
#[doc(hidden)]
51+
#[cfg(target_family = "wasm")]
52+
pub use gensym::gensym;
53+
5054
pub use crate::godot_ffi::{
5155
from_sys_init_or_init_default, GodotFfi, GodotNullableFfi, PrimitiveConversionError,
5256
PtrcallType,

godot-ffi/src/plugins.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,28 @@ macro_rules! plugin_registry {
2626
};
2727
}
2828

29+
#[doc(hidden)]
30+
#[macro_export]
31+
#[allow(clippy::deprecated_cfg_attr)]
32+
#[cfg_attr(rustfmt, rustfmt::skip)]
33+
// ^ skip: paste's [< >] syntax chokes fmt
34+
// cfg_attr: workaround for https://github.com/rust-lang/rust/pull/52234#issuecomment-976702997
35+
macro_rules! plugin_add_inner_wasm {
36+
($gensym:ident,) => {
37+
// Rust presently requires that statics with a custom `#[link_section]` must be a simple
38+
// list of bytes on the wasm target (with no extra levels of indirection such as references).
39+
//
40+
// As such, instead we export a fn with a random name of predictable format to be used
41+
// by the embedder.
42+
$crate::paste::paste! {
43+
#[no_mangle]
44+
extern "C" fn [< rust_gdext_registrant_ $gensym >] () {
45+
__init();
46+
}
47+
}
48+
};
49+
}
50+
2951
#[doc(hidden)]
3052
#[macro_export]
3153
#[allow(clippy::deprecated_cfg_attr)]
@@ -60,6 +82,9 @@ macro_rules! plugin_add_inner {
6082
}
6183
__inner_init
6284
};
85+
86+
#[cfg(target_family = "wasm")]
87+
$crate::gensym! { $crate::plugin_add_inner_wasm!() }
6388
};
6489
};
6590
}

godot-macros/src/gdextension.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,57 @@ pub fn attribute_gdextension(decl: Declaration) -> ParseResult<TokenStream> {
3636
Ok(quote! {
3737
#impl_decl
3838

39+
// This cfg cannot be checked from the outer proc-macro since its 'target' is the build
40+
// host. See: https://github.com/rust-lang/rust/issues/42587
41+
#[cfg(target_os = "emscripten")]
42+
fn emscripten_preregistration() {
43+
// Module is documented here[1] by emscripten so perhaps we can consider it a part
44+
// of its public API? In any case for now we mutate global state directly in order
45+
// to get things working.
46+
// [1] https://emscripten.org/docs/api_reference/module.html
47+
//
48+
// Warning: It may be possible that in the process of executing the code leading up
49+
// to `emscripten_run_script` that we might trigger usage of one of the symbols we
50+
// wish to monkey patch? It seems fairly unlikely, especially as long as no i64 are
51+
// involved, but I don't know what guarantees we have here.
52+
//
53+
// We should keep an eye out for these sorts of failures!
54+
let script = std::ffi::CString::new(concat!(
55+
"var pkgName = '", env!("CARGO_PKG_NAME"), "';", r#"
56+
var libName = pkgName.replaceAll('-', '_') + '.wasm';
57+
var dso = LDSO.loadedLibsByName[libName]["module"];
58+
var registrants = [];
59+
for (sym in dso) {
60+
if (sym.startsWith("dynCall_")) {
61+
if (!(sym in Module)) {
62+
console.log(`Patching Module with ${sym}`);
63+
Module[sym] = dso[sym];
64+
}
65+
} else if (sym.startsWith("rust_gdext_registrant_")) {
66+
registrants.push(sym);
67+
}
68+
}
69+
for (sym of registrants) {
70+
console.log(`Running registrant ${sym}`);
71+
dso[sym]();
72+
}
73+
console.log("Added", registrants.length, "plugins to registry!");
74+
"#)).expect("Unable to create CString from script");
75+
76+
extern "C" { fn emscripten_run_script(script: *const std::ffi::c_char); }
77+
unsafe { emscripten_run_script(script.as_ptr()); }
78+
}
79+
3980
#[no_mangle]
4081
unsafe extern "C" fn #entry_point(
4182
interface_or_get_proc_address: ::godot::sys::InitCompat,
4283
library: ::godot::sys::GDExtensionClassLibraryPtr,
4384
init: *mut ::godot::sys::GDExtensionInitialization,
4485
) -> ::godot::sys::GDExtensionBool {
86+
// Required due to the lack of a constructor facility such as .init_array in rust wasm
87+
#[cfg(target_os = "emscripten")]
88+
emscripten_preregistration();
89+
4590
::godot::init::__gdext_load_library::<#impl_ty>(
4691
interface_or_get_proc_address,
4792
library,

godot/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ serde = ["godot-core/serde"]
1616
lazy-function-tables = ["godot-core/codegen-lazy-fptrs"]
1717
experimental-threads = ["godot-core/experimental-threads"]
1818
experimental-godot-api = ["godot-core/experimental-godot-api"]
19+
experimental-wasm = []
1920

2021
# Private features, they are under no stability guarantee
2122
codegen-full = ["godot-core/codegen-full"]

0 commit comments

Comments
 (0)