-
-
Notifications
You must be signed in to change notification settings - Fork 198
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
base: master
Are you sure you want to change the base?
Conversation
The idea is that the "primary" impl block defines a The "secondary" impl blocks then just push into these Here is the generated code, for completeness sake: use godot::prelude::*;
use godot::classes::Node;
use godot::classes::INode;
#[derive(GodotClass)]
#[class(base=Node, init)]
pub struct TestNode {
base: Base<Node>
}
// Recursive expansion of godot_api macro
// =======================================
impl TestNode {
fn foo() {
godot_print!("[TestNode] called 'foo'");
}
}
#[used]
#[allow(non_upper_case_globals)]
#[doc(hidden)]
static __registration_methods_TestNode: std::sync::Mutex<Vec<fn()>> =
std::sync::Mutex::new(Vec::new());
#[used]
#[allow(non_upper_case_globals)]
#[doc(hidden)]
static __registration_constants_TestNode: std::sync::Mutex<Vec<fn()>> =
std::sync::Mutex::new(Vec::new());
impl ::godot::obj::cap::ImplementsGodotApi for TestNode {
fn __register_methods() {
let guard = __registration_methods_TestNode.lock().unwrap();
for f in guard.iter() {
f();
}
}
fn __register_constants() {
let guard = __registration_constants_TestNode.lock().unwrap();
for f in guard.iter() {
f();
}
}
}
const _: () = {
#[allow(non_upper_case_globals)]
#[used]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
#[cfg_attr(target_os = "ios", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "android", link_section = ".init_array")]
#[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
#[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
#[cfg_attr(target_os = "linux", link_section = ".init_array")]
#[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
#[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
static __init: extern "C" fn() = {
#[cfg_attr(target_os = "android", link_section = ".text.startup")]
#[cfg_attr(target_os = "linux", link_section = ".text.startup")]
extern "C" fn __inner_init() {
{
__registration_methods_TestNode.lock().unwrap().push(|| {
{
use ::godot::builtin::{StringName, Variant};
use ::godot::obj::GodotClass;
use ::godot::register::private::method::ClassMethodInfo;
use ::godot::sys;
type Sig = ((),);
let method_name = StringName::from("foo");
unsafe extern "C" fn varcall_fn(
_method_data: *mut std::ffi::c_void,
instance_ptr: sys::GDExtensionClassInstancePtr,
args_ptr: *const sys::GDExtensionConstVariantPtr,
arg_count: sys::GDExtensionInt,
ret: sys::GDExtensionVariantPtr,
err: *mut sys::GDExtensionCallError,
) {
let call_ctx = ::godot::meta::CallContext::func("TestNode", "foo");
::godot::private::handle_varcall_panic(&call_ctx, &mut *err, || {
<Sig as ::godot::meta::VarcallSignatureTuple>::in_varcall(
instance_ptr,
&call_ctx,
args_ptr,
arg_count,
ret,
err,
|_, params| {
let () = params;
TestNode::foo()
},
)
});
};
unsafe extern "C" fn ptrcall_fn(
_method_data: *mut std::ffi::c_void,
instance_ptr: sys::GDExtensionClassInstancePtr,
args_ptr: *const sys::GDExtensionConstTypePtr,
ret: sys::GDExtensionTypePtr,
) {
let call_ctx = ::godot::meta::CallContext::func("TestNode", "foo");
let _success = ::godot::private::handle_panic(
|| &call_ctx,
|| {
<Sig as ::godot::meta::PtrcallSignatureTuple>::in_ptrcall(
instance_ptr,
&call_ctx,
args_ptr,
ret,
|_, params| {
let () = params;
TestNode::foo()
},
sys::PtrcallType::Standard,
)
},
);
};
let method_info = unsafe {
ClassMethodInfo::from_signature::<TestNode, Sig>(
method_name,
Some(varcall_fn),
Some(ptrcall_fn),
::godot::global::MethodFlags::NORMAL
| ::godot::global::MethodFlags::STATIC,
&[],
)
};
{
if false {
format_args!(" Register fn: {}::{}", "TestNode", "foo");
}
};
method_info.register_extension_class_method();
};
});
__registration_constants_TestNode
.lock()
.unwrap()
.push(|| {});
}
}
__inner_init
};
#[cfg(target_family = "wasm")]
{
core::panicking::panic_fmt(core::const_format_args!(
"not yet implemented: {}",
core::format_args!("wasm not yet implemented")
));
}
};
const _: () = {
#[allow(non_upper_case_globals)]
#[used]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
#[cfg_attr(target_os = "ios", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "android", link_section = ".init_array")]
#[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
#[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
#[cfg_attr(target_os = "linux", link_section = ".init_array")]
#[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
#[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
static __init: extern "C" fn() = {
#[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 = ::godot::private::__godot_rust_plugin___GODOT_PLUGIN_REGISTRY
.lock()
.unwrap();
guard.push(
(::godot::private::ClassPlugin {
class_name: <TestNode as ::godot::obj::GodotClass>::class_name(),
item: ::godot::private::PluginItem::InherentImpl(
::godot::private::InherentImpl {
register_methods_constants_fn: ::godot::private::ErasedRegisterFn {
raw: ::godot::private::callbacks::register_user_methods_constants::<
TestNode,
>,
},
register_rpcs_fn: Some(::godot::private::ErasedRegisterRpcsFn {
raw: ::godot::private::callbacks::register_user_rpcs::<TestNode>,
}),
},
),
init_level: <TestNode as ::godot::obj::GodotClass>::INIT_LEVEL,
}),
);
}
__inner_init
};
#[cfg(target_family = "wasm")]
godot_ffi::gensym! {
godot_ffi::plugin_add_inner_wasm!()
}
};
// Recursive expansion of godot_api macro
// =======================================
impl TestNode {
fn bar() {
godot_print!("[TestNode] called 'bar'");
}
}
const _: () = {
#[allow(non_upper_case_globals)]
#[used]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
#[cfg_attr(target_os = "ios", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "android", link_section = ".init_array")]
#[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
#[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
#[cfg_attr(target_os = "linux", link_section = ".init_array")]
#[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
#[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
static __init: extern "C" fn() = {
#[cfg_attr(target_os = "android", link_section = ".text.startup")]
#[cfg_attr(target_os = "linux", link_section = ".text.startup")]
extern "C" fn __inner_init() {
{
__registration_methods_TestNode.lock().unwrap().push(|| {
{
use ::godot::builtin::{StringName, Variant};
use ::godot::obj::GodotClass;
use ::godot::register::private::method::ClassMethodInfo;
use ::godot::sys;
type Sig = ((),);
let method_name = StringName::from("bar");
unsafe extern "C" fn varcall_fn(
_method_data: *mut std::ffi::c_void,
instance_ptr: sys::GDExtensionClassInstancePtr,
args_ptr: *const sys::GDExtensionConstVariantPtr,
arg_count: sys::GDExtensionInt,
ret: sys::GDExtensionVariantPtr,
err: *mut sys::GDExtensionCallError,
) {
let call_ctx = ::godot::meta::CallContext::func("TestNode", "bar");
::godot::private::handle_varcall_panic(&call_ctx, &mut *err, || {
<Sig as ::godot::meta::VarcallSignatureTuple>::in_varcall(
instance_ptr,
&call_ctx,
args_ptr,
arg_count,
ret,
err,
|_, params| {
let () = params;
TestNode::bar()
},
)
});
};
unsafe extern "C" fn ptrcall_fn(
_method_data: *mut std::ffi::c_void,
instance_ptr: sys::GDExtensionClassInstancePtr,
args_ptr: *const sys::GDExtensionConstTypePtr,
ret: sys::GDExtensionTypePtr,
) {
let call_ctx = ::godot::meta::CallContext::func("TestNode", "bar");
let _success = ::godot::private::handle_panic(
|| &call_ctx,
|| {
<Sig as ::godot::meta::PtrcallSignatureTuple>::in_ptrcall(
instance_ptr,
&call_ctx,
args_ptr,
ret,
|_, params| {
let () = params;
TestNode::bar()
},
sys::PtrcallType::Standard,
)
},
);
};
let method_info = unsafe {
ClassMethodInfo::from_signature::<TestNode, Sig>(
method_name,
Some(varcall_fn),
Some(ptrcall_fn),
::godot::global::MethodFlags::NORMAL
| ::godot::global::MethodFlags::STATIC,
&[],
)
};
{
if false {
format_args!(" Register fn: {}::{}", "TestNode", "bar");
}
};
method_info.register_extension_class_method();
};
});
__registration_constants_TestNode
.lock()
.unwrap()
.push(|| {});
}
}
__inner_init
};
#[cfg(target_family = "wasm")]
{
core::panicking::panic_fmt(core::const_format_args!(
"not yet implemented: {}",
core::format_args!("wasm not yet implemented")
));
}
};
|
It should be possible to get rid of the explicit |
One limitation currently is that you can have multiple impl blocks, but only in the same file. The |
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-927 |
Wow, impressive 😮 thanks a lot!
Let's avoid that, stateful macros are a hack that may cease to work in the future, or with sandboxed macros (c.f. experimental projects like https://github.com/dtolnay/watt). It's a very minor QoL impediment to write "secondary", I don't see it as an issue in practice. Are multiple
Imo it's fine to have a first version with this limitation, and look into this feature in a separate PR. That way, the amount of changes to review and check isn't too big 🙂 |
yes, unlimited, unless there's a limit w.r.t. the linker etc |
d5c73e6
to
6adbdc3
Compare
2006f8a
to
a0df6ed
Compare
have the key 'secondary'
a0df6ed
to
2a5660f
Compare
Thanks! I'm a bit busy with preparing v0.2 release, so review may have to wait a bit, but I haven't forgotten! 🙂 |
I can confirm that as of 2a5660f, the automated tests pass on Linux. |
// ---------------------------------------------------------------------------------------------------------------------------------------------- | ||
|
||
#[derive(GodotClass)] | ||
#[class(init, base=Object)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add some kind of IObject
impl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to what end?
if meta.to_string() != "" { | ||
return bail!( | ||
meta, | ||
"#[godot_api] on a trait implementation currently does not support any parameters" | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused what this is supposed to catch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A user could do #[godot_api(a, b, x=y)]
, which in the old implementation would just be ignored, and now would throw an error.
The main thought behind this was to catch people accidentally adding secondary
to a trait impl block instead of an inherent impl block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good corner case -- could you add this explanation to the code itself as a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot, still impressed how quickly you figured this out! 👍
Added some remarks, most of them smaller bikeshedding things. As a general style rule, comments should typically start with uppercase and end with a period (unless they're really just keywords).
Would also need a rebase.
/// Test that multiple inherent '#[godot_api]' impl blocks can be registered. | ||
/// https://github.com/godot-rust/gdext/pull/927 | ||
#[itest] | ||
fn godot_api_multiple_impl_blocks_pull_927() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The link is enough, no need to include _pull_927
in the test name 🙂
method_name: StringName, | ||
expected_result: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These could both be &str
parameters and convert internally.
assert!(result_as_string.is_ok()); | ||
assert_eq!(expected_result, result_as_string.unwrap()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better as one statement -- also, expected should be on the right:
assert!(result_as_string.is_ok()); | |
assert_eq!(expected_result, result_as_string.unwrap()); | |
assert_eq!(result_as_string, Ok(expected_result)); |
@@ -1076,3 +1076,59 @@ fn double_use_reference() { | |||
double_use.free(); | |||
emitter.free(); | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really testing objects (Gd
) but rather the registration API (godot::register::godot_api
macro).
As such, it should be in register_tests
directory. Maybe you could even crate a new file multiple_impls_test.rs
?
@@ -679,9 +679,36 @@ 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// # Multiple inherent impl blocks | |
/// # Multiple inherent `impl` blocks |
Also, it's not part of the table of contents in the beginning. Alternatively, you can replace the existing ToC with a mention of Sections in the sidebar, maybe in bold, something along:
For a table of contents, consult the Sections label in the sidebar on the left.
// just call all three methods, if that works, then they have all been correctly registered | ||
call_and_check_result(&mut obj, "foo".into(), "result of foo".into()); | ||
call_and_check_result(&mut obj, "bar".into(), "result of bar".into()); | ||
call_and_check_result(&mut obj, "baz".into(), "result of baz".into()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// just call all three methods, if that works, then they have all been correctly registered | |
call_and_check_result(&mut obj, "foo".into(), "result of foo".into()); | |
call_and_check_result(&mut obj, "bar".into(), "result of bar".into()); | |
call_and_check_result(&mut obj, "baz".into(), "result of baz".into()); | |
// Just call all three methods; if that works, then they have all been correctly registered. | |
call_and_check_result(&mut obj, "foo", "result of foo"); | |
call_and_check_result(&mut obj, "bar", "result of bar"); | |
call_and_check_result(&mut obj, "baz", "result of baz"); |
let result = quote! { | ||
|
||
#impl_block | ||
|
||
#storage | ||
|
||
#trait_impl | ||
|
||
#fill_storage | ||
|
||
#class_registration | ||
|
||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty lines are helpful when there are multi-line code groups that need to be separated, but here there's individual lines only. Please remove the empty lines in between, including after opening {
and before closing }
.
Also below 🙂
}); | ||
}; | ||
|
||
Ok(result) | ||
if !meta.secondary { | ||
// we are the primary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// we are the primary | |
// We are the primary impl block. |
let storage = quote! { | ||
|
||
#[used] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let storage = quote! { | |
#[used] | |
let storage = quote! { | |
#[used] |
@@ -49,14 +49,15 @@ macro_rules! plugin_add_inner_wasm { | |||
}; | |||
} | |||
|
|||
/// executes a block of code before main by utilising platform specific linker instructions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// executes a block of code before main by utilising platform specific linker instructions | |
/// Executes a block of code before main, by utilising platform specific linker instructions. |
fixes #925
It errors with acceptable error messages and spans when unsupported keys are added to the attribute: