Skip to content

Commit e7cafaa

Browse files
authored
Merge pull request #418 from godot-rust/feature/custom-callables
Callables for custom Rust functions
2 parents a538aca + 695dae1 commit e7cafaa

File tree

7 files changed

+495
-27
lines changed

7 files changed

+495
-27
lines changed

godot-core/src/builtin/array.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ impl<T: VariantMetadata> Array<T> {
178178
interface_fn!(array_operator_index_const)(self.sys(), index)
179179
};
180180

181+
// Signature is wrong in GDExtension, semantically this is a const ptr
182+
let variant_ptr = sys::to_const_ptr(variant_ptr);
181183
Variant::ptr_from_sys(variant_ptr)
182184
}
183185

godot-core/src/builtin/callable.rs

Lines changed: 226 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@
66

77
use godot_ffi as sys;
88

9-
use crate::builtin::{inner, ToVariant, Variant};
9+
use crate::builtin::{inner, StringName, ToVariant, Variant, VariantArray};
1010
use crate::engine::Object;
1111
use crate::obj::mem::Memory;
1212
use crate::obj::{Gd, GodotClass, InstanceId};
13-
use std::fmt;
13+
use std::{fmt, ptr};
1414
use sys::{ffi_methods, GodotFfi};
1515

16-
use super::{StringName, VariantArray};
17-
1816
/// A `Callable` represents a function in Godot.
1917
///
2018
/// Usually a callable is a reference to an `Object` and a method name, this is a standard callable. But can
@@ -53,14 +51,96 @@ impl Callable {
5351
}
5452
}
5553

54+
/// Create a callable from a Rust function or closure.
55+
///
56+
/// `name` is used for the string representation of the closure, which helps debugging.
57+
///
58+
/// Callables created through multiple `from_fn()` calls are never equal, even if they refer to the same function. If you want to use
59+
/// equality, either clone an existing `Callable` instance, or define your own `PartialEq` impl with [`Callable::from_custom`].
60+
///
61+
/// # Example
62+
/// ```no_run
63+
/// # use godot::prelude::*;
64+
/// let callable = Callable::from_fn("sum", |args: &[&Variant]| {
65+
/// let sum: i32 = args.iter().map(|arg| arg.to::<i32>()).sum();
66+
/// Ok(sum.to_variant())
67+
/// });
68+
/// ```
69+
#[cfg(since_api = "4.2")]
70+
pub fn from_fn<F, S>(name: S, rust_function: F) -> Self
71+
where
72+
F: 'static + Send + Sync + FnMut(&[&Variant]) -> Result<Variant, ()>,
73+
S: Into<crate::builtin::GodotString>,
74+
{
75+
let userdata = CallableUserdata {
76+
inner: FnWrapper {
77+
rust_function,
78+
name: name.into(),
79+
},
80+
};
81+
82+
let info = sys::GDExtensionCallableCustomInfo {
83+
callable_userdata: Box::into_raw(Box::new(userdata)) as *mut std::ffi::c_void,
84+
token: ptr::null_mut(),
85+
object: ptr::null_mut(),
86+
call_func: Some(rust_callable_call_fn::<F>),
87+
is_valid_func: None, // could be customized, but no real use case yet.
88+
free_func: Some(rust_callable_destroy::<FnWrapper<F>>),
89+
hash_func: None,
90+
equal_func: None,
91+
// Op < is only used in niche scenarios and default is usually good enough, see https://github.com/godotengine/godot/issues/81901.
92+
less_than_func: None,
93+
to_string_func: Some(rust_callable_to_string_named::<F>),
94+
};
95+
96+
Self::from_custom_info(info)
97+
}
98+
99+
/// Create a highly configurable callable from Rust.
100+
///
101+
/// See [`RustCallable`] for requirements on the type.
102+
#[cfg(since_api = "4.2")]
103+
pub fn from_custom<C: RustCallable>(callable: C) -> Self {
104+
// Could theoretically use `dyn` but would need:
105+
// - double boxing
106+
// - a type-erased workaround for PartialEq supertrait (which has a `Self` type parameter and thus is not object-safe)
107+
let userdata = CallableUserdata { inner: callable };
108+
109+
let info = sys::GDExtensionCallableCustomInfo {
110+
callable_userdata: Box::into_raw(Box::new(userdata)) as *mut std::ffi::c_void,
111+
token: ptr::null_mut(),
112+
object: ptr::null_mut(),
113+
call_func: Some(rust_callable_call_custom::<C>),
114+
is_valid_func: None, // could be customized, but no real use case yet.
115+
free_func: Some(rust_callable_destroy::<C>),
116+
hash_func: Some(rust_callable_hash::<C>),
117+
equal_func: Some(rust_callable_equal::<C>),
118+
// Op < is only used in niche scenarios and default is usually good enough, see https://github.com/godotengine/godot/issues/81901.
119+
less_than_func: None,
120+
to_string_func: Some(rust_callable_to_string_display::<C>),
121+
};
122+
123+
Self::from_custom_info(info)
124+
}
125+
126+
#[cfg(since_api = "4.2")]
127+
fn from_custom_info(mut info: sys::GDExtensionCallableCustomInfo) -> Callable {
128+
// SAFETY: callable_custom_create() is a valid way of creating callables.
129+
unsafe {
130+
Callable::from_sys_init(|type_ptr| {
131+
sys::interface_fn!(callable_custom_create)(type_ptr, ptr::addr_of_mut!(info))
132+
})
133+
}
134+
}
135+
56136
/// Creates an invalid/empty object that is not able to be called.
57137
///
58138
/// _Godot equivalent: `Callable()`_
59139
pub fn invalid() -> Self {
60140
unsafe {
61141
Self::from_sys_init(|self_ptr| {
62-
let ctor = ::godot_ffi::builtin_fn!(callable_construct_default);
63-
ctor(self_ptr, std::ptr::null_mut())
142+
let ctor = sys::builtin_fn!(callable_construct_default);
143+
ctor(self_ptr, ptr::null_mut())
64144
})
65145
}
66146
}
@@ -177,12 +257,9 @@ impl_builtin_traits! {
177257
// Currently no Default::default() to encourage explicit valid initialization.
178258
//Default => callable_construct_default;
179259

180-
// Equality for custom callables depend on the equality implementation of that custom callable. This
181-
// is from what i can tell currently implemented as total equality in all cases, but i dont believe
182-
// there are any guarantees that all implementations of equality for custom callables will be.
183-
//
184-
// So we cannot implement `Eq` here and be confident equality will be total for all future custom
185-
// callables.
260+
// Equality for custom callables depend on the equality implementation of that custom callable.
261+
// So we cannot implement `Eq` here and be confident equality will be total for all future custom callables.
262+
// Godot does not define a less-than operator, so there is no `PartialOrd`.
186263
PartialEq => callable_operator_equal;
187264
Clone => callable_construct_copy;
188265
Drop => callable_destroy;
@@ -202,7 +279,7 @@ unsafe impl GodotFfi for Callable {
202279
}
203280
}
204281

205-
impl std::fmt::Debug for Callable {
282+
impl fmt::Debug for Callable {
206283
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207284
let method = self.method_name();
208285
let object = self.object();
@@ -214,8 +291,143 @@ impl std::fmt::Debug for Callable {
214291
}
215292
}
216293

217-
impl std::fmt::Display for Callable {
294+
impl fmt::Display for Callable {
218295
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219296
write!(f, "{}", self.to_variant())
220297
}
221298
}
299+
300+
// ----------------------------------------------------------------------------------------------------------------------------------------------
301+
// Callbacks for custom implementations
302+
303+
#[cfg(since_api = "4.2")]
304+
use custom_callable::*;
305+
306+
#[cfg(since_api = "4.2")]
307+
pub use custom_callable::RustCallable;
308+
309+
#[cfg(since_api = "4.2")]
310+
mod custom_callable {
311+
use super::*;
312+
use crate::builtin::GodotString;
313+
use std::hash::Hash;
314+
315+
pub struct CallableUserdata<T> {
316+
pub inner: T,
317+
}
318+
319+
impl<T> CallableUserdata<T> {
320+
/// # Safety
321+
/// Returns an unbounded reference. `void_ptr` must be a valid pointer to a `CallableUserdata`.
322+
unsafe fn inner_from_raw<'a>(void_ptr: *mut std::ffi::c_void) -> &'a mut T {
323+
let ptr = void_ptr as *mut CallableUserdata<T>;
324+
&mut (*ptr).inner
325+
}
326+
}
327+
328+
pub(crate) struct FnWrapper<F> {
329+
pub(crate) rust_function: F,
330+
pub(crate) name: GodotString,
331+
}
332+
333+
/// Represents a custom callable object defined in Rust.
334+
///
335+
/// This trait has a single method, `invoke`, which is called upon invocation.
336+
///
337+
/// Since callables can be invoked from anywhere, they must be self-contained (`'static`) and thread-safe (`Send + Sync`).
338+
/// They also should implement `Display` for the Godot string representation.
339+
/// Furthermore, `PartialEq` and `Hash` are required for equality checks and usage as a key in a `Dictionary`.
340+
pub trait RustCallable: 'static + PartialEq + Hash + fmt::Display + Send + Sync {
341+
/// Invokes the callable with the given arguments as `Variant` references.
342+
///
343+
/// Return `Ok(...)` if the call succeeded, and `Err(())` otherwise.
344+
/// Error handling is mostly needed in case argument number or types mismatch.
345+
fn invoke(&mut self, args: &[&Variant]) -> Result<Variant, ()>;
346+
}
347+
348+
pub unsafe extern "C" fn rust_callable_call_custom<C: RustCallable>(
349+
callable_userdata: *mut std::ffi::c_void,
350+
p_args: *const sys::GDExtensionConstVariantPtr,
351+
p_argument_count: sys::GDExtensionInt,
352+
r_return: sys::GDExtensionVariantPtr,
353+
r_error: *mut sys::GDExtensionCallError,
354+
) {
355+
let arg_refs: &[&Variant] =
356+
Variant::unbounded_refs_from_sys(p_args, p_argument_count as usize);
357+
358+
let c: &mut C = CallableUserdata::inner_from_raw(callable_userdata);
359+
360+
let result = c.invoke(arg_refs);
361+
crate::builtin::meta::varcall_return_checked(result, r_return, r_error);
362+
}
363+
364+
pub unsafe extern "C" fn rust_callable_call_fn<F>(
365+
callable_userdata: *mut std::ffi::c_void,
366+
p_args: *const sys::GDExtensionConstVariantPtr,
367+
p_argument_count: sys::GDExtensionInt,
368+
r_return: sys::GDExtensionVariantPtr,
369+
r_error: *mut sys::GDExtensionCallError,
370+
) where
371+
F: FnMut(&[&Variant]) -> Result<Variant, ()>,
372+
{
373+
let arg_refs: &[&Variant] =
374+
Variant::unbounded_refs_from_sys(p_args, p_argument_count as usize);
375+
376+
let w: &mut FnWrapper<F> = CallableUserdata::inner_from_raw(callable_userdata);
377+
378+
let result = (w.rust_function)(arg_refs);
379+
crate::builtin::meta::varcall_return_checked(result, r_return, r_error);
380+
}
381+
382+
pub unsafe extern "C" fn rust_callable_destroy<T>(callable_userdata: *mut std::ffi::c_void) {
383+
let rust_ptr = callable_userdata as *mut CallableUserdata<T>;
384+
let _drop = Box::from_raw(rust_ptr);
385+
}
386+
387+
pub unsafe extern "C" fn rust_callable_hash<T: Hash>(
388+
callable_userdata: *mut std::ffi::c_void,
389+
) -> u32 {
390+
let c: &T = CallableUserdata::<T>::inner_from_raw(callable_userdata);
391+
392+
// Just cut off top bits, not best-possible hash.
393+
sys::hash_value(c) as u32
394+
}
395+
396+
pub unsafe extern "C" fn rust_callable_equal<T: PartialEq>(
397+
callable_userdata_a: *mut std::ffi::c_void,
398+
callable_userdata_b: *mut std::ffi::c_void,
399+
) -> sys::GDExtensionBool {
400+
let a: &T = CallableUserdata::inner_from_raw(callable_userdata_a);
401+
let b: &T = CallableUserdata::inner_from_raw(callable_userdata_b);
402+
403+
(a == b) as sys::GDExtensionBool
404+
}
405+
406+
pub unsafe extern "C" fn rust_callable_to_string_display<T: fmt::Display>(
407+
callable_userdata: *mut std::ffi::c_void,
408+
r_is_valid: *mut sys::GDExtensionBool,
409+
r_out: sys::GDExtensionStringPtr,
410+
) {
411+
let c: &T = CallableUserdata::inner_from_raw(callable_userdata);
412+
let s = crate::builtin::GodotString::from(c.to_string());
413+
414+
s.move_string_ptr(r_out);
415+
*r_is_valid = true as sys::GDExtensionBool;
416+
}
417+
418+
pub unsafe extern "C" fn rust_callable_to_string_named<F>(
419+
callable_userdata: *mut std::ffi::c_void,
420+
r_is_valid: *mut sys::GDExtensionBool,
421+
r_out: sys::GDExtensionStringPtr,
422+
) {
423+
let w: &mut FnWrapper<F> = CallableUserdata::inner_from_raw(callable_userdata);
424+
425+
w.name.clone().move_string_ptr(r_out);
426+
*r_is_valid = true as sys::GDExtensionBool;
427+
}
428+
429+
pub fn unqualified_type_name<T>() -> &'static str {
430+
let type_name = std::any::type_name::<T>();
431+
type_name.split("::").last().unwrap()
432+
}
433+
}

0 commit comments

Comments
 (0)