Skip to content
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

Multiple #[godot_api] impl blocks #927

Merged
merged 1 commit into from
Nov 28, 2024
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
32 changes: 24 additions & 8 deletions godot-ffi/src/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ macro_rules! plugin_registry {
#[cfg_attr(rustfmt, rustfmt::skip)]
// ^ skip: paste's [< >] syntax chokes fmt
// cfg_attr: workaround for https://github.com/rust-lang/rust/pull/52234#issuecomment-976702997
macro_rules! plugin_add_inner_wasm {
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).
Expand All @@ -49,14 +49,15 @@ macro_rules! plugin_add_inner_wasm {
};
}

/// Executes a block of code before main, by utilising platform specific linker instructions.
#[doc(hidden)]
#[macro_export]
#[allow(clippy::deprecated_cfg_attr)]
#[cfg_attr(rustfmt, rustfmt::skip)]
// ^ skip: paste's [< >] syntax chokes fmt
// cfg_attr: workaround for https://github.com/rust-lang/rust/pull/52234#issuecomment-976702997
macro_rules! plugin_add_inner {
($registry:ident; $plugin:expr; $( $path_tt:tt )* ) => {
macro_rules! plugin_execute_pre_main {
($body:expr) => {
const _: () = {
#[allow(non_upper_case_globals)]
#[used]
Expand All @@ -76,20 +77,35 @@ macro_rules! plugin_add_inner {
#[cfg_attr(target_os = "android", link_section = ".text.startup")]
#[cfg_attr(target_os = "linux", link_section = ".text.startup")]
extern "C" fn __inner_init() {
let mut guard = $crate::paste::paste!( $( $path_tt )* [< __godot_rust_plugin_ $registry >] )
.lock()
.unwrap();
guard.push($plugin);
$body
}
__inner_init
};

#[cfg(target_family = "wasm")]
$crate::gensym! { $crate::plugin_add_inner_wasm!() }
$crate::gensym! { $crate::plugin_execute_pre_main_wasm!() }
};
};
}

/// register a plugin by executing code pre-main that adds the plugin to the plugin registry
#[doc(hidden)]
#[macro_export]
#[allow(clippy::deprecated_cfg_attr)]
#[cfg_attr(rustfmt, rustfmt::skip)]
// ^ skip: paste's [< >] syntax chokes fmt
// cfg_attr: workaround for https://github.com/rust-lang/rust/pull/52234#issuecomment-976702997
macro_rules! plugin_add_inner {
($registry:ident; $plugin:expr; $( $path_tt:tt )* ) => {
$crate::plugin_execute_pre_main!({
let mut guard = $crate::paste::paste!( $( $path_tt )* [< __godot_rust_plugin_ $registry >] )
.lock()
.unwrap();
guard.push($plugin);
});
};
}

/// Register a plugin to a registry
#[doc(hidden)]
#[macro_export]
Expand Down
115 changes: 91 additions & 24 deletions godot-macros/src/class/data_models/inherent_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,17 @@ struct FuncAttr {

// ----------------------------------------------------------------------------------------------------------------------------------------------

pub struct InherentImplAttr {
/// For implementation reasons, there can be a single 'primary' impl block and 0 or more 'secondary' impl blocks.
/// For now this is controlled by a key in the the 'godot_api' attribute
pub secondary: bool,
}

/// Codegen for `#[godot_api] impl MyType`
pub fn transform_inherent_impl(mut impl_block: venial::Impl) -> ParseResult<TokenStream> {
pub fn transform_inherent_impl(
meta: InherentImplAttr,
mut impl_block: venial::Impl,
) -> ParseResult<TokenStream> {
let class_name = util::validate_impl(&impl_block, None, "godot_api")?;
let class_name_obj = util::class_name_obj(&class_name);
let prv = quote! { ::godot::private };
Expand Down Expand Up @@ -93,38 +102,96 @@ pub fn transform_inherent_impl(mut impl_block: venial::Impl) -> ParseResult<Toke

let constant_registration = make_constant_registration(consts, &class_name, &class_name_obj)?;

let result = quote! {
#impl_block
let method_storage_name = format_ident!("__registration_methods_{class_name}");
let constants_storage_name = format_ident!("__registration_constants_{class_name}");

let fill_storage = quote! {
::godot::sys::plugin_execute_pre_main!({
#method_storage_name.lock().unwrap().push(||{

impl ::godot::obj::cap::ImplementsGodotApi for #class_name {
fn __register_methods() {
#( #method_registrations )*
#( #signal_registrations )*
}

fn __register_constants() {
#constant_registration
}
});
#constants_storage_name.lock().unwrap().push(||{

#rpc_registrations
}
#constant_registration

::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
class_name: #class_name_obj,
item: #prv::PluginItem::InherentImpl(#prv::InherentImpl {
register_methods_constants_fn: #prv::ErasedRegisterFn {
raw: #prv::callbacks::register_user_methods_constants::<#class_name>,
},
register_rpcs_fn: Some(#prv::ErasedRegisterRpcsFn {
raw: #prv::callbacks::register_user_rpcs::<#class_name>,
}),
#docs
}),
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
});
});
};

Ok(result)
if !meta.secondary {
// We are the primary `impl` block.

let storage = quote! {
#[allow(non_upper_case_globals)]
#[doc(hidden)]
static #method_storage_name: std::sync::Mutex<Vec<fn()>> = std::sync::Mutex::new(Vec::new());

#[allow(non_upper_case_globals)]
#[doc(hidden)]
static #constants_storage_name: std::sync::Mutex<Vec<fn()>> = std::sync::Mutex::new(Vec::new());
};

let trait_impl = quote! {
impl ::godot::obj::cap::ImplementsGodotApi for #class_name {
fn __register_methods() {
let guard = #method_storage_name.lock().unwrap();
for f in guard.iter() {
f();
}
}

fn __register_constants() {
let guard = #constants_storage_name.lock().unwrap();
for f in guard.iter() {
f();
}
}

#rpc_registrations
}
};

let class_registration = quote! {

::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
class_name: #class_name_obj,
item: #prv::PluginItem::InherentImpl(#prv::InherentImpl {
register_methods_constants_fn: #prv::ErasedRegisterFn {
raw: #prv::callbacks::register_user_methods_constants::<#class_name>,
},
register_rpcs_fn: Some(#prv::ErasedRegisterRpcsFn {
raw: #prv::callbacks::register_user_rpcs::<#class_name>,
}),
#docs
}),
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
});

};

let result = quote! {
#impl_block
#storage
#trait_impl
#fill_storage
#class_registration
};

Ok(result)
} else {
// We are in a secondary `impl` block, so most of the work has already been done
// and we just need to add our registration functions in the storage defined by the primary `impl` block.

let result = quote! {
#impl_block
#fill_storage
};

Ok(result)
}
}

fn process_godot_fns(
Expand Down
31 changes: 28 additions & 3 deletions godot-macros/src/class/godot_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,24 @@
use proc_macro2::TokenStream;

use crate::class::{transform_inherent_impl, transform_trait_impl};
use crate::util::bail;
use crate::util::{bail, venial_parse_meta, KvParser};
use crate::ParseResult;

pub fn attribute_godot_api(input_decl: venial::Item) -> ParseResult<TokenStream> {
use quote::{format_ident, quote};

fn parse_inherent_impl_attr(meta: TokenStream) -> Result<super::InherentImplAttr, venial::Error> {
let item = venial_parse_meta(&meta, format_ident!("godot_api"), &quote! { fn func(); })?;
let mut attr = KvParser::parse_required(item.attributes(), "godot_api", &meta)?;
let secondary = attr.handle_alone("secondary")?;
attr.finish()?;

Ok(super::InherentImplAttr { secondary })
}

pub fn attribute_godot_api(
meta: TokenStream,
input_decl: venial::Item,
) -> ParseResult<TokenStream> {
let decl = match input_decl {
venial::Item::Impl(decl) => decl,
_ => bail!(
Expand All @@ -32,8 +46,19 @@ pub fn attribute_godot_api(input_decl: venial::Item) -> ParseResult<TokenStream>
};

if decl.trait_ty.is_some() {
// 'meta' contains the parameters to the macro, that is, for `#[godot_api(a, b, x=y)]`, anything inside the braces.
// We currently don't accept any parameters for a trait `impl`, so show an error to the user if they added something there.
if meta.to_string() != "" {
return bail!(
meta,
"#[godot_api] on a trait implementation currently does not support any parameters"
);
}
0x53A marked this conversation as resolved.
Show resolved Hide resolved
transform_trait_impl(decl)
} else {
transform_inherent_impl(decl)
match parse_inherent_impl_attr(meta) {
Ok(meta) => transform_inherent_impl(meta, decl),
Err(err) => Err(err),
}
}
}
40 changes: 30 additions & 10 deletions godot-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ mod util;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;

use crate::util::{bail, ident, KvParser};

Expand Down Expand Up @@ -520,6 +519,7 @@ pub fn derive_godot_class(input: TokenStream) -> TokenStream {
/// - [Virtual methods](#virtual-methods)
/// - [RPC attributes](#rpc-attributes)
/// - [Constants and signals](#signals)
/// - [Multiple inherent `impl` blocks](#multiple-inherent-impl-blocks)
///
/// # Constructors
///
Expand Down Expand Up @@ -749,9 +749,35 @@ pub fn derive_godot_class(input: TokenStream) -> TokenStream {
/// # Constants and signals
///
/// Please refer to [the book](https://godot-rust.github.io/book/register/constants.html).
///
/// # Multiple inherent `impl` blocks
///
/// Just like with regular structs, you can have multiple inherent `impl` blocks. This can be useful for code organization or when you want to generate code from a proc-macro.
/// For implementation reasons, all but one `impl` blocks must have the key `secondary`. There is no difference between implementing all functions in one block or splitting them up between multiple blocks.
/// ```no_run
/// # use godot::prelude::*;
/// # #[derive(GodotClass)]
/// # #[class(init)]
/// # struct MyStruct {
/// # base: Base<RefCounted>,
/// # }
/// #[godot_api]
/// impl MyStruct {
/// #[func]
/// pub fn one(&self) { }
/// }
///
/// #[godot_api(secondary)]
/// impl MyStruct {
/// #[func]
/// pub fn two(&self) { }
/// }
/// ```
#[proc_macro_attribute]
pub fn godot_api(_meta: TokenStream, input: TokenStream) -> TokenStream {
translate(input, class::attribute_godot_api)
pub fn godot_api(meta: TokenStream, input: TokenStream) -> TokenStream {
translate(input, |body| {
class::attribute_godot_api(TokenStream2::from(meta), body)
})
}

/// Derive macro for [`GodotConvert`](../builtin/meta/trait.GodotConvert.html) on structs.
Expand Down Expand Up @@ -961,13 +987,7 @@ where
let input2 = TokenStream2::from(input);
let meta2 = TokenStream2::from(meta);

// Hack because venial doesn't support direct meta parsing yet
let input = quote! {
#[#self_name(#meta2)]
#input2
};

let result2 = venial::parse_item(input)
let result2 = util::venial_parse_meta(&meta2, self_name, &input2)
.and_then(transform)
.unwrap_or_else(|e| e.to_compile_error());

Expand Down
18 changes: 18 additions & 0 deletions godot-macros/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,21 @@ pub fn safe_ident(s: &str) -> Ident {
_ => ident(s)
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

/// Parses a `meta` TokenStream, that is, the tokens in parameter position of a proc-macro (between the braces).
/// Because venial can't actually parse a meta item directly, this is done by reconstructing the full macro attribute on top of some content and then parsing *that*.
pub fn venial_parse_meta(
meta: &TokenStream,
self_name: Ident,
content: &TokenStream,
) -> Result<venial::Item, venial::Error> {
// Hack because venial doesn't support direct meta parsing yet
let input = quote! {
#[#self_name(#meta)]
#content
};

venial::parse_item(input)
}
1 change: 1 addition & 0 deletions itest/rust/src/register_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod conversion_test;
mod derive_godotconvert_test;
mod func_test;
mod gdscript_ffi_test;
mod multiple_impl_blocks_test;
mod naming_tests;
mod option_ffi_test;
mod register_docs_test;
Expand Down
Loading
Loading