Skip to content

Commit 18f1602

Browse files
committed
feat(core): support easier, type-safe deferred calls
1 parent eb9253a commit 18f1602

File tree

3 files changed

+152
-7
lines changed

3 files changed

+152
-7
lines changed

godot-core/src/obj/deferred.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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::obj::{bounds, Bounds, Gd, GodotClass, WithBaseField};
9+
use godot_ffi::is_main_thread;
10+
use std::ops::DerefMut;
11+
12+
pub trait DeferredCallableGd<T>
13+
where
14+
T: GodotClass + Bounds<Declarer = bounds::DeclUser>,
15+
{
16+
/// Runs the given Closure deferred.
17+
///
18+
/// This can be a type-safe alternative to [`classes::Object::call_deferred`], but does not handle dynamic dispatch, unless explicitly used.
19+
/// This must be used on the main thread.
20+
fn apply_deferred<F>(&mut self, rust_function: F)
21+
where
22+
F: FnMut(&mut T) + 'static;
23+
}
24+
25+
pub trait CallEngineClassDeferred<T>
26+
where
27+
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
28+
{
29+
/// Runs the given Closure deferred.
30+
///
31+
/// This can be a type-safe alternative to [`classes::Object::call_deferred`], but does not handle dynamic dispatch, unless explicitly used.
32+
/// This must be used on the main thread.
33+
fn apply_deferred<F>(&mut self, rust_function: F)
34+
where
35+
F: FnMut(Gd<T>) + 'static;
36+
}
37+
38+
impl<T> DeferredCallableGd<T> for T
39+
where
40+
T: GodotClass + Bounds<Declarer = bounds::DeclUser> + WithBaseField,
41+
{
42+
fn apply_deferred<F>(&mut self, rust_function: F)
43+
where
44+
F: FnMut(&mut T) + 'static,
45+
{
46+
self.to_gd().apply_deferred(rust_function);
47+
}
48+
}
49+
50+
impl<T> DeferredCallableGd<T> for Gd<T>
51+
where
52+
T: GodotClass + Bounds<Declarer = bounds::DeclUser>,
53+
{
54+
fn apply_deferred<F>(&mut self, mut rust_function: F)
55+
where
56+
F: FnMut(&mut T) + 'static,
57+
{
58+
assert!(
59+
is_main_thread(),
60+
"apply_deferred must be called on main thread"
61+
);
62+
let this = self.clone();
63+
let callable = Callable::from_local_fn("apply_deferred", move |_| {
64+
rust_function(this.clone().bind_mut().deref_mut());
65+
Ok(Variant::nil())
66+
});
67+
callable.call_deferred(&[]);
68+
}
69+
}
70+
71+
impl<T> CallEngineClassDeferred<T> for Gd<T>
72+
where
73+
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
74+
{
75+
fn apply_deferred<F>(&mut self, mut rust_function: F)
76+
where
77+
F: FnMut(Gd<T>) + 'static,
78+
{
79+
assert!(
80+
is_main_thread(),
81+
"apply_deferred must be called on main thread"
82+
);
83+
let this = self.clone();
84+
let callable = Callable::from_local_fn("apply_deferred", move |_| {
85+
rust_function(this.clone());
86+
Ok(Variant::nil())
87+
});
88+
callable.call_deferred(&[]);
89+
}
90+
}

godot-core/src/obj/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ pub use raw_gd::*;
3434
pub use traits::*;
3535

3636
pub mod bounds;
37+
#[cfg(since_api = "4.2")]
38+
pub mod deferred;
3739
pub mod script;
40+
3841
pub use bounds::private::Bounds;
3942

4043
// Do not re-export rtti here.

itest/rust/src/object_tests/deferred_call_test.rs

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,28 @@
44
* License, v. 2.0. If a copy of the MPL was not distributed with this
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
7-
87
use crate::framework::itest;
9-
use crate::object_tests::deferred_call_test::TestState::{Accepted, Initial};
8+
use crate::object_tests::deferred_call_test::TestState::{
9+
Accepted, FailedEngineClass, Initial, VerifyEngineClass,
10+
};
11+
use godot::obj::deferred::{CallEngineClassDeferred, DeferredCallableGd};
1012
use godot::prelude::*;
1113
use godot::task::{SignalFuture, TaskHandle};
14+
use std::ops::DerefMut;
1215

1316
#[derive(GodotConvert, Var, Export, Clone, PartialEq, Debug)]
1417
#[godot(via = GString)]
1518
enum TestState {
1619
Initial,
1720
Accepted,
21+
VerifyEngineClass,
22+
FailedEngineClass,
1823
}
1924

2025
#[derive(GodotClass)]
21-
#[class(base=Node)]
26+
#[class(base=Node2D)]
2227
struct DeferredTestNode {
23-
base: Base<Node>,
28+
base: Base<Node2D>,
2429
state: TestState,
2530
}
2631

@@ -35,10 +40,11 @@ impl DeferredTestNode {
3540
}
3641

3742
fn as_expectation_task(&self) -> TaskHandle {
38-
assert_eq!(Initial, self.state, "accept evaluated synchronously");
43+
assert_ne!(Accepted, self.state, "accept evaluated synchronously");
3944

4045
let test_will_succeed: SignalFuture<(Variant,)> =
4146
Signal::from_object_signal(&self.to_gd(), "test_completed").to_future();
47+
4248
godot::task::spawn(async move {
4349
let (final_state,) = test_will_succeed.await;
4450
let final_state: TestState = final_state.to();
@@ -49,7 +55,7 @@ impl DeferredTestNode {
4955
}
5056

5157
#[godot_api]
52-
impl INode for DeferredTestNode {
58+
impl INode2D for DeferredTestNode {
5359
fn init(base: Base<Self::Base>) -> Self {
5460
Self {
5561
base,
@@ -58,6 +64,14 @@ impl INode for DeferredTestNode {
5864
}
5965

6066
fn process(&mut self, _delta: f64) {
67+
if self.state == VerifyEngineClass {
68+
if self.base().get_name() == "changed".into() {
69+
self.state = Accepted;
70+
} else {
71+
self.state = FailedEngineClass;
72+
}
73+
}
74+
6175
let args = vslice![self.state];
6276
self.base_mut().emit_signal("test_completed", args);
6377
}
@@ -67,9 +81,47 @@ impl INode for DeferredTestNode {
6781
fn calls_method_names_deferred(ctx: &crate::framework::TestContext) -> TaskHandle {
6882
let mut test_node = DeferredTestNode::new_alloc();
6983
ctx.scene_tree.clone().add_child(&test_node);
70-
84+
7185
test_node.call_deferred("accept", &[]);
7286

7387
let handle = test_node.bind().as_expectation_task();
7488
handle
7589
}
90+
91+
#[itest(async)]
92+
fn calls_user_class_deferred_from_self(ctx: &crate::framework::TestContext) -> TaskHandle {
93+
let mut test_node = DeferredTestNode::new_alloc();
94+
ctx.scene_tree.clone().add_child(&test_node);
95+
96+
test_node
97+
.bind_mut()
98+
.deref_mut()
99+
.apply_deferred(DeferredTestNode::accept);
100+
101+
let handle = test_node.bind().as_expectation_task();
102+
handle
103+
}
104+
105+
#[itest(async)]
106+
fn calls_user_class_deferred(ctx: &crate::framework::TestContext) -> TaskHandle {
107+
let mut test_node = DeferredTestNode::new_alloc();
108+
ctx.scene_tree.clone().add_child(&test_node);
109+
110+
test_node.apply_deferred(DeferredTestNode::accept);
111+
112+
let handle = test_node.bind().as_expectation_task();
113+
handle
114+
}
115+
116+
#[itest(async)]
117+
fn calls_engine_class_deferred(ctx: &crate::framework::TestContext) -> TaskHandle {
118+
let mut test_node = DeferredTestNode::new_alloc();
119+
ctx.scene_tree.clone().add_child(&test_node);
120+
121+
let mut node = test_node.clone().upcast::<Node>();
122+
node.apply_deferred(|mut that_node| that_node.set_name("changed"));
123+
124+
test_node.bind_mut().state = VerifyEngineClass;
125+
let handle = test_node.bind().as_expectation_task();
126+
handle
127+
}

0 commit comments

Comments
 (0)