Skip to content

Commit 4023c5a

Browse files
committed
Add Callable::from_once_fn() for one-time calls
1 parent 8d9a1ee commit 4023c5a

File tree

3 files changed

+52
-9
lines changed

3 files changed

+52
-9
lines changed

godot-core/src/builtin/callable.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ use godot_ffi as sys;
1111
use sys::{ffi_methods, ExtVariantType, GodotFfi};
1212

1313
use crate::builtin::{inner, GString, StringName, Variant, VariantArray};
14-
use crate::classes;
1514
use crate::meta::{GodotType, ToGodot};
1615
use crate::obj::bounds::DynMemory;
1716
use crate::obj::{Bounds, Gd, GodotClass, InstanceId};
17+
use crate::{classes, meta};
1818

1919
#[cfg(all(since_api = "4.2", before_api = "4.3"))]
2020
type CallableCustomInfo = sys::GDExtensionCallableCustomInfo;
@@ -181,6 +181,40 @@ impl Callable {
181181
})
182182
}
183183

184+
/// Create callable from **single-threaded** Rust function or closure that can only be called once.
185+
///
186+
/// `name` is used for the string representation of the closure, which helps debugging.
187+
///
188+
/// After the first invocation, subsequent calls will panic with a message indicating the callable has already been consumed. This is
189+
/// useful for deferred operations that should only execute once. For repeated execution, use [`from_local_fn()][Self::from_local_fn].
190+
#[cfg(since_api = "4.2")]
191+
pub(crate) fn from_once_fn<F, S>(name: S, rust_function: F) -> Self
192+
where
193+
F: 'static + FnOnce(&[&Variant]) -> Result<Variant, ()>,
194+
S: meta::AsArg<GString>,
195+
{
196+
meta::arg_into_owned!(name);
197+
198+
let mut rust_fn_once = Some(rust_function);
199+
Self::from_local_fn(&name, move |args| {
200+
let rust_fn_once = rust_fn_once
201+
.take()
202+
.expect("callable created with from_once_fn() has already been consumed");
203+
rust_fn_once(args)
204+
})
205+
}
206+
207+
#[cfg(feature = "trace")] // Test only.
208+
#[cfg(since_api = "4.2")]
209+
#[doc(hidden)]
210+
pub fn __once_fn<F, S>(name: S, rust_function: F) -> Self
211+
where
212+
F: 'static + FnOnce(&[&Variant]) -> Result<Variant, ()>,
213+
S: meta::AsArg<GString>,
214+
{
215+
Self::from_once_fn(name, rust_function)
216+
}
217+
184218
#[cfg(since_api = "4.2")]
185219
pub(crate) fn with_scoped_fn<S, F, Fc, R>(name: S, rust_function: F, callable_usage: Fc) -> R
186220
where
@@ -518,8 +552,6 @@ pub use custom_callable::RustCallable;
518552
#[cfg(since_api = "4.2")]
519553
use custom_callable::*;
520554

521-
use crate::meta;
522-
523555
#[cfg(since_api = "4.2")]
524556
mod custom_callable {
525557
use std::hash::Hash;

godot-core/src/obj/call_deferred.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,11 @@ where
6161
is_main_thread(),
6262
"`apply_deferred` must be called on the main thread"
6363
);
64-
let mut rust_fn_once = Some(rust_function);
64+
6565
let mut this = self.to_signal_obj().clone();
66-
let callable = Callable::from_local_fn("apply_deferred", move |_| {
67-
let rust_fn_once = rust_fn_once
68-
.take()
69-
.expect("rust_fn_once was already consumed");
66+
let callable = Callable::from_once_fn("apply_deferred", move |_| {
7067
let mut this_mut = T::object_as_mut(&mut this);
71-
rust_fn_once(this_mut.deref_mut());
68+
rust_function(this_mut.deref_mut());
7269
Ok(Variant::nil())
7370
});
7471
callable.call_deferred(&[]);

itest/rust/src/builtin_tests/containers/callable_test.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,20 @@ pub mod custom_callable {
648648
assert!(signal.is_connected(&identical_callable));
649649
}
650650

651+
#[cfg(since_api = "4.2")]
652+
#[itest]
653+
fn callable_from_once_fn() {
654+
let callable = Callable::__once_fn("once_test", move |_| Ok(42.to_variant()));
655+
656+
// First call should succeed.
657+
let result = callable.call(&[]);
658+
assert_eq!(result.to::<i32>(), 42);
659+
660+
// Second call should fail (panic currently isn't propagated, see other tests).
661+
let result = callable.call(&[]);
662+
assert!(result.is_nil());
663+
}
664+
651665
// ------------------------------------------------------------------------------------------------------------------------------------------
652666
// Helper structs and functions for custom callables
653667

0 commit comments

Comments
 (0)