Skip to content

Remove gensym, fix Wasm class registration #1092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 23, 2025
Merged
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
16 changes: 7 additions & 9 deletions .github/other/deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -182,18 +182,16 @@ allow = [
# List of crates to deny
# See also: https://github.com/RustSec/advisory-db/issues/173
deny = [
# unmaintained since Feb 20 + https://github.com/noamtashma/owning-ref-unsoundness
{ name = "owning_ref" },

# unmaintained since Jul 20, RustSec refuses to acknowledge "because author says maintained"
# https://togithub.com/rustsec/advisory-db/pull/891
{ name = "im" },
{ name = "im-rc" },

# Too heavy in default setup. Can be included for api-custom etc.
# Note: rand indirectly depends on syn via rand_chacha -> ppv-lite86 -> zerocopy -> zerocopy-derive.
{ name = "syn", wrappers = ["bindgen", "gensym", "zerocopy-derive"] },
{ name = "syn", wrappers = ["bindgen"] },
{ name = "serde", wrappers = ["godot-core", "serde_json"] },
{ name = "rand" },

# Unmaintained since Jul 2020, RustSec refuses to acknowledge "because author says maintained".
# https://github.com/rustsec/advisory-db/pull/891
{ name = "im" },
{ name = "im-rc" },
]

# List of features to allow/deny
Expand Down
8 changes: 3 additions & 5 deletions godot-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,14 @@ api-4-3 = ["godot-bindings/api-4-3"]
api-4-4 = ["godot-bindings/api-4-4"]
# ]]

# TODO: get rid of gensym, which is implementable with proc-macros. Update cargo-deny.
# gensym pulls in entire syn for 10 LoC. See https://github.com/regiontog/gensym/blob/master/src/lib.rs.
# Might however require godot-ffi to depend on godot-macro, which is not ideal...
[dependencies]

[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2.153"
libc = "0.2.171"

[target.'cfg(target_family = "wasm")'.dependencies]
gensym = "0.1.1"
# Only needed for WASM identifier generation.
godot-macros = { path = "../godot-macros", version = "=0.2.4", features = ["experimental-wasm"] }

[build-dependencies]
godot-bindings = { path = "../godot-bindings", version = "=0.2.4" }
Expand Down
16 changes: 3 additions & 13 deletions godot-ffi/src/interface_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

use crate as sys;

#[cfg(not(target_family = "wasm"))]
use crate::toolbox::read_version_string;

// In WebAssembly, function references and data pointers live in different memory spaces, so trying to read the "memory"
// at a function pointer (an index into a table) to heuristically determine which API we have (as is done below) won't work.
#[cfg(target_family = "wasm")]
Expand Down Expand Up @@ -146,16 +149,3 @@ pub unsafe fn load_interface(
) -> sys::GDExtensionInterface {
sys::GDExtensionInterface::load(get_proc_address)
}

fn read_version_string(version_ptr: &sys::GDExtensionGodotVersion) -> String {
let char_ptr = version_ptr.string;

// SAFETY: `version_ptr` points to a layout-compatible version struct.
let c_str = unsafe { std::ffi::CStr::from_ptr(char_ptr) };

String::from_utf8_lossy(c_str.to_bytes())
.as_ref()
.strip_prefix("Godot Engine ")
.unwrap_or(&String::from_utf8_lossy(c_str.to_bytes()))
.to_string()
}
10 changes: 9 additions & 1 deletion godot-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,15 @@ mod toolbox;

#[doc(hidden)]
#[cfg(target_family = "wasm")]
pub use gensym::gensym;
pub use godot_macros::wasm_declare_init_fn;

// No-op otherwise.
#[doc(hidden)]
#[cfg(not(target_family = "wasm"))]
#[macro_export]
macro_rules! wasm_declare_init_fn {
() => {};
}

pub use crate::godot_ffi::{GodotFfi, GodotNullableFfi, PrimitiveConversionError, PtrcallType};

Expand Down
22 changes: 1 addition & 21 deletions godot-ffi/src/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,6 @@ macro_rules! plugin_registry {
};
}

#[doc(hidden)]
#[macro_export]
// Following rustfmt::skip is no longer needed, but there are 2 workarounds good to know, thus preserved.
// #[allow(clippy::deprecated_cfg_attr)]
// #[cfg_attr(rustfmt, rustfmt::skip)]
// cfg_attr: workaround for https://github.com/rust-lang/rust/pull/52234#issuecomment-976702997
macro_rules! plugin_execute_pre_main_wasm {
($gensym:ident,) => {
// Rust presently requires that statics with a custom `#[link_section]` must be a simple
// list of bytes on the Wasm target (with no extra levels of indirection such as references).
//
// As such, instead we export a function with a random name of predictable format to be used by the embedder.
#[no_mangle]
extern "C" fn $gensym() {
__init();
}
};
}

/// Executes a block of code before main, by utilising platform specific linker instructions.
#[doc(hidden)]
#[macro_export]
Expand Down Expand Up @@ -73,8 +54,7 @@ macro_rules! plugin_execute_pre_main {
__inner_init
};

#[cfg(target_family = "wasm")]
$crate::gensym! { $crate::plugin_execute_pre_main_wasm!() }
$crate::wasm_declare_init_fn!();
};
};
}
Expand Down
8 changes: 6 additions & 2 deletions godot-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ homepage = "https://godot-rust.github.io"

[features]
api-custom = ["godot-bindings/api-custom"]
register-docs = ["dep:markdown", "dep:litrs"]
codegen-full = ["godot/__codegen-full"]
experimental-wasm = ["dep:libc"]
register-docs = ["dep:markdown", "dep:litrs"]

[lib]
proc-macro = true
Expand All @@ -25,9 +26,12 @@ quote = "1.0.29"
# Enabled by `docs`.
markdown = { version = "=1.0.0-alpha.21", optional = true }
litrs = { version = "0.4.1", optional = true }
# Requires bugfixes from 0.6.1.
venial = "0.6.1"

# Cannot use [target.'cfg(target_family = "wasm")'.dependencies], as proc-macro crates are always compiled for host platform, not target.
# Thus solved via feature.
libc = { version = "0.2.171", optional = true }

[build-dependencies]
godot-bindings = { path = "../godot-bindings", version = "=0.2.4" } # emit_godot_version_cfg

Expand Down
42 changes: 42 additions & 0 deletions godot-macros/src/ffi_macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

//! Macro implementations used by `godot-ffi` crate.

#![cfg(feature = "experimental-wasm")]

use crate::util::bail;
use crate::ParseResult;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

pub(super) fn wasm_declare_init_fn(input: TokenStream) -> ParseResult<TokenStream> {
if !input.is_empty() {
return bail!(input, "macro expects no arguments");
}

// Create sufficiently unique identifier without entire `uuid` (let alone `rand`) crate dependency.
let a = unsafe { libc::rand() };
let b = unsafe { libc::rand() };

// Rust presently requires that statics with a custom `#[link_section]` must be a simple
// list of bytes on the Wasm target (with no extra levels of indirection such as references).
//
// As such, instead we export a function with a random name of known prefix to be used by the embedder.
// This prefix is queried at load time, see godot-macros/src/gdextension.rs.
let function_name = format_ident!("__godot_rust_registrant_{a}_{b}");

let code = quote! {
#[cfg(target_family = "wasm")] // Strictly speaking not necessary, as this macro is only invoked for Wasm.
#[no_mangle]
extern "C" fn #function_name() {
__init();
}
};

Ok(code)
}
2 changes: 1 addition & 1 deletion godot-macros/src/gdextension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub fn attribute_gdextension(item: venial::Item) -> ParseResult<TokenStream> {
console.log(`Patching Module with ${sym}`);
Module[sym] = dso_exports[sym];
}
} else if (sym.startsWith("rust_gdext_registrant_")) {
} else if (sym.startsWith("__godot_rust_registrant_")) {
registrants.push(sym);
}
}
Expand Down
29 changes: 26 additions & 3 deletions godot-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod class;
mod derive;
#[cfg(all(feature = "register-docs", since_api = "4.3"))]
mod docs;
mod ffi_macros;
mod gdextension;
mod itest;
mod util;
Expand Down Expand Up @@ -774,9 +775,6 @@ pub fn derive_godot_class(input: TokenStream) -> TokenStream {
/// method, you can access all declared signals in `self.signals().some_signal()` or `gd.signals().some_signal()`. The returned object is
/// of type [`TypedSignal`], which provides further APIs for emitting and connecting, among others.
///
/// Visibility of signals **must not exceed class visibility**. If your class is private (as above) and you declare your signal as `pub fn`,
/// you will get a compile error "can't leak private type".
///
/// A detailed explanation with examples is available in the [book chapter _Registering signals_](https://godot-rust.github.io/book/register/signals.html).
///
/// [`WithSignals`]: ../obj/trait.WithSignals.html
Expand Down Expand Up @@ -1051,9 +1049,21 @@ pub fn gdextension(meta: TokenStream, input: TokenStream) -> TokenStream {
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Used by godot-ffi

/// Creates an initialization block for Wasm.
#[proc_macro]
#[cfg(feature = "experimental-wasm")]
pub fn wasm_declare_init_fn(input: TokenStream) -> TokenStream {
translate_functional(input, ffi_macros::wasm_declare_init_fn)
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Implementation

type ParseResult<T> = Result<T, venial::Error>;

/// For `#[derive(...)]` derive macros.
fn translate<F>(input: TokenStream, transform: F) -> TokenStream
where
F: FnOnce(venial::Item) -> ParseResult<TokenStream2>,
Expand All @@ -1067,6 +1077,7 @@ where
TokenStream::from(result2)
}

/// For `#[proc_macro_attribute]` procedural macros.
fn translate_meta<F>(
self_name: &str,
meta: TokenStream,
Expand All @@ -1087,6 +1098,18 @@ where
TokenStream::from(result2)
}

/// For `#[proc_macro]` function-style macros.
#[cfg(feature = "experimental-wasm")]
fn translate_functional<F>(input: TokenStream, transform: F) -> TokenStream
where
F: FnOnce(TokenStream2) -> ParseResult<TokenStream2>,
{
let input2 = TokenStream2::from(input);
let result2 = transform(input2).unwrap_or_else(|e| e.to_compile_error());

TokenStream::from(result2)
}

/// Returns the index of the key in `keys` (if any) that is present.
fn handle_mutually_exclusive_keys(
parser: &mut KvParser,
Expand Down
8 changes: 6 additions & 2 deletions godot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,18 @@
//! Support for WebAssembly exports is still a work-in-progress and is not yet well tested. This feature is in place for users
//! to explicitly opt in to any instabilities or rough edges that may result.
//!
//! By default, Wasm threads are enabled and require the flag `"-C", "link-args=-sUSE_PTHREADS=1"` in the `wasm32-unknown-unknown` target.
//! Please read [Export to Web](https://godot-rust.github.io/book/toolchain/export-web.html) in the book.
//!
//! By default, Wasm threads are enabled and require the flag `"-C", "link-args=-pthread"` in the `wasm32-unknown-unknown` target.
//! This must be kept in sync with Godot's Web export settings (threading support enabled). To disable it, use **additionally* the feature
//! `experimental-wasm-nothreads`.<br><br>
//!
//! It is recommended to use this feature in combination with `lazy-function-tables` to reduce the size of the generated Wasm binary.
//!
//! * **`experimental-wasm-nothreads`**
//!
//! Requires the `experimental-wasm` feature. Disables threading support for WebAssembly exports. This needs to be kept in sync with
//! Godot's Web export setting (threading support disabled), and must _not_ use the `"-C", "link-args=-sUSE_PTHREADS=1"` flag in the
//! Godot's Web export setting (threading support disabled), and must _not_ use the `"-C", "link-args=-pthread"` flag in the
//! `wasm32-unknown-unknown` target.<br><br>
//!
//! * **`codegen-rustfmt`**
Expand Down
Loading