Skip to content

Commit 3cf6fdc

Browse files
goatfryedBromeon
authored andcommitted
feat(core): support easier, type-safe deferred calls
1 parent b1d9ee8 commit 3cf6fdc

File tree

6 files changed

+114
-1
lines changed

6 files changed

+114
-1
lines changed

godot-core/src/obj/call_deferred.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
use crate::builtin::{Callable, Variant};
8+
use crate::meta::UniformObjectDeref;
9+
use crate::obj::bounds::Declarer;
10+
use crate::obj::GodotClass;
11+
#[cfg(since_api = "4.2")]
12+
use crate::registry::signal::ToSignalObj;
13+
use godot_ffi::is_main_thread;
14+
use std::ops::DerefMut;
15+
16+
// Dummy traits to still allow bounds and imports.
17+
#[cfg(before_api = "4.2")]
18+
pub trait WithDeferredCall<T: GodotClass> {}
19+
20+
/// Enables `Gd::apply_deferred()` for type-safe deferred calls.
21+
///
22+
/// The trait is automatically available for all engine-defined Godot classes and user classes containing a `Base<T>` field.
23+
///
24+
/// # Usage
25+
///
26+
/// ```no_run
27+
/// # use godot::prelude::*;
28+
/// # use std::f32::consts::PI;
29+
/// fn some_fn(mut node: Gd<Node2D>) {
30+
/// node.apply_deferred(|n: &mut Node2D| n.rotate(PI))
31+
/// }
32+
/// ```
33+
#[cfg(since_api = "4.2")]
34+
pub trait WithDeferredCall<T: GodotClass> {
35+
/// Defers the given closure to run during [idle time](https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-call-deferred).
36+
///
37+
/// This is a type-safe alternative to [`Object::call_deferred()`][crate::classes::Object::call_deferred].
38+
///
39+
/// # Panics
40+
/// If called outside the main thread.
41+
fn apply_deferred<F>(&mut self, rust_function: F)
42+
where
43+
F: FnOnce(&mut T) + 'static;
44+
}
45+
46+
#[cfg(since_api = "4.2")]
47+
impl<T, S, D> WithDeferredCall<T> for S
48+
where
49+
T: UniformObjectDeref<D, Declarer = D>,
50+
S: ToSignalObj<T>,
51+
D: Declarer,
52+
{
53+
fn apply_deferred<'a, F>(&mut self, rust_function: F)
54+
where
55+
F: FnOnce(&mut T) + 'static,
56+
{
57+
assert!(
58+
is_main_thread(),
59+
"`apply_deferred` must be called on the main thread"
60+
);
61+
let mut rust_fn_once = Some(rust_function);
62+
let mut this = self.to_signal_obj().clone();
63+
let callable = Callable::from_local_fn("apply_deferred", move |_| {
64+
let rust_fn_once = rust_fn_once
65+
.take()
66+
.expect("rust_fn_once was already consumed");
67+
let mut this_mut = T::object_as_mut(&mut this);
68+
rust_fn_once(this_mut.deref_mut());
69+
Ok(Variant::nil())
70+
});
71+
callable.call_deferred(&[]);
72+
}
73+
}

godot-core/src/obj/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! * [`Gd`], a smart pointer that manages instances of Godot classes.
1313
1414
mod base;
15+
mod call_deferred;
1516
mod casts;
1617
mod dyn_gd;
1718
mod gd;
@@ -25,6 +26,7 @@ mod traits;
2526
pub(crate) mod rtti;
2627

2728
pub use base::*;
29+
pub use call_deferred::WithDeferredCall;
2830
pub use dyn_gd::DynGd;
2931
pub use gd::*;
3032
pub use guards::{BaseMut, BaseRef, DynGdMut, DynGdRef, GdMut, GdRef};

godot-core/src/registry/signal/typed_signal.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::borrow::Cow;
1616
use std::marker::PhantomData;
1717
use std::ops::DerefMut;
1818

19+
// TODO(v0.4): find more general name for trait.
1920
/// Object part of the signal receiver (handler).
2021
///
2122
/// Functionality overlaps partly with [`meta::AsObjectArg`] and [`meta::AsArg<ObjectArg>`]. Can however not directly be replaced

godot-ffi/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,6 @@ pub fn main_thread_id() -> std::thread::ThreadId {
435435
///
436436
/// # Panics
437437
/// - If it is called before the engine bindings have been initialized.
438-
///
439438
pub fn is_main_thread() -> bool {
440439
#[cfg(not(wasm_nothreads))]
441440
{

godot/src/prelude.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ pub use super::obj::EngineEnum as _;
3636
pub use super::obj::NewAlloc as _;
3737
pub use super::obj::NewGd as _;
3838
pub use super::obj::WithBaseField as _; // base(), base_mut(), to_gd()
39+
pub use super::obj::WithDeferredCall as _; // apply_deferred()
3940
pub use super::obj::WithSignals as _; // Gd::signals()
4041
pub use super::obj::WithUserSignals as _; // self.signals()

itest/rust/src/object_tests/call_deferred_test.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::framework::itest;
88
use godot::obj::WithBaseField;
99
use godot::prelude::*;
1010
use godot::task::{SignalFuture, TaskHandle};
11+
use std::ops::DerefMut;
1112

1213
const ACCEPTED_NAME: &str = "touched";
1314

@@ -68,3 +69,39 @@ fn call_deferred_untyped(ctx: &crate::framework::TestContext) -> TaskHandle {
6869
let mut gd_mut = test_node.bind_mut();
6970
gd_mut.create_assertion_task()
7071
}
72+
73+
#[itest(async)]
74+
fn call_deferred_godot_class(ctx: &crate::framework::TestContext) -> TaskHandle {
75+
let mut test_node = DeferredTestNode::new_alloc();
76+
ctx.scene_tree.clone().add_child(&test_node);
77+
78+
let mut gd_mut = test_node.bind_mut();
79+
// Explicitly check that this can be invoked on &mut T.
80+
let godot_class_ref: &mut DeferredTestNode = gd_mut.deref_mut();
81+
godot_class_ref.apply_deferred(DeferredTestNode::accept);
82+
83+
gd_mut.create_assertion_task()
84+
}
85+
86+
#[itest(async)]
87+
fn call_deferred_gd_user_class(ctx: &crate::framework::TestContext) -> TaskHandle {
88+
let mut test_node = DeferredTestNode::new_alloc();
89+
ctx.scene_tree.clone().add_child(&test_node);
90+
91+
test_node.apply_deferred(DeferredTestNode::accept);
92+
93+
let mut gd_mut = test_node.bind_mut();
94+
gd_mut.create_assertion_task()
95+
}
96+
97+
#[itest(async)]
98+
fn call_deferred_gd_engine_class(ctx: &crate::framework::TestContext) -> TaskHandle {
99+
let mut test_node = DeferredTestNode::new_alloc();
100+
ctx.scene_tree.clone().add_child(&test_node);
101+
102+
let mut node = test_node.clone().upcast::<Node>();
103+
node.apply_deferred(|that_node| that_node.set_name(ACCEPTED_NAME));
104+
105+
let mut gd_mut = test_node.bind_mut();
106+
gd_mut.create_assertion_task()
107+
}

0 commit comments

Comments
 (0)