Skip to content

Commit

Permalink
[macros] allow multiple inherent impl blocks.
Browse files Browse the repository at this point in the history
All but one blocks must have the key 'secondary'.
  • Loading branch information
0x53A committed Nov 28, 2024
1 parent 8a62088 commit 8fb7059
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 44 deletions.
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"
);
}
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),
}
}
}
39 changes: 30 additions & 9 deletions godot-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,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 +750,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 +988,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

0 comments on commit 8fb7059

Please sign in to comment.