diff --git a/godot-codegen/src/generator/signals.rs b/godot-codegen/src/generator/signals.rs index d13a69848..ce81b89f7 100644 --- a/godot-codegen/src/generator/signals.rs +++ b/godot-codegen/src/generator/signals.rs @@ -120,9 +120,9 @@ fn make_with_signals_impl( // During construction, C = Self. #[doc(hidden)] - fn __signals_from_external(gd_mut: &mut Gd) -> Self::SignalCollection<'_, Self> { + fn __signals_from_external(gd_ref: & Gd) -> Self::SignalCollection<'_, Self> { Self::SignalCollection { - __internal_obj: Some(gd_mut.clone()), + __internal_obj: Some(gd_ref.clone()), } } } diff --git a/godot-core/src/obj/gd.rs b/godot-core/src/obj/gd.rs index f6b848052..d320033e5 100644 --- a/godot-core/src/obj/gd.rs +++ b/godot-core/src/obj/gd.rs @@ -732,7 +732,7 @@ where /// /// [`WithUserSignals::signals()`]: crate::obj::WithUserSignals::signals() #[cfg(since_api = "4.2")] - pub fn signals(&mut self) -> T::SignalCollection<'_, T> { + pub fn signals(&self) -> T::SignalCollection<'_, T> { T::__signals_from_external(self) } } diff --git a/godot-core/src/obj/traits.rs b/godot-core/src/obj/traits.rs index 8efb533c4..e41a0dad2 100644 --- a/godot-core/src/obj/traits.rs +++ b/godot-core/src/obj/traits.rs @@ -473,7 +473,7 @@ pub trait WithSignals: GodotClass + Inherits { /// /// Takes by reference and not value, to retain lifetime chain. #[doc(hidden)] - fn __signals_from_external(external: &mut Gd) -> Self::SignalCollection<'_, Self>; + fn __signals_from_external(external: &Gd) -> Self::SignalCollection<'_, Self>; } /// Implemented for user-defined classes with at least one `#[signal]` declaration. diff --git a/godot-core/src/registry/signal/connect_builder.rs b/godot-core/src/registry/signal/connect_builder.rs index 60eb07431..fac1864d0 100644 --- a/godot-core/src/registry/signal/connect_builder.rs +++ b/godot-core/src/registry/signal/connect_builder.rs @@ -5,13 +5,17 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use super::{make_callable_name, make_godot_fn}; use crate::builtin::{Callable, GString, Variant}; use crate::classes::object::ConnectFlags; use crate::meta; -use crate::obj::{bounds, Bounds, Gd, GodotClass, WithSignals}; -use crate::registry::signal::{SignalReceiver, TypedSignal}; +use crate::meta::FromGodot; +use crate::obj::WithSignals; +use crate::registry::signal::{GodotDeref, ToSignalObj, TypedSignal}; +use std::fmt::Debug; +use std::ops::DerefMut; -/// Type-state builder for customizing signal connections. +/// Builder for customizing signal connections. /// /// Allows a high degree of customization for connecting signals, while maintaining complete type safety. /// @@ -25,231 +29,54 @@ use crate::registry::signal::{SignalReceiver, TypedSignal}; // - Pack multiple types together into "type lists", i.e. custom structs carrying the type state. For a user, this would appear as one type, // - which could also be #[doc(hidden)]. However, this may make the trait resolution more complex and worsen error messages, so not done now. /// -/// # Builder stages +/// # Customization +/// Customizing your signal connection must be done **before** providing the function being connected +/// (can be done by using of the `connect_**` methods) (see section `Finalizing` bellow). /// -/// The builder API has a well-defined flow and is separated in stages. In each stage, you have certain builder methods available that you can -/// or must call, before advancing to the next stage. Check the instructions. -/// -/// ## Stage 1 (required) -/// Choose one: -/// - [`function`][Self::function]: Connect a global/associated function or a closure. -/// - [`object_self`][Self::object_self]: If you want to connect a method (in stage 2), running on the same object as the signal. -/// - [`object`][Self::object]: If you want to connect a method, running on a separate object. -/// -/// ## Stage 2 (conditional) -/// Required iff _(if and only if)_ `object_self` or `object` was called in stage 1. -/// - [`method_mut`][Self::method_mut]: Connect a `&mut self` method. -/// - [`method_immut`][Self::method_immut]: Connect a `&self` method. -/// -/// ## Stage 3 /// All these methods are optional, and they can be combined. // Use HTML link due to conditional compilation; renders badly if target symbol is unavailable. -/// - [`sync`](#method.sync): If the signal connection should be callable across threads. \ -/// Requires `Send` + `Sync` bounds on the provided function/method, and is only available for the `experimental-threads` Cargo feature. -/// - [`name`][Self::name]: Name of the `Callable` (for debug purposes). \ +/// - [`name()`][Self::name]: Name of the `Callable` (for debug purposes). \ /// If not specified, the Rust function name is used. This is typically a good default, but not very readable for closures. -/// - [`flags`][Self::flags]: Provide one or multiple [`ConnectFlags`][crate::classes::object::ConnectFlags], possibly combined with bitwise OR. -/// -/// ## Final stage -/// - [`done`][Self::done]: Finalize the connection. Consumes the builder and registers the signal with Godot. +/// - [`flags()`][Self::flags]: Provide one or multiple [`ConnectFlags`][crate::classes::object::ConnectFlags], possibly combined with bitwise OR. /// +/// # Finalizing +/// After customizing your builder, you can register the connection by using one of the following methods: +/// - [`connect()`][Self::connect]: Connect a global/associated function or a closure. +/// - [`connect_self()`][Self::connect_self]: Connect a method or closure that runs on the signal emitter. +/// - [`connect_other()`][Self::connect_other]: Connect a method or closure that runs on a separate object. +/// - [`connect_sync()`](#method.connect_sync): Connect a global/associated function or closure that should be callable across threads. \ +/// Allows signal to be emitted from other threads. \ +/// Requires `Send` + `Sync` bounds on the provided function/method, and is only available for the `experimental-threads` Cargo feature. #[must_use] -pub struct ConnectBuilder<'ts, 'c, CSig: WithSignals, CRcv, Ps, GodotFn> { - parent_sig: &'ts mut TypedSignal<'c, CSig, Ps>, +pub struct ConnectBuilder<'ts, 'c, C: WithSignals, Ps> { + parent_sig: &'ts TypedSignal<'c, C, Ps>, data: BuilderData, - - // Type-state data. - receiver_obj: CRcv, - godot_fn: GodotFn, } -impl<'ts, 'c, CSig: WithSignals, Ps: meta::ParamTuple> ConnectBuilder<'ts, 'c, CSig, (), Ps, ()> { - pub(super) fn new(parent_sig: &'ts mut TypedSignal<'c, CSig, Ps>) -> Self { - ConnectBuilder { - parent_sig, - data: BuilderData::default(), - godot_fn: (), - receiver_obj: (), - } - } - - /// **Stage 1:** global/associated function or closure. - pub fn function( - self, - mut function: F, - ) -> ConnectBuilder< - 'ts, - 'c, - /* CSig = */ CSig, - /* CRcv = */ (), - /* Ps = */ Ps, - /* GodotFn= */ impl FnMut(&[&Variant]) -> Result + 'static, - > - where - F: SignalReceiver<(), Ps>, - Ps: meta::InParamTuple + 'static, - { - let godot_fn = make_godot_fn(move |args| { - function.call((), args); - }); - - ConnectBuilder { - parent_sig: self.parent_sig, - data: self.data.with_callable_name::(), - godot_fn, - receiver_obj: (), - } - } - - /// **Stage 1:** prepare for a method taking `self` (the class declaring the `#[signal]`). - pub fn object_self(self) -> ConnectBuilder<'ts, 'c, CSig, Gd, Ps, ()> { - let receiver_obj: Gd = self.parent_sig.receiver_object(); - - ConnectBuilder { - parent_sig: self.parent_sig, - data: self.data, - godot_fn: (), - receiver_obj, - } - } - - /// **Stage 1:** prepare for a method taking any `Gd` object. - pub fn object( - self, - object: &Gd, - ) -> ConnectBuilder<'ts, 'c, CSig, Gd, Ps, ()> { - ConnectBuilder { - parent_sig: self.parent_sig, - data: self.data, - godot_fn: (), - receiver_obj: object.clone(), - } - } -} - -impl<'ts, 'c, CSig: WithSignals, CRcv: GodotClass, Ps: meta::ParamTuple> - ConnectBuilder<'ts, 'c, CSig, Gd, Ps, ()> -{ - /// **Stage 2:** method taking `&mut self`. - pub fn method_mut( - self, - mut method_with_mut_self: F, - ) -> ConnectBuilder< - 'ts, - 'c, - /* CSig = */ CSig, - /* CRcv: again reset to unit type, after object has been captured in closure. */ - (), - /* Ps = */ Ps, - /* GodotFn = */ impl FnMut(&[&Variant]) -> Result, - > - where - CRcv: GodotClass + Bounds, - for<'c_rcv> F: SignalReceiver<&'c_rcv mut CRcv, Ps>, - Ps: meta::InParamTuple, - { - let mut gd: Gd = self.receiver_obj; - let godot_fn = make_godot_fn(move |args| { - let mut guard = gd.bind_mut(); - let instance = &mut *guard; - method_with_mut_self.call(instance, args); - }); - - ConnectBuilder { - parent_sig: self.parent_sig, - data: self.data.with_callable_name::(), - godot_fn, - receiver_obj: (), - } - } - - /// **Stage 2:** method taking `&self`. - pub fn method_immut( - self, - mut method_with_shared_self: F, - ) -> ConnectBuilder< - 'ts, - 'c, - /* CSig = */ CSig, - /* CRcv: again reset to unit type, after object has been captured in closure. */ - (), - /* Ps = */ Ps, - /* GodotFn = */ impl FnMut(&[&Variant]) -> Result, - > - where - CRcv: GodotClass + Bounds, - for<'c_rcv> F: SignalReceiver<&'c_rcv CRcv, Ps>, - Ps: meta::InParamTuple, - { - let gd: Gd = self.receiver_obj; - let godot_fn = make_godot_fn(move |args| { - let guard = gd.bind(); - let instance = &*guard; - method_with_shared_self.call(instance, args); - }); +/// Gathers all the non-typestate data, so that the builder can easily transfer it without manually moving each field. +#[derive(Default)] +struct BuilderData { + /// User-specified name; if not provided, the Rust RTTI type name of the function is used. + callable_name: Option, - ConnectBuilder { - parent_sig: self.parent_sig, - data: self.data.with_callable_name::(), - godot_fn, - receiver_obj: (), - } - } + /// Godot connection flags. + connect_flags: Option, } #[allow(clippy::needless_lifetimes)] // 'ts + 'c are used conditionally. -impl<'ts, 'c, CSig, CRcv, Ps, GodotFn> ConnectBuilder<'ts, 'c, CSig, CRcv, Ps, GodotFn> +impl<'ts, 'c, C, Ps> ConnectBuilder<'ts, 'c, C, Ps> where - CSig: WithSignals, + C: WithSignals, Ps: meta::ParamTuple, - GodotFn: FnMut(&[&Variant]) -> Result + 'static, { - /// **Stage 3:** allow signal to be called across threads. - /// - /// Requires `Send` + `Sync` bounds on the previously provided function/method, and is only available for the `experimental-threads` - /// Cargo feature. - #[cfg(feature = "experimental-threads")] - pub fn sync( - self, - ) -> ConnectBuilder< - 'ts, - 'c, - /* CSig = */ CSig, - /* CRcv = */ CRcv, - /* Ps = */ Ps, - /* GodotFn = */ impl FnMut(&[&Variant]) -> Result, - > - where - // Why both Send+Sync: closure can not only impact another thread (Sync), but it's also possible to share such Callables across threads - // (Send) or even call them from multiple threads (Sync). We don't differentiate the fine-grained needs, it's either thread-safe or not. - GodotFn: Send + Sync, - { - let Self { - parent_sig, - mut data, - receiver_obj, - godot_fn, - } = self; - - assert!( - data.sync_callable.is_none(), - "sync() called twice on the same builder." - ); - - let dummy_fn = - |_variants: &[&Variant]| panic!("sync() closure should have been replaced by now."); - - data.sync_callable = Some(Callable::from_sync_fn(data.callable_name_ref(), godot_fn)); - + pub(super) fn new(parent_sig: &'ts TypedSignal<'c, C, Ps>) -> Self { ConnectBuilder { parent_sig, - data, - godot_fn: dummy_fn, - receiver_obj, + data: BuilderData::default(), } } - /// **Stage 3:** Name of the `Callable`, mostly used for debugging. + /// Name of the `Callable`, mostly used for debugging. /// /// If not provided, the Rust type name of the function/method is used. pub fn name(mut self, name: impl meta::AsArg) -> Self { @@ -263,7 +90,7 @@ where self } - /// **Stage 3:** add one or multiple flags to the connection, possibly combined with `|` operator. + /// Add one or multiple flags to the connection, possibly combined with `|` operator. pub fn flags(mut self, flags: ConnectFlags) -> Self { assert!( self.data.connect_flags.is_none(), @@ -274,81 +101,141 @@ where self } - /// Finalize the builder. + /// Directly connect a Rust callable `godot_fn`, with a name based on `F`. /// - /// Actually connects the signal with the provided function/method. Consumes this builder instance and returns the mutable borrow of - /// the parent [`TypedSignal`] for further use. - pub fn done(self) { - let Self { - parent_sig, - data, - godot_fn, - receiver_obj: _, - } = self; - - let callable_name = data.callable_name_ref(); - - // If sync() was previously called, use the already-existing callable, otherwise construct a local one now. - #[cfg(feature = "experimental-threads")] - let callable = match data.sync_callable { - Some(sync_callable) => sync_callable, - None => Callable::from_local_fn(callable_name, godot_fn), + /// This exists as a shorthand for the connect methods and avoids the generic instantiation of the full-blown + /// type state builder for simple + common connections, thus hopefully being a tiny bit lighter on compile times. + fn inner_connect_godot_fn( + self, + godot_fn: impl FnMut(&[&Variant]) -> Result + 'static, + ) { + let callable_name = match &self.data.callable_name { + Some(user_provided_name) => user_provided_name, + None => &make_callable_name::(), }; - #[cfg(not(feature = "experimental-threads"))] let callable = Callable::from_local_fn(callable_name, godot_fn); - - parent_sig.inner_connect_untyped(&callable, data.connect_flags); - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -/// Gathers all the non-typestate data, so that the builder can easily transfer it without manually moving each field. -#[derive(Default)] -struct BuilderData { - /// User-specified name; if not provided, the Rust RTTI type name of the function is used. - callable_name: Option, - - /// Godot connection flags. - connect_flags: Option, - - /// If [`sync()`][ConnectBuilder::sync] was called, then this already contains the populated closure. - #[cfg(feature = "experimental-threads")] - sync_callable: Option, -} - -impl BuilderData { - fn with_callable_name(mut self) -> Self { - self.callable_name = Some(make_callable_name::()); - self - } - - fn callable_name_ref(&self) -> &GString { - self.callable_name - .as_ref() - .expect("Signal connect name not set; this is a bug.") + self.parent_sig + .inner_connect_untyped(&callable, self.data.connect_flags); } } -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -pub(super) fn make_godot_fn(mut input: F) -> impl FnMut(&[&Variant]) -> Result -where - F: FnMut(Ps), - Ps: meta::InParamTuple, -{ - move |variant_args: &[&Variant]| -> Result { - let args = Ps::from_variant_array(variant_args); - input(args); - - Ok(Variant::nil()) - } +macro_rules! impl_builder_connect { + ($( $args:ident : $Ps:ident ),*) => { + // -------------------------------------------------------------------------------------------------------------------------------------- + // SignalReceiver + + impl + ConnectBuilder<'_, '_, C, ($($Ps,)*)> { + /// Connect a non-member function (global function, associated function or closure). + /// + /// Example usages: + /// ```ignore + /// sig.connect_builder().connect(Self::static_func); + /// sig.connect_builder().flags(ConnectFlags::DEFERRED).connect(global_func); + /// sig.connect(|arg| { /* closure */ }); + /// ``` + /// + /// - To connect to a method on the object that owns this signal, use [`connect_self()`][Self::connect_self]. + /// - If you need [`connect flags`](ConnectFlags), call [`flags()`](Self::flags) before this. + /// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature "experimental-threads"). + pub fn connect(self, mut function: F) + where + F: FnMut($($Ps),*) -> R + 'static, + { + let godot_fn = make_godot_fn(move |($($args,)*):($($Ps,)*)| { + function($($args),*); + }); + + self.inner_connect_godot_fn::(godot_fn); + } + + /// Connect a method (member function) with `&mut self` as the first parameter. + /// + /// - To connect to methods on other objects, use [`connect_other()`][Self::connect_other]. + /// - If you need [`connect flags`](ConnectFlags), call [`flags()`](Self::flags) before this. + /// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature "experimental-threads"). + pub fn connect_self(self, mut function: F) + where + F: FnMut(&mut C, $($Ps),*) -> R + 'static, + C: GodotDeref, + { + let mut gd = self.parent_sig.receiver_object(); + let godot_fn = make_godot_fn(move |($($args,)*):($($Ps,)*)| { + let mut target = C::get_mut(&mut gd); + let target_mut = target.deref_mut(); + function(target_mut, $($args),*); + }); + + self.inner_connect_godot_fn::(godot_fn); + } + + /// Connect a method (member function) with any `&mut OtherC` as the first parameter, where + /// `OtherC`: [`GodotClass`](crate::obj::GodotClass) (both user and engine classes are accepted). + /// + /// The parameter `object` can be of 2 different "categories": + /// - Any `&Gd` (e.g.: `&Gd`, `&Gd`). + /// - `&OtherC`, as long as `OtherC` is a user class that contains a `base` field (it implements the + /// [`WithBaseField`](crate::obj::WithBaseField) trait). + /// + /// --- + /// + /// - To connect to methods on the object that owns this signal, use [`connect_self()`][Self::connect_self]. + /// - If you need [`connect flags`](ConnectFlags), call [`flags()`](Self::flags) before this. + /// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature "experimental-threads"). + pub fn connect_other(self, object: &impl ToSignalObj, mut method: F) + where + F: FnMut(&mut OtherC, $($Ps),*) -> R + 'static, + OtherC: GodotDeref, + { + let mut gd = object.to_signal_obj(); + + let godot_fn = make_godot_fn(move |($($args,)*):($($Ps,)*)| { + let mut target = OtherC::get_mut(&mut gd); + let target_mut = target.deref_mut(); + method(target_mut, $($args),*); + }); + + self.inner_connect_godot_fn::(godot_fn); + } + + /// Connect to this signal using a thread-safe function, allows the signal to be called across threads. + /// + /// Requires `Send` + `Sync` bounds on the provided function `F`, and is only available for the `experimental-threads` + /// Cargo feature. + /// + /// If you need [`connect flags`](ConnectFlags), call [`flags()`](Self::flags) before this. + #[cfg(feature = "experimental-threads")] + pub fn connect_sync(self, mut function: F) + where + // Why both Send+Sync: closure can not only impact another thread (Sync), but it's also possible to share such Callables across threads + // (Send) or even call them from multiple threads (Sync). We don't differentiate the fine-grained needs, it's either thread-safe or not. + F: FnMut($($Ps),*) -> R + Send + Sync + 'static, + { + let godot_fn = make_godot_fn(move |($($args,)*):($($Ps,)*)| { + function($($args),*); + }); + + let callable_name = match &self.data.callable_name { + Some(user_provided_name) => user_provided_name, + None => &make_callable_name::(), + }; + + let callable = Callable::from_sync_fn(callable_name, godot_fn); + self.parent_sig.inner_connect_untyped(&callable, self.data.connect_flags); + } + } + }; } -pub(super) fn make_callable_name() -> GString { - // When using sys::short_type_name() in the future, make sure global "func" and member "MyClass::func" are rendered as such. - // PascalCase heuristic should then be good enough. - - std::any::type_name::().into() -} +impl_builder_connect!(); +impl_builder_connect!(arg0: P0); +impl_builder_connect!(arg0: P0, arg1: P1); +impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2); +impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3); +impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4); +impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5); +impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6); +impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7); +impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8); +impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8, arg9: P9); diff --git a/godot-core/src/registry/signal/godot_deref.rs b/godot-core/src/registry/signal/godot_deref.rs new file mode 100644 index 000000000..61d3a093b --- /dev/null +++ b/godot-core/src/registry/signal/godot_deref.rs @@ -0,0 +1,75 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * 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 crate::obj::bounds::{DeclEngine, DeclUser}; +use crate::obj::{Gd, GdMut, GdRef, GodotClass, WithBaseField}; +use std::ops::{Deref, DerefMut}; + +/// Provides a unified way of both user and engine classes to be explicitly dereferenced. +/// +/// This is mainly used by the `connect_**` functions of [`TypedSignal`](crate::registry::signal::TypedSignal). +/// +/// # Motivation +/// Although both user and engine classes are often wrapped in a `Gd`, dereferencing them is done differently depending +/// on whether they are made by the user or engine: +/// - `Gd` can be de-refed directly into `&EngineClass` and `&mut EngineClass`. +/// - `Gd` must first go through [`bind()`](Gd::bind)/[`bind_mut()`](Gd::bind_mut), which can finally +/// be de-refed into `&UserClass` and `&mut UserClass`, respectively. +/// +/// Without this trait, there's no clear/generic way of writing functions that can accept both user and engine classes +/// but need to deref said classes in some way. +/// +/// [`GodotDeref`](Self) solves this explicitly handling each category in a different way, but still resulting +/// in a variable that can be de-refed into `&T`/`&mut T`. +/// +/// # Generic param `Decl` +/// Rustc [does not acknowledge associated type bounds when checking for overlapping impls](https://github.com/rust-lang/rust/issues/20400), +/// this parameter is essentially used to create 2 different traits, one for each "category" (user or engine). +/// +/// Despite being 2 different traits, a function can accept both by simply being generic over `Decl`: +/// ```rust ignore +/// fn o_minus>(obj: &Gd) { +/// let ref_provider = obj.get_ref(); +/// let obj_ref: &C = & *ref_provider; // regardless of `Decl`, we can still deref since the bounds on `TargetRef`/`TargetMut` enforce that. +/// } +/// ``` +/// +// The crate `https://crates.io/crates/disjoint_impls` handles this in a more user-friendly way, we should +// consider using it if disjoint impls are going to be frequently used. +#[allow(clippy::needless_lifetimes)] // False positive. +pub trait GodotDeref: GodotClass { + type TargetRef<'a>: Deref; + type TargetMut<'a>: DerefMut; + + fn get_ref<'a>(gd: &'a Gd) -> Self::TargetRef<'a>; + fn get_mut<'a>(gd: &'a mut Gd) -> Self::TargetMut<'a>; +} + +#[allow(clippy::needless_lifetimes)] // False positive. +impl> GodotDeref for T { + type TargetRef<'a> = Gd; + type TargetMut<'a> = Gd; + + fn get_ref<'a>(gd: &'a Gd) -> Self::TargetRef<'a> { + gd.clone() + } + fn get_mut<'a>(gd: &'a mut Gd) -> Self::TargetMut<'a> { + gd.clone() + } +} + +#[allow(clippy::needless_lifetimes)] // False positive. +impl GodotDeref for T { + type TargetRef<'a> = GdRef<'a, T>; + type TargetMut<'a> = GdMut<'a, T>; + + fn get_ref<'a>(gd: &'a Gd) -> Self::TargetRef<'a> { + gd.bind() + } + fn get_mut<'a>(gd: &'a mut Gd) -> Self::TargetMut<'a> { + gd.bind_mut() + } +} diff --git a/godot-core/src/registry/signal/mod.rs b/godot-core/src/registry/signal/mod.rs index 889f1a415..f80204321 100644 --- a/godot-core/src/registry/signal/mod.rs +++ b/godot-core/src/registry/signal/mod.rs @@ -8,20 +8,21 @@ // Whole module only available in Godot 4.2+. mod connect_builder; +mod godot_deref; mod signal_object; mod typed_signal; -pub(crate) mod variadic; +use crate::builtin::{GString, Variant}; +use crate::meta; pub(crate) use connect_builder::*; +use godot_deref::GodotDeref; pub(crate) use signal_object::*; pub(crate) use typed_signal::*; -pub(crate) use variadic::SignalReceiver; // Used in `godot` crate. pub mod re_export { pub use super::connect_builder::ConnectBuilder; pub use super::typed_signal::TypedSignal; - pub use super::variadic::SignalReceiver; } // Used in `godot::private` module. @@ -32,3 +33,27 @@ pub mod priv_re_export { } // ParamTuple re-exported in crate::meta. + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +// Used by both `TypedSignal` and `ConnectBuilder`. +fn make_godot_fn(mut input: F) -> impl FnMut(&[&Variant]) -> Result +where + F: FnMut(Ps), + Ps: meta::InParamTuple, +{ + move |variant_args: &[&Variant]| -> Result { + let args = Ps::from_variant_array(variant_args); + input(args); + + Ok(Variant::nil()) + } +} + +// Used by both `TypedSignal` and `ConnectBuilder`. +fn make_callable_name() -> GString { + // When using sys::short_type_name() in the future, make sure global "func" and member "MyClass::func" are rendered as such. + // PascalCase heuristic should then be good enough. + + std::any::type_name::().into() +} diff --git a/godot-core/src/registry/signal/typed_signal.rs b/godot-core/src/registry/signal/typed_signal.rs index 43c70fb98..4a5894eaf 100644 --- a/godot-core/src/registry/signal/typed_signal.rs +++ b/godot-core/src/registry/signal/typed_signal.rs @@ -5,15 +5,16 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use super::{make_callable_name, make_godot_fn, ConnectBuilder, GodotDeref, SignalObject}; use crate::builtin::{Callable, Variant}; use crate::classes::object::ConnectFlags; use crate::meta; -use crate::obj::{bounds, Bounds, Gd, GodotClass, WithBaseField, WithSignals, WithUserSignals}; -use crate::registry::signal::{ - make_callable_name, make_godot_fn, ConnectBuilder, SignalObject, SignalReceiver, -}; +use crate::meta::FromGodot; +use crate::obj::{Gd, GodotClass, WithBaseField, WithSignals}; use std::borrow::Cow; +use std::fmt::Debug; use std::marker::PhantomData; +use std::ops::DerefMut; /// Object part of the signal receiver (handler). /// @@ -46,12 +47,12 @@ impl ToSignalObj for C { /// `Deref` and `DerefMut` to `TypedSignal`. This allows you to either use the concrete APIs of the generated types, or the more generic /// ones of `TypedSignal`. /// -/// # Connecting a signal to a receiver +/// # Connecting a signal to a receiver. /// Receiver functions are functions that are called when a signal is emitted. You can connect a signal in many different ways: -/// - [`connect()`][Self::connect] for global functions, associated functions or closures. -/// - [`connect_self()`][Self::connect_self] for methods with `&mut self` as the first parameter. -/// - [`connect_obj()`][Self::connect_obj] for methods with any `Gd` (not `self`) as the first parameter. -/// - [`connect_builder()`][Self::connect_builder] for more complex setups. +/// - [`connect()`][Self::connect]: Connect a global/associated function or a closure. +/// - [`connect_self()`][Self::connect_self]: Connect a method or closure that runs on the signal emitter. +/// - [`connect_other()`][Self::connect_other]: Connect a method or closure that runs on a separate object. +/// - [`connect_builder()`][Self::connect_builder] for more complex setups (such as choosing [`ConnectFlags`] or making thread-safe connections). /// /// # Emitting a signal /// Code-generated signal types provide a method `emit(...)`, which adopts the names and types of the `#[signal]` parameter list. @@ -102,6 +103,14 @@ impl<'c, C: WithSignals, Ps: meta::ParamTuple> TypedSignal<'c, C, Ps> { object.cast() } + /// Fully customizable connection setup. + /// + /// The returned builder provides several methods to configure how to connect the signal. It needs to be finalized with a call + /// to any of the builder's `connect_**` methods. + pub fn connect_builder<'ts>(&'ts self) -> ConnectBuilder<'ts, 'c, C, Ps> { + ConnectBuilder::new(self) + } + /// Emit the signal with the given parameters. /// /// This is intended for generic use. Typically, you'll want to use the more specific `emit()` method of the code-generated signal @@ -117,89 +126,28 @@ impl<'c, C: WithSignals, Ps: meta::ParamTuple> TypedSignal<'c, C, Ps> { }); } - /// Connect a non-member function (global function, associated function or closure). - /// - /// Example usages: - /// ```ignore - /// sig.connect(Self::static_func); - /// sig.connect(global_func); - /// sig.connect(|arg| { /* closure */ }); - /// ``` - /// - /// To connect to a method of the own object `self`, use [`connect_self()`][Self::connect_self]. \ - /// If you need cross-thread signals or connect flags, use [`connect_builder()`][Self::connect_builder]. - pub fn connect(&mut self, mut function: F) - where - F: SignalReceiver<(), Ps>, - Ps: meta::InParamTuple + 'static, - { - let godot_fn = make_godot_fn(move |args| { - function.call((), args); - }); - - self.inner_connect_godot_fn::(godot_fn); - } - - /// Connect a method (member function) with any `Gd` (not `self`) as the first parameter. - /// - /// To connect to methods on the same object that declares the `#[signal]`, use [`connect_self()`][Self::connect_self]. \ - /// If you need cross-thread signals or connect flags, use [`connect_builder()`][Self::connect_builder]. - pub fn connect_obj(&mut self, object: &impl ToSignalObj, mut method: F) - where - OtherC: GodotClass + Bounds, - for<'c_rcv> F: SignalReceiver<&'c_rcv mut OtherC, Ps>, - Ps: meta::InParamTuple + 'static, - { - let mut gd = object.to_signal_obj(); - // let mut gd = gd.to_owned_object(); - let godot_fn = make_godot_fn(move |args| { - let mut instance = gd.bind_mut(); - let instance = &mut *instance; - method.call(instance, args); - }); - - self.inner_connect_godot_fn::(godot_fn); - } - - /// Fully customizable connection setup. - /// - /// The returned builder provides several methods to configure how to connect the signal. It needs to be finalized with a call to - /// [`ConnectBuilder::done()`]. - pub fn connect_builder(&mut self) -> ConnectBuilder<'_, 'c, C, (), Ps, ()> { - ConnectBuilder::new(self) - } - /// Directly connect a Rust callable `godot_fn`, with a name based on `F`. /// - /// This exists as a short-hand for the connect methods on [`TypedSignal`] and avoids the generic instantiation of the full-blown + /// This exists as a shorthand for the connect methods on [`TypedSignal`] and avoids the generic instantiation of the full-blown /// type state builder for simple + common connections, thus hopefully being a tiny bit lighter on compile times. fn inner_connect_godot_fn( - &mut self, + &self, godot_fn: impl FnMut(&[&Variant]) -> Result + 'static, ) { let callable_name = make_callable_name::(); let callable = Callable::from_local_fn(&callable_name, godot_fn); - - let signal_name = self.name.as_ref(); - self.object.with_object_mut(|obj| { - obj.connect(signal_name, &callable); - }); + self.inner_connect_untyped(&callable, None); } /// Connect an untyped callable, with optional flags. /// - /// Used by [`ConnectBuilder::done()`]. Any other type-state (such as thread-local/sync, callable debug name, etc.) are baked into - /// `callable` and thus type-erased into runtime logic. - pub(super) fn inner_connect_untyped( - &mut self, - callable: &Callable, - flags: Option, - ) { + /// Used by [`inner_connect_godot_fn`] and `ConnectBuilder::connect_sync`. + pub(super) fn inner_connect_untyped(&self, callable: &Callable, flags: Option) { use crate::obj::EngineBitfield; let signal_name = self.name.as_ref(); - self.object.with_object_mut(|obj| { + self.object.to_owned_object().with_object_mut(|obj| { let mut c = obj.connect_ex(signal_name, callable); if let Some(flags) = flags { c = c.flags(flags.ord() as u32); @@ -213,22 +161,93 @@ impl<'c, C: WithSignals, Ps: meta::ParamTuple> TypedSignal<'c, C, Ps> { } } -impl TypedSignal<'_, C, Ps> { - /// Connect a method (member function) with `&mut self` as the first parameter. - /// - /// To connect to methods on other objects, use [`connect_obj()`][Self::connect_obj]. \ - /// If you need a `&self` receiver, cross-thread signals or connect flags, use [`connect_builder()`][Self::connect_builder]. - pub fn connect_self(&mut self, mut function: F) - where - for<'c_rcv> F: SignalReceiver<&'c_rcv mut C, Ps>, - { - let mut gd = self.receiver_object(); - let godot_fn = make_godot_fn(move |args| { - let mut instance = gd.bind_mut(); - let instance = &mut *instance; - function.call(instance, args); - }); +macro_rules! impl_signal_connect { + ($( $args:ident : $Ps:ident ),*) => { + // -------------------------------------------------------------------------------------------------------------------------------------- + // SignalReceiver + + impl + TypedSignal<'_, C, ($($Ps,)*)> { + /// Connect a non-member function (global function, associated function or closure). + /// + /// Example usages: + /// ```ignore + /// sig.connect(Self::static_func); + /// sig.connect(global_func); + /// sig.connect(|arg| { /* closure */ }); + /// ``` + /// + /// - To connect to a method on the object that owns this signal, use [`connect_self()`][Self::connect_self]. + /// - If you need [`connect flags`](ConnectFlags) or cross-thread signals, use [`connect_builder()`][Self::connect_builder]. + pub fn connect(&self, mut function: F) + where + F: FnMut($($Ps),*) -> R + 'static, + { + let godot_fn = make_godot_fn(move |($($args,)*):($($Ps,)*)| { + function($($args),*); + }); + + self.inner_connect_godot_fn::(godot_fn); + } - self.inner_connect_godot_fn::(godot_fn); - } + /// Connect a method (member function) with `&mut self` as the first parameter. + /// + /// - To connect to methods on other objects, use [`connect_other()`][Self::connect_other]. + /// - If you need [`connect flags`](ConnectFlags) or cross-thread signals, use [`connect_builder()`][Self::connect_builder]. + pub fn connect_self(&self, mut function: F) + where + F: FnMut(&mut C, $($Ps),*) -> R + 'static, + C: GodotDeref, + { + let mut gd = self.receiver_object(); + let godot_fn = make_godot_fn(move |($($args,)*):($($Ps,)*)| { + let mut target = C::get_mut(&mut gd); + let target_mut = target.deref_mut(); + function(target_mut, $($args),*); + }); + + self.inner_connect_godot_fn::(godot_fn); + } + + /// Connect a method (member function) with any `&mut OtherC` as the first parameter, where + /// `OtherC`: [`GodotClass`](GodotClass) (both user and engine classes are accepted). + /// + /// The parameter `object` can be of 2 different "categories": + /// - Any `&Gd` (e.g.: `&Gd`, `&Gd`). + /// - `&OtherC`, as long as `OtherC` is a user class that contains a `base` field (it implements the + /// [`WithBaseField`](WithBaseField) trait). + /// + /// --- + /// + /// - To connect to methods on the object that owns this signal, use [`connect_self()`][Self::connect_self]. + /// - If you need [`connect flags`](ConnectFlags) or cross-thread signals, use [`connect_builder()`][Self::connect_builder]. + pub fn connect_other(&self, object: &impl ToSignalObj, mut method: F) + where + F: FnMut(&mut OtherC, $($Ps),*) -> R + 'static, + OtherC: GodotDeref, + { + let mut gd = object.to_signal_obj(); + + let godot_fn = make_godot_fn(move |($($args,)*):($($Ps,)*)| { + let mut target = OtherC::get_mut(&mut gd); + let target_mut = target.deref_mut(); + method(target_mut, $($args),*); + }); + + self.inner_connect_godot_fn::(godot_fn); + } + } + }; } + +impl_signal_connect!(); +impl_signal_connect!(arg0: P0); +impl_signal_connect!(arg0: P0, arg1: P1); +impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2); +impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3); +impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4); +impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5); +impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6); +impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7); +impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8); +impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8, arg9: P9); diff --git a/godot-core/src/registry/signal/variadic.rs b/godot-core/src/registry/signal/variadic.rs deleted file mode 100644 index e493c645a..000000000 --- a/godot-core/src/registry/signal/variadic.rs +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) godot-rust; Bromeon and contributors. - * 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/. - */ - -//! Emulates variadic argument lists (via tuples), related to functions and signals. - -// https://geo-ant.github.io/blog/2021/rust-traits-and-variadic-functions -// -// Could be generalized with R return type, and not special-casing `self`. But keep simple until actually needed. - -/// Trait that is implemented for functions that can be connected to signals. -/// -// Direct RustDoc link doesn't work, for whatever reason again... -/// This is used in [`ConnectBuilder`](struct.ConnectBuilder.html). There are three variations of the `I` (instance) parameter: -/// - `()` for global and associated ("static") functions. -/// - `&C` for `&self` methods. -/// - `&mut C` for `&mut self` methods. -/// -/// See also [Signals](https://godot-rust.github.io/book/register/signals.html) in the book. -pub trait SignalReceiver: 'static { - /// Invoke the receiver on the given instance (possibly `()`) with `params`. - fn call(&mut self, maybe_instance: I, params: Ps); -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- -// Generated impls - -macro_rules! impl_signal_recipient { - ($( $args:ident : $Ps:ident ),*) => { - // -------------------------------------------------------------------------------------------------------------------------------------- - // SignalReceiver - - // Global and associated functions. - impl SignalReceiver<(), ( $($Ps,)* )> for F - where F: FnMut( $($Ps,)* ) -> R + 'static - { - fn call(&mut self, _no_instance: (), ($($args,)*): ( $($Ps,)* )) { - self($($args,)*); - } - } - - // Methods with mutable receiver - &mut self. - impl SignalReceiver<&mut C, ( $($Ps,)* )> for F - where F: FnMut( &mut C, $($Ps,)* ) -> R + 'static - { - fn call(&mut self, instance: &mut C, ($($args,)*): ( $($Ps,)* )) { - self(instance, $($args,)*); - } - } - - // Methods with immutable receiver - &self. - impl SignalReceiver<&C, ( $($Ps,)* )> for F - where F: FnMut( &C, $($Ps,)* ) -> R + 'static - { - fn call(&mut self, instance: &C, ($($args,)*): ( $($Ps,)* )) { - self(instance, $($args,)*); - } - } - }; -} - -impl_signal_recipient!(); -impl_signal_recipient!(arg0: P0); -impl_signal_recipient!(arg0: P0, arg1: P1); -impl_signal_recipient!(arg0: P0, arg1: P1, arg2: P2); -impl_signal_recipient!(arg0: P0, arg1: P1, arg2: P2, arg3: P3); -impl_signal_recipient!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4); -impl_signal_recipient!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5); -impl_signal_recipient!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6); -impl_signal_recipient!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7); -impl_signal_recipient!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8); -impl_signal_recipient!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8, arg9: P9); diff --git a/godot-macros/src/class/data_models/signal.rs b/godot-macros/src/class/data_models/signal.rs index 3912b4987..b79143638 100644 --- a/godot-macros/src/class/data_models/signal.rs +++ b/godot-macros/src/class/data_models/signal.rs @@ -524,7 +524,7 @@ fn make_with_signals_impl(class_name: &Ident, collection_struct_name: &Ident) -> type __SignalObj<'c> = ::godot::private::UserSignalObject<'c, Self>; #[doc(hidden)] - fn __signals_from_external(external: &mut ::godot::obj::Gd) -> Self::SignalCollection<'_, Self> { + fn __signals_from_external(external: & ::godot::obj::Gd) -> Self::SignalCollection<'_, Self> { Self::SignalCollection { __internal_obj: Some(::godot::private::UserSignalObject::External { gd: external.clone().upcast_object() diff --git a/itest/rust/src/builtin_tests/containers/signal_test.rs b/itest/rust/src/builtin_tests/containers/signal_test.rs index 75b0ffcb5..a715fb9d1 100644 --- a/itest/rust/src/builtin_tests/containers/signal_test.rs +++ b/itest/rust/src/builtin_tests/containers/signal_test.rs @@ -7,6 +7,7 @@ use crate::framework::itest; use godot::builtin::{GString, Signal, StringName}; +use godot::classes::object::ConnectFlags; use godot::classes::{Node, Node3D, Object, RefCounted}; use godot::meta::ToGodot; use godot::obj::{Base, Gd, InstanceId, NewAlloc, NewGd}; @@ -79,7 +80,7 @@ fn signal_symbols_internal() { #[cfg(since_api = "4.2")] #[itest] fn signal_symbols_external() { - let mut emitter = Emitter::new_alloc(); + let emitter = Emitter::new_alloc(); let mut sig = emitter.signals().signal_int(); // Local function; deliberately use a !Send type. @@ -96,7 +97,7 @@ fn signal_symbols_external() { // Connect to other object. let receiver = Receiver::new_alloc(); - sig.connect_obj(&receiver, Receiver::receive_int_mut); + sig.connect_other(&receiver, Receiver::receive_int_mut); // Emit signal (now via tuple). sig.emit_tuple((987,)); @@ -126,7 +127,7 @@ fn signal_symbols_external() { #[cfg(since_api = "4.2")] #[itest] fn signal_symbols_complex_emit() { - let mut emitter = Emitter::new_alloc(); + let emitter = Emitter::new_alloc(); let arg = emitter.clone(); let mut sig = emitter.signals().signal_obj(); @@ -148,35 +149,24 @@ fn signal_symbols_complex_emit() { #[cfg(since_api = "4.2")] #[itest] fn signal_symbols_external_builder() { - let mut emitter = Emitter::new_alloc(); + let emitter = Emitter::new_alloc(); let mut sig = emitter.signals().signal_int(); // Self-modifying method. - sig.connect_builder() - .object_self() - .method_mut(Emitter::self_receive) - .done(); + sig.connect_self(Emitter::self_receive); // Connect to other object. let receiver_mut = Receiver::new_alloc(); sig.connect_builder() - .object(&receiver_mut) - .method_mut(Receiver::receive_int_mut) - .done(); + .name("receive_the_knowledge") + .connect_other(&receiver_mut, Receiver::receive_int_mut); - // Connect to yet another object, immutable receiver. - let receiver_immut = Receiver::new_alloc(); - sig.connect_builder() - .object(&receiver_immut) - .method_immut(Receiver::receive_int) - .done(); + sig.connect_other(&receiver_mut, Receiver::receive_int_mut); let tracker = Rc::new(Cell::new(0)); { let tracker = tracker.clone(); - sig.connect_builder() - .function(move |i| tracker.set(i)) - .done(); + sig.connect_builder().connect(move |i| tracker.set(i)); } // Emit signal. @@ -192,13 +182,6 @@ fn signal_symbols_external_builder() { "Emit failed (mut method)" ); - // Check that *other* instance method is invoked. - assert_eq!( - receiver_immut.bind().last_received(), - LastReceived::Int(552), - "Emit failed (other object, immut method)" - ); - // Check that *other* instance method is invoked. assert_eq!( receiver_mut.bind().last_received(), @@ -209,7 +192,6 @@ fn signal_symbols_external_builder() { // Check that closures set up with builder are invoked. assert_eq!(tracker.get(), 552, "Emit failed (builder local)"); - receiver_immut.free(); receiver_mut.free(); emitter.free(); } @@ -219,16 +201,14 @@ fn signal_symbols_external_builder() { fn signal_symbols_sync() { use std::sync::{Arc, Mutex}; - let mut emitter = Emitter::new_alloc(); + let emitter = Emitter::new_alloc(); let mut sig = emitter.signals().signal_int(); let sync_tracker = Arc::new(Mutex::new(0)); { let sync_tracker = sync_tracker.clone(); sig.connect_builder() - .function(move |i| *sync_tracker.lock().unwrap() = i) - .sync() - .done(); + .connect_sync(move |i| *sync_tracker.lock().unwrap() = i); } sig.emit(1143); @@ -249,24 +229,21 @@ fn signal_symbols_engine(ctx: &crate::framework::TestContext) { ctx.scene_tree.clone().add_child(&node); // API allows to only modify one signal at a time (borrowing &mut self). - let mut renamed = node.signals().renamed(); + let renamed = node.signals().renamed(); let renamed_count = Rc::new(Cell::new(0)); { let renamed_count = renamed_count.clone(); renamed.connect(move || renamed_count.set(renamed_count.get() + 1)); } - let mut entered = node.signals().child_entered_tree(); + let entered = node.signals().child_entered_tree(); let entered_tracker = Rc::new(RefCell::new(None)); { let entered_tracker = entered_tracker.clone(); - entered - .connect_builder() - .function(move |node| { - *entered_tracker.borrow_mut() = Some(node); - }) - .done(); + entered.connect_builder().connect(move |node| { + *entered_tracker.borrow_mut() = Some(node); + }); } // Apply changes, triggering signals. @@ -300,8 +277,8 @@ fn signal_symbols_engine_inherited(ctx: &crate::framework::TestContext) { // Add to tree, so signals are propagated. ctx.scene_tree.clone().add_child(&node); - let mut sig = node.signals().renamed(); - sig.connect_self(|this: &mut Emitter| { + let sig = node.signals().renamed(); + sig.connect_self(|this| { this.last_received_int = 887; }); @@ -323,8 +300,8 @@ fn signal_symbols_engine_inherited_indirect(ctx: &crate::framework::TestContext) // Add to tree, so signals are propagated. ctx.scene_tree.clone().add_child(&node); - let mut sig = node.signals().renamed(); - sig.connect_obj(&original, |this: &mut Emitter| { + let sig = node.signals().renamed(); + sig.connect_other(&original, |this: &mut Emitter| { this.last_received_int = 887; }); @@ -349,6 +326,82 @@ fn signal_symbols_engine_inherited_internal() { node.free(); } +// Test that signal API methods accept engine types as receivers. +#[cfg(since_api = "4.2")] +#[itest] +fn signal_symbols_connect_engine() { + // No tree needed; signal is emitted manually. + let node = Emitter::new_alloc(); + let mut engine = Node::new_alloc(); + engine.set_name("hello"); + + node.signals() + .property_list_changed() + .connect_other(&engine, |this| { + assert_eq!(this.get_name(), StringName::from("hello")); + }); + + node.signals() + .property_list_changed() + .connect_builder() + .connect_other(&engine, |this| { + assert_eq!(this.get_name(), StringName::from("hello")); + }); + + node.signals().property_list_changed().emit(); + + node.free(); + engine.free(); +} + +// Test that rustc is capable of inferring the parameter types of closures passed to the signal API's connect methods. +#[cfg(since_api = "4.2")] +#[itest] +fn signal_symbols_connect_inferred() { + let user = Emitter::new_alloc(); + let engine = Node::new_alloc(); + + user.signals() + .child_entered_tree() + .connect_other(&engine, |this, mut child| { + // Use methods that `Node` declares. + let _ = this.get_path(); // ref. + this.set_unique_name_in_owner(true); // mut. + + // `child` is also a node. + let _ = child.get_path(); // ref. + child.set_unique_name_in_owner(true); // mut. + }); + + user.signals().renamed().connect_self(|this| { + // Use method/field that `Emitter` declares. + this.connect_base_signals_internal(); + let _ = this.last_received_int; + }); + + engine.signals().ready().connect_other(&user, |this| { + // Use method/field that `Emitter` declares. + this.connect_base_signals_internal(); + let _ = this.last_received_int; + }); + + engine + .signals() + .tree_exiting() + .connect_builder() + .flags(ConnectFlags::DEFERRED) + .connect_self(|this| { + // Use methods that `Node` declares. + let _ = this.get_path(); // ref. + this.set_unique_name_in_owner(true); // mut. + }); + + // Don't emit any signals, this test just needs to compile. + + user.free(); + engine.free(); +} + // Test that Node signals are accessible from a derived class, when the class itself has no #[signal] declarations. // Verifies the code path that only generates the traits, no dedicated signal collection. #[cfg(since_api = "4.2")] @@ -356,8 +409,8 @@ fn signal_symbols_engine_inherited_internal() { fn signal_symbols_engine_inherited_no_own_signals() { let mut obj = Receiver::new_alloc(); - let mut sig = obj.signals().property_list_changed(); - sig.connect_self(|this: &mut Receiver| { + let sig = obj.signals().property_list_changed(); + sig.connect_self(|this| { this.receive_int(941); }); @@ -445,7 +498,7 @@ mod emitter { #[cfg(since_api = "4.2")] pub fn connect_signals_internal(&mut self, tracker: Rc>) { - let mut sig = self.signals().signal_int(); + let sig = self.signals().signal_int(); sig.connect_self(Self::self_receive); sig.connect(Self::self_receive_static); sig.connect(move |i| tracker.set(i)); @@ -460,7 +513,7 @@ mod emitter { pub fn connect_base_signals_internal(&mut self) { self.signals() .renamed() - .connect_self(Self::self_receive_constant); + .connect_self(Emitter::self_receive_constant); } #[cfg(since_api = "4.2")] diff --git a/itest/rust/src/engine_tests/async_test.rs b/itest/rust/src/engine_tests/async_test.rs index 3c8fce580..3b4979453 100644 --- a/itest/rust/src/engine_tests/async_test.rs +++ b/itest/rust/src/engine_tests/async_test.rs @@ -204,8 +204,8 @@ fn resolver_callabable_equality() { #[itest(async)] fn async_typed_signal() -> TaskHandle { - let mut object = AsyncRefCounted::new_gd(); - let mut copy = object.clone(); + let object = AsyncRefCounted::new_gd(); + let copy = object.clone(); let task_handle = task::spawn(async move { let (result,) = copy.signals().custom_signal().deref().await; @@ -220,8 +220,8 @@ fn async_typed_signal() -> TaskHandle { #[itest(async)] fn async_typed_signal_with_array() -> TaskHandle { - let mut object = AsyncRefCounted::new_gd(); - let mut copy = object.clone(); + let object = AsyncRefCounted::new_gd(); + let copy = object.clone(); let task_handle = task::spawn(async move { let (result,) = copy.signals().custom_signal_array().to_future().await;