Skip to content

Commit 6ba99ba

Browse files
committed
[macros] allow multiple inherent impl blocks.
All but one blocks must have the key 'secondary'.
1 parent 8a62088 commit 6ba99ba

File tree

7 files changed

+269
-45
lines changed

7 files changed

+269
-45
lines changed

godot-ffi/src/plugins.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ macro_rules! plugin_registry {
3333
#[cfg_attr(rustfmt, rustfmt::skip)]
3434
// ^ skip: paste's [< >] syntax chokes fmt
3535
// cfg_attr: workaround for https://github.com/rust-lang/rust/pull/52234#issuecomment-976702997
36-
macro_rules! plugin_add_inner_wasm {
36+
macro_rules! plugin_execute_pre_main_wasm {
3737
($gensym:ident,) => {
3838
// Rust presently requires that statics with a custom `#[link_section]` must be a simple
3939
// list of bytes on the wasm target (with no extra levels of indirection such as references).
@@ -49,14 +49,15 @@ macro_rules! plugin_add_inner_wasm {
4949
};
5050
}
5151

52+
/// Executes a block of code before main, by utilising platform specific linker instructions.
5253
#[doc(hidden)]
5354
#[macro_export]
5455
#[allow(clippy::deprecated_cfg_attr)]
5556
#[cfg_attr(rustfmt, rustfmt::skip)]
5657
// ^ skip: paste's [< >] syntax chokes fmt
5758
// cfg_attr: workaround for https://github.com/rust-lang/rust/pull/52234#issuecomment-976702997
58-
macro_rules! plugin_add_inner {
59-
($registry:ident; $plugin:expr; $( $path_tt:tt )* ) => {
59+
macro_rules! plugin_execute_pre_main {
60+
($body:expr) => {
6061
const _: () = {
6162
#[allow(non_upper_case_globals)]
6263
#[used]
@@ -76,20 +77,35 @@ macro_rules! plugin_add_inner {
7677
#[cfg_attr(target_os = "android", link_section = ".text.startup")]
7778
#[cfg_attr(target_os = "linux", link_section = ".text.startup")]
7879
extern "C" fn __inner_init() {
79-
let mut guard = $crate::paste::paste!( $( $path_tt )* [< __godot_rust_plugin_ $registry >] )
80-
.lock()
81-
.unwrap();
82-
guard.push($plugin);
80+
$body
8381
}
8482
__inner_init
8583
};
8684

8785
#[cfg(target_family = "wasm")]
88-
$crate::gensym! { $crate::plugin_add_inner_wasm!() }
86+
$crate::gensym! { $crate::plugin_execute_pre_main_wasm!() }
8987
};
9088
};
9189
}
9290

91+
/// register a plugin by executing code pre-main that adds the plugin to the plugin registry
92+
#[doc(hidden)]
93+
#[macro_export]
94+
#[allow(clippy::deprecated_cfg_attr)]
95+
#[cfg_attr(rustfmt, rustfmt::skip)]
96+
// ^ skip: paste's [< >] syntax chokes fmt
97+
// cfg_attr: workaround for https://github.com/rust-lang/rust/pull/52234#issuecomment-976702997
98+
macro_rules! plugin_add_inner {
99+
($registry:ident; $plugin:expr; $( $path_tt:tt )* ) => {
100+
$crate::plugin_execute_pre_main!({
101+
let mut guard = $crate::paste::paste!( $( $path_tt )* [< __godot_rust_plugin_ $registry >] )
102+
.lock()
103+
.unwrap();
104+
guard.push($plugin);
105+
});
106+
};
107+
}
108+
93109
/// Register a plugin to a registry
94110
#[doc(hidden)]
95111
#[macro_export]

godot-macros/src/class/data_models/inherent_impl.rs

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,17 @@ struct FuncAttr {
6464

6565
// ----------------------------------------------------------------------------------------------------------------------------------------------
6666

67+
pub struct InherentImplAttr {
68+
/// For implementation reasons, there can be a single 'primary' impl block and 0 or more 'secondary' impl blocks.
69+
/// For now this is controlled by a key in the the 'godot_api' attribute
70+
pub secondary: bool,
71+
}
72+
6773
/// Codegen for `#[godot_api] impl MyType`
68-
pub fn transform_inherent_impl(mut impl_block: venial::Impl) -> ParseResult<TokenStream> {
74+
pub fn transform_inherent_impl(
75+
meta: InherentImplAttr,
76+
mut impl_block: venial::Impl,
77+
) -> ParseResult<TokenStream> {
6978
let class_name = util::validate_impl(&impl_block, None, "godot_api")?;
7079
let class_name_obj = util::class_name_obj(&class_name);
7180
let prv = quote! { ::godot::private };
@@ -93,38 +102,96 @@ pub fn transform_inherent_impl(mut impl_block: venial::Impl) -> ParseResult<Toke
93102

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

96-
let result = quote! {
97-
#impl_block
105+
let method_storage_name = format_ident!("__registration_methods_{class_name}");
106+
let constants_storage_name = format_ident!("__registration_constants_{class_name}");
107+
108+
let fill_storage = quote! {
109+
::godot::sys::plugin_execute_pre_main!({
110+
#method_storage_name.lock().unwrap().push(||{
98111

99-
impl ::godot::obj::cap::ImplementsGodotApi for #class_name {
100-
fn __register_methods() {
101112
#( #method_registrations )*
102113
#( #signal_registrations )*
103-
}
104114

105-
fn __register_constants() {
106-
#constant_registration
107-
}
115+
});
116+
#constants_storage_name.lock().unwrap().push(||{
108117

109-
#rpc_registrations
110-
}
118+
#constant_registration
111119

112-
::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
113-
class_name: #class_name_obj,
114-
item: #prv::PluginItem::InherentImpl(#prv::InherentImpl {
115-
register_methods_constants_fn: #prv::ErasedRegisterFn {
116-
raw: #prv::callbacks::register_user_methods_constants::<#class_name>,
117-
},
118-
register_rpcs_fn: Some(#prv::ErasedRegisterRpcsFn {
119-
raw: #prv::callbacks::register_user_rpcs::<#class_name>,
120-
}),
121-
#docs
122-
}),
123-
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
120+
});
124121
});
125122
};
126123

127-
Ok(result)
124+
if !meta.secondary {
125+
// We are the primary `impl` block.
126+
127+
let storage = quote! {
128+
#[allow(non_upper_case_globals)]
129+
#[doc(hidden)]
130+
static #method_storage_name: std::sync::Mutex<Vec<fn()>> = std::sync::Mutex::new(Vec::new());
131+
132+
#[allow(non_upper_case_globals)]
133+
#[doc(hidden)]
134+
static #constants_storage_name: std::sync::Mutex<Vec<fn()>> = std::sync::Mutex::new(Vec::new());
135+
};
136+
137+
let trait_impl = quote! {
138+
impl ::godot::obj::cap::ImplementsGodotApi for #class_name {
139+
fn __register_methods() {
140+
let guard = #method_storage_name.lock().unwrap();
141+
for f in guard.iter() {
142+
f();
143+
}
144+
}
145+
146+
fn __register_constants() {
147+
let guard = #constants_storage_name.lock().unwrap();
148+
for f in guard.iter() {
149+
f();
150+
}
151+
}
152+
153+
#rpc_registrations
154+
}
155+
};
156+
157+
let class_registration = quote! {
158+
159+
::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
160+
class_name: #class_name_obj,
161+
item: #prv::PluginItem::InherentImpl(#prv::InherentImpl {
162+
register_methods_constants_fn: #prv::ErasedRegisterFn {
163+
raw: #prv::callbacks::register_user_methods_constants::<#class_name>,
164+
},
165+
register_rpcs_fn: Some(#prv::ErasedRegisterRpcsFn {
166+
raw: #prv::callbacks::register_user_rpcs::<#class_name>,
167+
}),
168+
#docs
169+
}),
170+
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
171+
});
172+
173+
};
174+
175+
let result = quote! {
176+
#impl_block
177+
#storage
178+
#trait_impl
179+
#fill_storage
180+
#class_registration
181+
};
182+
183+
Ok(result)
184+
} else {
185+
// We are in a secondary `impl` block, so most of the work has already been done
186+
// and we just need to add our registration functions in the storage defined by the primary `impl` block.
187+
188+
let result = quote! {
189+
#impl_block
190+
#fill_storage
191+
};
192+
193+
Ok(result)
194+
}
128195
}
129196

130197
fn process_godot_fns(

godot-macros/src/class/godot_api.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,24 @@
88
use proc_macro2::TokenStream;
99

1010
use crate::class::{transform_inherent_impl, transform_trait_impl};
11-
use crate::util::bail;
11+
use crate::util::{bail, venial_parse_meta, KvParser};
1212
use crate::ParseResult;
1313

14-
pub fn attribute_godot_api(input_decl: venial::Item) -> ParseResult<TokenStream> {
14+
use quote::{format_ident, quote};
15+
16+
fn parse_inherent_impl_attr(meta: TokenStream) -> Result<super::InherentImplAttr, venial::Error> {
17+
let item = venial_parse_meta(&meta, format_ident!("godot_api"), &quote! { fn func(); })?;
18+
let mut attr = KvParser::parse_required(item.attributes(), "godot_api", &meta)?;
19+
let secondary = attr.handle_alone("secondary")?;
20+
attr.finish()?;
21+
22+
Ok(super::InherentImplAttr { secondary })
23+
}
24+
25+
pub fn attribute_godot_api(
26+
meta: TokenStream,
27+
input_decl: venial::Item,
28+
) -> ParseResult<TokenStream> {
1529
let decl = match input_decl {
1630
venial::Item::Impl(decl) => decl,
1731
_ => bail!(
@@ -32,8 +46,19 @@ pub fn attribute_godot_api(input_decl: venial::Item) -> ParseResult<TokenStream>
3246
};
3347

3448
if decl.trait_ty.is_some() {
49+
// 'meta' contains the parameters to the macro, that is, for `#[godot_api(a, b, x=y)]`, anything inside the braces.
50+
// We currently don't accept any parameters for a trait `impl`, so show an error to the user if they added something there.
51+
if meta.to_string() != "" {
52+
return bail!(
53+
meta,
54+
"#[godot_api] on a trait implementation currently does not support any parameters"
55+
);
56+
}
3557
transform_trait_impl(decl)
3658
} else {
37-
transform_inherent_impl(decl)
59+
match parse_inherent_impl_attr(meta) {
60+
Ok(meta) => transform_inherent_impl(meta, decl),
61+
Err(err) => Err(err),
62+
}
3863
}
3964
}

godot-macros/src/lib.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ mod util;
2121

2222
use proc_macro::TokenStream;
2323
use proc_macro2::TokenStream as TokenStream2;
24-
use quote::quote;
2524

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

@@ -520,6 +519,7 @@ pub fn derive_godot_class(input: TokenStream) -> TokenStream {
520519
/// - [Virtual methods](#virtual-methods)
521520
/// - [RPC attributes](#rpc-attributes)
522521
/// - [Constants and signals](#signals)
522+
/// - [Multiple inherent `impl` blocks](#multiple-inherent-impl-blocks)
523523
///
524524
/// # Constructors
525525
///
@@ -749,9 +749,35 @@ pub fn derive_godot_class(input: TokenStream) -> TokenStream {
749749
/// # Constants and signals
750750
///
751751
/// Please refer to [the book](https://godot-rust.github.io/book/register/constants.html).
752+
///
753+
/// # Multiple inherent `impl` blocks
754+
///
755+
/// 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.
756+
/// 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.
757+
/// ```no_run
758+
/// # use godot::prelude::*;
759+
/// # #[derive(GodotClass)]
760+
/// # #[class(init)]
761+
/// # struct MyStruct {
762+
/// # base: Base<RefCounted>,
763+
/// # }
764+
/// #[godot_api]
765+
/// impl MyStruct {
766+
/// #[func]
767+
/// pub fn one(&self) { }
768+
/// }
769+
///
770+
/// #[godot_api(secondary)]
771+
/// impl MyStruct {
772+
/// #[func]
773+
/// pub fn two(&self) { }
774+
/// }
775+
/// ```
752776
#[proc_macro_attribute]
753-
pub fn godot_api(_meta: TokenStream, input: TokenStream) -> TokenStream {
754-
translate(input, class::attribute_godot_api)
777+
pub fn godot_api(meta: TokenStream, input: TokenStream) -> TokenStream {
778+
translate(input, |body| {
779+
class::attribute_godot_api(TokenStream2::from(meta), body)
780+
})
755781
}
756782

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

964-
// Hack because venial doesn't support direct meta parsing yet
965-
let input = quote! {
966-
#[#self_name(#meta2)]
967-
#input2
968-
};
969-
970-
let result2 = venial::parse_item(input)
990+
let result2 = util::venial_parse_meta(&meta2, self_name, &input2)
971991
.and_then(transform)
972992
.unwrap_or_else(|e| e.to_compile_error());
973993

godot-macros/src/util/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,21 @@ pub fn safe_ident(s: &str) -> Ident {
288288
_ => ident(s)
289289
}
290290
}
291+
292+
// ----------------------------------------------------------------------------------------------------------------------------------------------
293+
294+
/// Parses a `meta` TokenStream, that is, the tokens in parameter position of a proc-macro (between the braces).
295+
/// 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*.
296+
pub fn venial_parse_meta(
297+
meta: &TokenStream,
298+
self_name: Ident,
299+
content: &TokenStream,
300+
) -> Result<venial::Item, venial::Error> {
301+
// Hack because venial doesn't support direct meta parsing yet
302+
let input = quote! {
303+
#[#self_name(#meta)]
304+
#content
305+
};
306+
307+
venial::parse_item(input)
308+
}

itest/rust/src/register_tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod conversion_test;
1010
mod derive_godotconvert_test;
1111
mod func_test;
1212
mod gdscript_ffi_test;
13+
mod multiple_impl_blocks_test;
1314
mod naming_tests;
1415
mod option_ffi_test;
1516
mod register_docs_test;

0 commit comments

Comments
 (0)