Skip to content

Commit

Permalink
Integrate connect/emit with Rust named function pointers
Browse files Browse the repository at this point in the history
  • Loading branch information
Bromeon committed Jan 6, 2025
1 parent 8cde581 commit 223a47c
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 99 deletions.
3 changes: 0 additions & 3 deletions godot-core/src/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ pub mod __prelude_reexport {
pub use string::{GString, NodePath, StringName};
pub use transform2d::*;
pub use transform3d::*;
// TODO move to register?
pub use typed_signal::{Func, TypedSignal};
pub use variant::*;
pub use vectors::*;

Expand Down Expand Up @@ -113,7 +111,6 @@ mod signal;
mod string;
mod transform2d;
mod transform3d;
mod typed_signal;
mod variant;
mod vectors;

Expand Down
2 changes: 1 addition & 1 deletion godot-core/src/classes/class_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub(crate) fn display_string<T: GodotClass>(
obj: &Gd<T>,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let string: GString = obj.raw.as_object().to_string();
let string: GString = obj.raw.as_object_ref().to_string();
<GString as std::fmt::Display>::fmt(&string, f)
}

Expand Down
5 changes: 5 additions & 0 deletions godot-core/src/obj/gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,11 @@ impl<T: GodotClass> Gd<T> {
.expect("Upcast to Object failed. This is a bug; please report it.")
}

/// Equivalent to [`upcast_mut::<Object>()`][Self::upcast_mut], but without bounds.
pub(crate) fn upcast_object_mut(&mut self) -> &mut classes::Object {
self.raw.as_object_mut()
}

/// **Upcast shared-ref:** access this object as a shared reference to a base class.
///
/// This is semantically equivalent to multiple applications of [`Self::deref()`]. Not really useful on its own, but combined with
Expand Down
7 changes: 6 additions & 1 deletion godot-core/src/obj/raw_gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,16 @@ impl<T: GodotClass> RawGd<T> {
// self.as_target_mut()
// }

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

pub(crate) fn as_object_mut(&mut self) -> &mut classes::Object {
// SAFETY: Object is always a valid upcast target.
unsafe { self.as_upcast_mut() }
}

/// # Panics
/// If this `RawGd` is null. In Debug mode, sanity checks (valid upcast, ID comparisons) can also lead to panics.
///
Expand Down
2 changes: 0 additions & 2 deletions godot-core/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ pub use sys::out;
#[cfg(feature = "trace")]
pub use crate::meta::trace;

use crate::builtin::Variant;
use crate::global::godot_error;
use crate::meta::error::CallError;
use crate::meta::CallContext;
use crate::obj::{bounds, BaseMut, GodotClass, Inherits};
use crate::sys;
use std::sync::atomic;
#[cfg(debug_assertions)]
Expand Down
46 changes: 46 additions & 0 deletions godot-core/src/registry/as_func.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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/.
*/

// 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.

pub trait AsFunc<I, Ps> {
fn call(&mut self, maybe_instance: I, params: Ps);
}

// pub trait AsMethod<C, Ps> {
// fn call(&mut self, instance: &mut C, params: Ps);
// }

// Now generalize via macro:
macro_rules! impl_signal_recipient {
($( $args:ident : $Ps:ident ),*) => {
// Global and associated functions.
impl<F, R, $($Ps,)*> AsFunc<(), ( $($Ps,)* )> for F
where F: FnMut( $($Ps,)* ) -> R
{
fn call(&mut self, _no_instance: (), ($($args,)*): ( $($Ps,)* )) {
self($($args,)*);
}
}

// Methods.
impl<F, R, C, $($Ps,)*> AsFunc<&mut C, ( $($Ps,)* )> for F
where F: FnMut( &mut C, $($Ps,)* ) -> R
{
fn call(&mut self, instance: &mut 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);
2 changes: 2 additions & 0 deletions godot-core/src/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
// Note: final re-exports from godot-core are in lib.rs, mod register::private.
// These are public here for simplicity, but many are not imported by the main crate.

pub mod as_func;
pub mod callbacks;
pub mod class;
pub mod constant;
pub mod method;
pub mod plugin;
pub mod property;
pub mod typed_signal;

// RpcConfig uses MultiplayerPeer::TransferMode and MultiplayerApi::RpcMode, which are only enabled in `codegen-full` feature.
#[cfg(feature = "codegen-full")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,163 @@

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

use crate::builtin::{Callable, Signal, Variant};
use crate::obj::Gd;
use crate::builtin::{Callable, Variant};
use crate::obj::{Gd, GodotClass, WithBaseField};
use crate::registry::as_func::AsFunc;
use crate::{classes, meta, sys};
use std::borrow::Cow;
use std::fmt;

pub trait ParamTuple {
fn to_variant_array(&self) -> Vec<Variant>;
fn from_variant_array(array: &[&Variant]) -> Self;
}

impl ParamTuple for () {
fn to_variant_array(&self) -> Vec<Variant> {
Vec::new()
}
macro_rules! impl_param_tuple {
// Recursive case for tuple with N elements
($($args:ident : $Ps:ident),*) => {
impl<$($Ps),*> ParamTuple for ($($Ps,)*)
where
$($Ps: meta::ToGodot + meta::FromGodot),*
{
fn to_variant_array(&self) -> Vec<Variant> {
let ($($args,)*) = self;

vec![
$( $args.to_variant(), )*
]
}

#[allow(unused_variables, unused_mut)]
fn from_variant_array(array: &[&Variant]) -> Self {
let mut iter = array.iter();
( $(
<$Ps>::from_variant(
iter.next().unwrap_or_else(|| panic!("ParamTuple: {} access out-of-bounds (len {})", stringify!($args), array.len()))
),
)* )
}
}
};
}

impl<T> ParamTuple for (T,)
impl_param_tuple!();
impl_param_tuple!(arg0: P0);
impl_param_tuple!(arg0: P0, arg1: P1);
impl_param_tuple!(arg0: P0, arg1: P1, arg2: P2);

// ----------------------------------------------------------------------------------------------------------------------------------------------

#[doc(hidden)]
pub enum ObjectRef<'a, C: GodotClass> {
/// Helpful for emit: reuse `&self` from within the `impl` block, goes through `base()` re-borrowing and thus allows re-entrant calls
/// through Godot.
Internal { obj_mut: &'a mut C },

/// From outside, based on `Gd` pointer.
External { gd: Gd<C> },
}

impl<C> ObjectRef<'_, C>
where
T: meta::ToGodot,
C: WithBaseField,
{
fn to_variant_array(&self) -> Vec<Variant> {
vec![self.0.to_variant()]
fn with_object_mut(&mut self, f: impl FnOnce(&mut classes::Object)) {
match self {
ObjectRef::Internal { obj_mut } => f(obj_mut.base_mut().upcast_object_mut()),
ObjectRef::External { gd } => f(gd.upcast_object_mut()),
}
}

fn to_owned(&self) -> Gd<C> {
match self {
ObjectRef::Internal { obj_mut } => WithBaseField::to_gd(*obj_mut),
ObjectRef::External { gd } => gd.clone(),
}
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

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

impl<Ps: ParamTuple> TypedSignal<Ps> {
pub(crate) fn from_untyped(signal: Signal) -> Self {
impl<'a, C: WithBaseField, Ps: ParamTuple> TypedSignal<'a, C, Ps> {
#[doc(hidden)]
pub fn new(owner: ObjectRef<'a, C>, name: &'static str) -> Self {
Self {
signal,
owner,
name: Cow::Borrowed(name),
_signature: std::marker::PhantomData,
}
}

pub fn emit(&self, params: Ps) {
self.signal.emit(&params.to_variant_array());
pub fn emit(&mut self, params: Ps) {
let name = self.name.as_ref();

self.owner.with_object_mut(|obj| {
obj.emit_signal(name, &params.to_variant_array());
});
}

pub fn connect_untyped(&mut self, callable: &Callable, flags: i64) {
self.signal.connect(callable, flags);
/// Connect a method (member function) with `&mut self` as the first parameter.
pub fn connect_self<F>(&mut self, mut function: F)
where
for<'c> F: AsFunc<&'c mut C, Ps> + 'static,
{
// 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.
let callable_name = std::any::type_name_of_val(&function);

let object = self.owner.to_owned();
let godot_fn = move |variant_args: &[&Variant]| -> Result<Variant, ()> {
let args = Ps::from_variant_array(variant_args);

// let mut function = function;
// function.call(instance, args);
let mut object = object.clone();

// TODO: how to avoid another bind, when emitting directly from Rust?
let mut instance = object.bind_mut();
let instance = &mut *instance;
function.call(instance, args);

Ok(Variant::nil())
};

let name = self.name.as_ref();
let callable = Callable::from_local_fn(callable_name, godot_fn);

self.owner.with_object_mut(|obj| {
obj.connect(name, &callable);
});
}

pub fn to_untyped(&self) -> Signal {
self.signal.clone()
/// Connect a static function (global or associated function).
pub fn connect<F>(&mut self, mut function: F)
where
F: AsFunc<(), Ps> + 'static,
{
let callable_name = std::any::type_name_of_val(&function);

let godot_fn = move |variant_args: &[&Variant]| -> Result<Variant, ()> {
let args = Ps::from_variant_array(variant_args);
function.call((), args);

Ok(Variant::nil())
};

let name = self.name.as_ref();
let callable = Callable::from_local_fn(callable_name, godot_fn);

self.owner.with_object_mut(|obj| {
obj.connect(name, &callable);
});
}
}

Expand Down
8 changes: 4 additions & 4 deletions godot-macros/src/class/data_models/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,16 +193,16 @@ pub fn make_func_collection(
static_collection_methods.push(quote! {
#(#cfg_attrs)*
// Use `&self` here to enable `.` chaining, such as in MyClass::static_funcs().my_func().
fn #rust_func_name(self) -> ::godot::builtin::Func<#generic_args> {
fn #rust_func_name(self) -> ::godot::register::Func<#generic_args> {
let class_name = <#class_name as ::godot::obj::GodotClass>::class_name();
::godot::builtin::Func::from_static_function(class_name.to_cow_str(), #godot_func_name)
::godot::register::Func::from_static_function(class_name.to_cow_str(), #godot_func_name)
}
});
} else {
instance_collection_methods.push(quote! {
#(#cfg_attrs)*
fn #rust_func_name(self) -> ::godot::builtin::Func<#generic_args> {
::godot::builtin::Func::from_instance_method(self.obj, #godot_func_name)
fn #rust_func_name(self) -> ::godot::register::Func<#generic_args> {
::godot::register::Func::from_instance_method(self.obj, #godot_func_name)
}
});
}
Expand Down
Loading

0 comments on commit 223a47c

Please sign in to comment.