From 2da901425ecc7a548537eb3686f763a06c390a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luciano=20Le=C3=A3o?= Date: Thu, 18 May 2023 07:47:37 -0300 Subject: [PATCH 1/2] Signals now accept parameters --- godot-core/src/lib.rs | 3 +- godot-core/src/macros.rs | 39 +++++++++++++++---------- godot-macros/src/godot_api.rs | 54 ++++++++++++++++++++++++++--------- 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/godot-core/src/lib.rs b/godot-core/src/lib.rs index b1f0b4783..3c8397985 100644 --- a/godot-core/src/lib.rs +++ b/godot-core/src/lib.rs @@ -53,7 +53,8 @@ pub mod private { pub use crate::registry::{callbacks, ClassPlugin, ErasedRegisterFn, PluginComponent}; pub use crate::storage::as_storage; pub use crate::{ - gdext_register_method, gdext_register_method_inner, gdext_virtual_method_callback, + gdext_get_arguments_info, gdext_register_method, gdext_register_method_inner, + gdext_virtual_method_callback, }; use crate::{log, sys}; diff --git a/godot-core/src/macros.rs b/godot-core/src/macros.rs index a08d04c26..794f10791 100644 --- a/godot-core/src/macros.rs +++ b/godot-core/src/macros.rs @@ -229,20 +229,7 @@ macro_rules! gdext_register_method_inner { // Arguments meta-information let argument_count = NUM_ARGS as u32; - let mut arguments_info: [PropertyInfo; NUM_ARGS] = { - let mut i = -1i32; - [$( - { - i += 1; - let prop = Sig::property_info(i, stringify!($param)); - //OnceArg::new(prop) - prop - }, - )*] - }; - let mut arguments_info_sys: [sys::GDExtensionPropertyInfo; NUM_ARGS] - = std::array::from_fn(|i| arguments_info[i].property_sys()); -// = std::array::from_fn(|i| arguments_info[i].once_sys()); + let mut arguments_info: [sys::GDExtensionPropertyInfo; NUM_ARGS] = $crate::gdext_get_arguments_info!(($($RetTy)+, $($ParamTy),*), $( $param, )*); let mut arguments_metadata: [sys::GDExtensionClassMethodArgumentMetadata; NUM_ARGS] = std::array::from_fn(|i| Sig::param_metadata(i as i32)); @@ -262,7 +249,7 @@ macro_rules! gdext_register_method_inner { return_value_info: std::ptr::addr_of_mut!(return_value_info_sys), return_value_metadata, argument_count, - arguments_info: arguments_info_sys.as_mut_ptr(), + arguments_info: arguments_info.as_mut_ptr(), arguments_metadata: arguments_metadata.as_mut_ptr(), default_argument_count: 0, default_arguments: std::ptr::null_mut(), @@ -621,3 +608,25 @@ macro_rules! gdext_ptrcall { ); }; } + +#[doc(hidden)] +#[macro_export] +macro_rules! gdext_get_arguments_info { + ( + $Signature:ty, + $($param:ident,)* + ) => { + { + use $crate::builtin::meta::*; + + let mut i = -1i32; + [$( + { + i += 1; + let prop = <$Signature as VarcallSignatureTuple>::property_info(i, stringify!($param)).property_sys(); + prop + }, + )*] + } + }; +} diff --git a/godot-macros/src/godot_api.rs b/godot-macros/src/godot_api.rs index 6a55ec3f2..3834c0efb 100644 --- a/godot-macros/src/godot_api.rs +++ b/godot-macros/src/godot_api.rs @@ -8,7 +8,7 @@ use crate::util; use crate::util::bail; use proc_macro2::{Ident, TokenStream}; use quote::quote; -use venial::{AttributeValue, Declaration, Error, Function, Impl, ImplMember}; +use venial::{AttributeValue, Declaration, Error, FnParam, Function, Impl, ImplMember, TyExpr}; pub fn transform(input_decl: Declaration) -> Result { let decl = match input_decl { @@ -62,11 +62,34 @@ fn transform_inherent_impl(mut decl: Impl) -> Result { let class_name = util::validate_impl(&decl, None, "godot_api")?; let class_name_str = class_name.to_string(); - //let register_fn = format_ident!("__godot_rust_register_{}", class_name_str); - //#[allow(non_snake_case)] - let (funcs, signals) = process_godot_fns(&mut decl)?; - let signal_name_strs = signals.into_iter().map(|ident| ident.to_string()); + + let mut signal_name_strs: Vec = Vec::new(); + let mut signal_parameters_count: Vec = Vec::new(); + let mut signal_parameters: Vec = Vec::new(); + + for signature in signals { + let mut param_types: Vec = Vec::new(); + let mut param_names: Vec = Vec::new(); + + for param in signature.params.inner { + match ¶m.0 { + FnParam::Typed(param) => { + param_types.push(param.ty.clone()); + param_names.push(param.name.clone()); + } + FnParam::Receiver(_) => {} + }; + } + + signal_name_strs.push(signature.name.to_string()); + signal_parameters_count.push(param_names.len() as i64); + signal_parameters.push( + quote! { + ::godot::private::gdext_get_arguments_info!(((), #(#param_types ),*), #(#param_names, )*).as_ptr() + }, + ); + } let prv = quote! { ::godot::private }; @@ -74,7 +97,6 @@ fn transform_inherent_impl(mut decl: Impl) -> Result { #decl impl ::godot::obj::cap::ImplementsGodotApi for #class_name { - //fn __register_methods(_builder: &mut ::godot::builder::ClassBuilder) { fn __register_methods() { #( ::godot::private::gdext_register_method!(#class_name, #funcs); @@ -83,14 +105,17 @@ fn transform_inherent_impl(mut decl: Impl) -> Result { unsafe { let class_name = ::godot::builtin::StringName::from(#class_name_str); use ::godot::sys; + #( + let parameters = #signal_parameters; let signal_name = ::godot::builtin::StringName::from(#signal_name_strs); + sys::interface_fn!(classdb_register_extension_class_signal)( sys::get_library(), class_name.string_sys(), signal_name.string_sys(), - std::ptr::null(), // NULL only valid for zero parameters, in current impl; maybe better empty slice - 0, + parameters, + sys::GDExtensionInt::from(#signal_parameters_count), ); )* } @@ -110,9 +135,9 @@ fn transform_inherent_impl(mut decl: Impl) -> Result { Ok(result) } -fn process_godot_fns(decl: &mut Impl) -> Result<(Vec, Vec), Error> { +fn process_godot_fns(decl: &mut Impl) -> Result<(Vec, Vec), Error> { let mut func_signatures = vec![]; - let mut signal_idents = vec![]; // TODO consider signature + let mut signal_signatures = vec![]; let mut removed_indexes = vec![]; for (index, item) in decl.body_items.iter_mut().enumerate() { @@ -147,11 +172,12 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec, Vec), Err func_signatures.push(sig); } BoundAttrType::Signal(ref _attr_val) => { - if !method.params.is_empty() || method.return_ty.is_some() { - return attr.bail("parameters and return types not yet supported", method); + if method.return_ty.is_some() { + return attr.bail("return types are not supported", method); } + let sig = util::reduce_to_signature(method); - signal_idents.push(method.name.clone()); + signal_signatures.push(sig.clone()); removed_indexes.push(index); } } @@ -164,7 +190,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec, Vec), Err decl.body_items.remove(index); } - Ok((func_signatures, signal_idents)) + Ok((func_signatures, signal_signatures)) } fn extract_attributes(method: &Function) -> Result, Error> { From 069b64732e6ff194fcaaa0f23ba7ed4bd5b5efed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luciano=20Le=C3=A3o?= Date: Fri, 2 Jun 2023 06:29:05 -0300 Subject: [PATCH 2/2] Add test for signals with multiple parameters --- itest/rust/src/lib.rs | 1 + itest/rust/src/signal_test.rs | 97 +++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 itest/rust/src/signal_test.rs diff --git a/itest/rust/src/lib.rs b/itest/rust/src/lib.rs index 9366ea52b..19b7f98d1 100644 --- a/itest/rust/src/lib.rs +++ b/itest/rust/src/lib.rs @@ -31,6 +31,7 @@ mod projection_test; mod quaternion_test; mod rect2i_test; mod rid_test; +mod signal_test; mod singleton_test; mod string; mod transform2d_test; diff --git a/itest/rust/src/signal_test.rs b/itest/rust/src/signal_test.rs new file mode 100644 index 000000000..ed89de42c --- /dev/null +++ b/itest/rust/src/signal_test.rs @@ -0,0 +1,97 @@ +/* + * 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/. + */ + +use std::cell::Cell; + +use godot::bind::{godot_api, GodotClass}; +use godot::builtin::{GodotString, Variant}; + +use godot::engine::Object; +use godot::obj::{Base, Gd, Share}; +use godot::sys; + +use crate::itest; + +#[derive(GodotClass)] +#[class(init, base=Object)] +struct Emitter { + #[base] + base: Base, +} + +#[godot_api] +impl Emitter { + #[signal] + fn signal_0_arg(); + #[signal] + fn signal_1_arg(arg1: i64); + #[signal] + fn signal_2_arg(arg1: Gd, arg2: GodotString); +} + +#[derive(GodotClass)] +#[class(init, base=Object)] +struct Receiver { + used: [Cell; 3], + #[base] + base: Base, +} + +#[godot_api] +impl Receiver { + #[func] + fn receive_0_arg(&self) { + self.used[0].set(true); + } + #[func] + fn receive_1_arg(&self, arg1: i64) { + self.used[1].set(true); + assert_eq!(arg1, 987); + } + #[func] + fn receive_2_arg(&self, arg1: Gd, arg2: GodotString) { + assert_eq!(self.base.share(), arg1); + assert_eq!(SIGNAL_ARG_STRING, arg2.to_string()); + + self.used[2].set(true); + } +} + +const SIGNAL_ARG_STRING: &str = "Signal string arg"; + +#[itest] +/// Test that godot can call a method that is connect with a signal +fn signals() { + let mut emitter = Gd::::new_default(); + let receiver = Gd::::new_default(); + + let args = [ + vec![], + vec![Variant::from(987)], + vec![ + Variant::from(receiver.share()), + Variant::from(SIGNAL_ARG_STRING), + ], + ]; + + for (i, arg) in args.iter().enumerate() { + let signal_name = format!("signal_{i}_arg"); + let receiver_name = format!("receive_{i}_arg"); + + emitter.bind_mut().connect( + signal_name.clone().into(), + receiver.callable(receiver_name), + 0, + ); + + emitter.bind_mut().emit_signal(signal_name.into(), arg); + + assert!(receiver.bind().used[i].get()); + } + + receiver.free(); + emitter.free(); +}