Skip to content

Commit 223a47c

Browse files
committed
Integrate connect/emit with Rust named function pointers
1 parent 8cde581 commit 223a47c

File tree

12 files changed

+245
-99
lines changed

12 files changed

+245
-99
lines changed

godot-core/src/builtin/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ pub mod __prelude_reexport {
6363
pub use string::{GString, NodePath, StringName};
6464
pub use transform2d::*;
6565
pub use transform3d::*;
66-
// TODO move to register?
67-
pub use typed_signal::{Func, TypedSignal};
6866
pub use variant::*;
6967
pub use vectors::*;
7068

@@ -113,7 +111,6 @@ mod signal;
113111
mod string;
114112
mod transform2d;
115113
mod transform3d;
116-
mod typed_signal;
117114
mod variant;
118115
mod vectors;
119116

godot-core/src/classes/class_runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub(crate) fn display_string<T: GodotClass>(
4747
obj: &Gd<T>,
4848
f: &mut std::fmt::Formatter<'_>,
4949
) -> std::fmt::Result {
50-
let string: GString = obj.raw.as_object().to_string();
50+
let string: GString = obj.raw.as_object_ref().to_string();
5151
<GString as std::fmt::Display>::fmt(&string, f)
5252
}
5353

godot-core/src/obj/gd.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,11 @@ impl<T: GodotClass> Gd<T> {
327327
.expect("Upcast to Object failed. This is a bug; please report it.")
328328
}
329329

330+
/// Equivalent to [`upcast_mut::<Object>()`][Self::upcast_mut], but without bounds.
331+
pub(crate) fn upcast_object_mut(&mut self) -> &mut classes::Object {
332+
self.raw.as_object_mut()
333+
}
334+
330335
/// **Upcast shared-ref:** access this object as a shared reference to a base class.
331336
///
332337
/// This is semantically equivalent to multiple applications of [`Self::deref()`]. Not really useful on its own, but combined with

godot-core/src/obj/raw_gd.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,16 @@ impl<T: GodotClass> RawGd<T> {
207207
// self.as_target_mut()
208208
// }
209209

210-
pub(crate) fn as_object(&self) -> &classes::Object {
210+
pub(crate) fn as_object_ref(&self) -> &classes::Object {
211211
// SAFETY: Object is always a valid upcast target.
212212
unsafe { self.as_upcast_ref() }
213213
}
214214

215+
pub(crate) fn as_object_mut(&mut self) -> &mut classes::Object {
216+
// SAFETY: Object is always a valid upcast target.
217+
unsafe { self.as_upcast_mut() }
218+
}
219+
215220
/// # Panics
216221
/// If this `RawGd` is null. In Debug mode, sanity checks (valid upcast, ID comparisons) can also lead to panics.
217222
///

godot-core/src/private.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ pub use sys::out;
1717
#[cfg(feature = "trace")]
1818
pub use crate::meta::trace;
1919

20-
use crate::builtin::Variant;
2120
use crate::global::godot_error;
2221
use crate::meta::error::CallError;
2322
use crate::meta::CallContext;
24-
use crate::obj::{bounds, BaseMut, GodotClass, Inherits};
2523
use crate::sys;
2624
use std::sync::atomic;
2725
#[cfg(debug_assertions)]

godot-core/src/registry/as_func.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
// https://geo-ant.github.io/blog/2021/rust-traits-and-variadic-functions
9+
//
10+
// Could be generalized with R return type, and not special-casing `self`. But keep simple until actually needed.
11+
12+
pub trait AsFunc<I, Ps> {
13+
fn call(&mut self, maybe_instance: I, params: Ps);
14+
}
15+
16+
// pub trait AsMethod<C, Ps> {
17+
// fn call(&mut self, instance: &mut C, params: Ps);
18+
// }
19+
20+
// Now generalize via macro:
21+
macro_rules! impl_signal_recipient {
22+
($( $args:ident : $Ps:ident ),*) => {
23+
// Global and associated functions.
24+
impl<F, R, $($Ps,)*> AsFunc<(), ( $($Ps,)* )> for F
25+
where F: FnMut( $($Ps,)* ) -> R
26+
{
27+
fn call(&mut self, _no_instance: (), ($($args,)*): ( $($Ps,)* )) {
28+
self($($args,)*);
29+
}
30+
}
31+
32+
// Methods.
33+
impl<F, R, C, $($Ps,)*> AsFunc<&mut C, ( $($Ps,)* )> for F
34+
where F: FnMut( &mut C, $($Ps,)* ) -> R
35+
{
36+
fn call(&mut self, instance: &mut C, ($($args,)*): ( $($Ps,)* )) {
37+
self(instance, $($args,)*);
38+
}
39+
}
40+
};
41+
}
42+
43+
impl_signal_recipient!();
44+
impl_signal_recipient!(arg0: P0);
45+
impl_signal_recipient!(arg0: P0, arg1: P1);
46+
impl_signal_recipient!(arg0: P0, arg1: P1, arg2: P2);

godot-core/src/registry/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
// Note: final re-exports from godot-core are in lib.rs, mod register::private.
99
// These are public here for simplicity, but many are not imported by the main crate.
1010

11+
pub mod as_func;
1112
pub mod callbacks;
1213
pub mod class;
1314
pub mod constant;
1415
pub mod method;
1516
pub mod plugin;
1617
pub mod property;
18+
pub mod typed_signal;
1719

1820
// RpcConfig uses MultiplayerPeer::TransferMode and MultiplayerApi::RpcMode, which are only enabled in `codegen-full` feature.
1921
#[cfg(feature = "codegen-full")]

godot-core/src/builtin/typed_signal.rs renamed to godot-core/src/registry/typed_signal.rs

Lines changed: 128 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,163 @@
77

88
// Maybe move this to builtin::functional module?
99

10-
use crate::builtin::{Callable, Signal, Variant};
11-
use crate::obj::Gd;
10+
use crate::builtin::{Callable, Variant};
11+
use crate::obj::{Gd, GodotClass, WithBaseField};
12+
use crate::registry::as_func::AsFunc;
1213
use crate::{classes, meta, sys};
1314
use std::borrow::Cow;
1415
use std::fmt;
1516

1617
pub trait ParamTuple {
1718
fn to_variant_array(&self) -> Vec<Variant>;
19+
fn from_variant_array(array: &[&Variant]) -> Self;
1820
}
1921

20-
impl ParamTuple for () {
21-
fn to_variant_array(&self) -> Vec<Variant> {
22-
Vec::new()
23-
}
22+
macro_rules! impl_param_tuple {
23+
// Recursive case for tuple with N elements
24+
($($args:ident : $Ps:ident),*) => {
25+
impl<$($Ps),*> ParamTuple for ($($Ps,)*)
26+
where
27+
$($Ps: meta::ToGodot + meta::FromGodot),*
28+
{
29+
fn to_variant_array(&self) -> Vec<Variant> {
30+
let ($($args,)*) = self;
31+
32+
vec![
33+
$( $args.to_variant(), )*
34+
]
35+
}
36+
37+
#[allow(unused_variables, unused_mut)]
38+
fn from_variant_array(array: &[&Variant]) -> Self {
39+
let mut iter = array.iter();
40+
( $(
41+
<$Ps>::from_variant(
42+
iter.next().unwrap_or_else(|| panic!("ParamTuple: {} access out-of-bounds (len {})", stringify!($args), array.len()))
43+
),
44+
)* )
45+
}
46+
}
47+
};
2448
}
2549

26-
impl<T> ParamTuple for (T,)
50+
impl_param_tuple!();
51+
impl_param_tuple!(arg0: P0);
52+
impl_param_tuple!(arg0: P0, arg1: P1);
53+
impl_param_tuple!(arg0: P0, arg1: P1, arg2: P2);
54+
55+
// ----------------------------------------------------------------------------------------------------------------------------------------------
56+
57+
#[doc(hidden)]
58+
pub enum ObjectRef<'a, C: GodotClass> {
59+
/// Helpful for emit: reuse `&self` from within the `impl` block, goes through `base()` re-borrowing and thus allows re-entrant calls
60+
/// through Godot.
61+
Internal { obj_mut: &'a mut C },
62+
63+
/// From outside, based on `Gd` pointer.
64+
External { gd: Gd<C> },
65+
}
66+
67+
impl<C> ObjectRef<'_, C>
2768
where
28-
T: meta::ToGodot,
69+
C: WithBaseField,
2970
{
30-
fn to_variant_array(&self) -> Vec<Variant> {
31-
vec![self.0.to_variant()]
71+
fn with_object_mut(&mut self, f: impl FnOnce(&mut classes::Object)) {
72+
match self {
73+
ObjectRef::Internal { obj_mut } => f(obj_mut.base_mut().upcast_object_mut()),
74+
ObjectRef::External { gd } => f(gd.upcast_object_mut()),
75+
}
76+
}
77+
78+
fn to_owned(&self) -> Gd<C> {
79+
match self {
80+
ObjectRef::Internal { obj_mut } => WithBaseField::to_gd(*obj_mut),
81+
ObjectRef::External { gd } => gd.clone(),
82+
}
3283
}
3384
}
3485

3586
// ----------------------------------------------------------------------------------------------------------------------------------------------
3687

37-
pub struct TypedSignal<Ps> {
38-
signal: Signal,
88+
pub struct TypedSignal<'a, C: GodotClass, Ps> {
89+
//signal: Signal,
90+
/// In Godot, valid signals (unlike funcs) are _always_ declared in a class and become part of each instance. So there's always an object.
91+
owner: ObjectRef<'a, C>,
92+
name: Cow<'static, str>,
3993
_signature: std::marker::PhantomData<Ps>,
4094
}
4195

42-
impl<Ps: ParamTuple> TypedSignal<Ps> {
43-
pub(crate) fn from_untyped(signal: Signal) -> Self {
96+
impl<'a, C: WithBaseField, Ps: ParamTuple> TypedSignal<'a, C, Ps> {
97+
#[doc(hidden)]
98+
pub fn new(owner: ObjectRef<'a, C>, name: &'static str) -> Self {
4499
Self {
45-
signal,
100+
owner,
101+
name: Cow::Borrowed(name),
46102
_signature: std::marker::PhantomData,
47103
}
48104
}
49105

50-
pub fn emit(&self, params: Ps) {
51-
self.signal.emit(&params.to_variant_array());
106+
pub fn emit(&mut self, params: Ps) {
107+
let name = self.name.as_ref();
108+
109+
self.owner.with_object_mut(|obj| {
110+
obj.emit_signal(name, &params.to_variant_array());
111+
});
52112
}
53113

54-
pub fn connect_untyped(&mut self, callable: &Callable, flags: i64) {
55-
self.signal.connect(callable, flags);
114+
/// Connect a method (member function) with `&mut self` as the first parameter.
115+
pub fn connect_self<F>(&mut self, mut function: F)
116+
where
117+
for<'c> F: AsFunc<&'c mut C, Ps> + 'static,
118+
{
119+
// When using sys::short_type_name() in the future, make sure global "func" and member "MyClass::func" are rendered as such.
120+
// PascalCase heuristic should then be good enough.
121+
let callable_name = std::any::type_name_of_val(&function);
122+
123+
let object = self.owner.to_owned();
124+
let godot_fn = move |variant_args: &[&Variant]| -> Result<Variant, ()> {
125+
let args = Ps::from_variant_array(variant_args);
126+
127+
// let mut function = function;
128+
// function.call(instance, args);
129+
let mut object = object.clone();
130+
131+
// TODO: how to avoid another bind, when emitting directly from Rust?
132+
let mut instance = object.bind_mut();
133+
let instance = &mut *instance;
134+
function.call(instance, args);
135+
136+
Ok(Variant::nil())
137+
};
138+
139+
let name = self.name.as_ref();
140+
let callable = Callable::from_local_fn(callable_name, godot_fn);
141+
142+
self.owner.with_object_mut(|obj| {
143+
obj.connect(name, &callable);
144+
});
56145
}
57146

58-
pub fn to_untyped(&self) -> Signal {
59-
self.signal.clone()
147+
/// Connect a static function (global or associated function).
148+
pub fn connect<F>(&mut self, mut function: F)
149+
where
150+
F: AsFunc<(), Ps> + 'static,
151+
{
152+
let callable_name = std::any::type_name_of_val(&function);
153+
154+
let godot_fn = move |variant_args: &[&Variant]| -> Result<Variant, ()> {
155+
let args = Ps::from_variant_array(variant_args);
156+
function.call((), args);
157+
158+
Ok(Variant::nil())
159+
};
160+
161+
let name = self.name.as_ref();
162+
let callable = Callable::from_local_fn(callable_name, godot_fn);
163+
164+
self.owner.with_object_mut(|obj| {
165+
obj.connect(name, &callable);
166+
});
60167
}
61168
}
62169

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,16 +193,16 @@ pub fn make_func_collection(
193193
static_collection_methods.push(quote! {
194194
#(#cfg_attrs)*
195195
// Use `&self` here to enable `.` chaining, such as in MyClass::static_funcs().my_func().
196-
fn #rust_func_name(self) -> ::godot::builtin::Func<#generic_args> {
196+
fn #rust_func_name(self) -> ::godot::register::Func<#generic_args> {
197197
let class_name = <#class_name as ::godot::obj::GodotClass>::class_name();
198-
::godot::builtin::Func::from_static_function(class_name.to_cow_str(), #godot_func_name)
198+
::godot::register::Func::from_static_function(class_name.to_cow_str(), #godot_func_name)
199199
}
200200
});
201201
} else {
202202
instance_collection_methods.push(quote! {
203203
#(#cfg_attrs)*
204-
fn #rust_func_name(self) -> ::godot::builtin::Func<#generic_args> {
205-
::godot::builtin::Func::from_instance_method(self.obj, #godot_func_name)
204+
fn #rust_func_name(self) -> ::godot::register::Func<#generic_args> {
205+
::godot::register::Func::from_instance_method(self.obj, #godot_func_name)
206206
}
207207
});
208208
}

0 commit comments

Comments
 (0)