gdext_async/lib.rs
1
2//! Provides utilities for bidirectional support between Rust and
3//! GDScript for `async` and `await`.
4//!
5//! This crate provides two macros, both intended for interoperability
6//! with GDScript: [`godot_async`] and [`godot_await`]. If you are
7//! writing a pure-Rust project that does not need to inter-operate
8//! with GDScript, then you probably do NOT need this crate.
9
10use godot::prelude::*;
11
12use std::cell::OnceCell;
13use std::rc::Rc;
14
15#[derive(GodotClass, Debug)]
16#[class(no_init)]
17#[doc(hidden)] // Only public for use in macro.
18pub struct AsyncAwaitableTask {
19 base: Base<RefCounted>,
20 known_result: Rc<OnceCell<Variant>>,
21}
22
23impl AsyncAwaitableTask {
24 pub fn create<F>(future: F) -> Gd<Self>
25 where F: IntoFuture + 'static,
26 <F as IntoFuture>::Output: ToGodot {
27 let known_result = Rc::new(OnceCell::new());
28 let inst = Gd::from_init_fn(|base| {
29 AsyncAwaitableTask { base, known_result: Rc::clone(&known_result) }
30 });
31 {
32 let inst = inst.clone(); // RefCounted: Cheap clone
33 let _task_handle = godot::task::spawn(async move {
34 let res = future.into_future().await;
35 known_result.set(res.to_variant())
36 .expect("known_result was set twice!");
37 inst.signals().completed().emit(&res.to_variant());
38 });
39 }
40 inst
41 }
42
43 /// If the task is finished already, return its result. Else, return
44 /// a signal that will fire when it finishes.
45 pub fn get_maybe_awaitable_value(&mut self) -> Variant {
46 if let Some(res) = self.known_result.get() {
47 res.clone()
48 } else {
49 self.signals().completed().to_untyped().to_variant()
50 }
51 }
52}
53
54#[godot_api]
55impl AsyncAwaitableTask {
56 #[signal]
57 pub fn completed(result: Variant);
58}
59
60/// If the object is a `GDScriptFunctionState`, await it. If not,
61/// return it unmodified. Note that, unlike GDScript, this function
62/// does NOT attempt to detect and await signals. Only actual
63/// `GDScriptFunctionState` objects will be awaited.
64///
65/// This is the function used under-the-hood by the [`godot_await!`]
66/// macro, but it can also be used independently, for instance, if you
67/// need to store the future (as a [`Future`]) and not evaluate it
68/// yet.
69pub async fn resolve_gdscript_coroutine(variant: Variant) -> Variant {
70 // This function is adapted from
71 // [https://github.com/godot-rust/gdext/pull/1645/changes] and uses
72 // undocumented Godot trickery to adapt GDScript's `await` keyword
73 // into Rust.
74 if let Some(state) = to_gdscript_function_state(&variant) {
75 let signal = Signal::from_object_signal(&state, "completed");
76 let (result,) = signal.to_future::<(Variant,)>().await;
77 drop(state);
78 result
79 } else {
80 variant
81 }
82}
83
84fn to_gdscript_function_state(variant: &Variant) -> Option<Gd<Object>> {
85 let state = variant.try_to::<Gd<Object>>().ok()?;
86 if state.get_class() == "GDScriptFunctionState" {
87 Some(state)
88 } else {
89 None
90 }
91}
92
93/// Convert a block of Rust async code into a Godot task whose
94/// completion can safely be awaited from GDScript.
95///
96/// This macro returns a `Variant` which can be awaited on the
97/// GDScript side. If this macro is the entire function body, your
98/// function must return `Variant`.
99///
100/// It is strongly recommended that functions which use this macro
101/// take `this: Gd<Self>` via `#[func(gd_self)]` (as opposed to
102/// `&self` or `&mut self`) in order to avoid holding a `Gd` binding
103/// across an `await` point.
104///
105/// # Example Usage
106///
107/// ```rust
108/// # use godot::prelude::*;
109/// # use gdext_async::godot_async;
110/// // Rust
111///
112/// #[derive(GodotClass)]
113/// #[class(init, base=Node)]
114/// struct MyRustNode {}
115///
116/// #[godot_api]
117/// impl MyRustNode {
118/// #[func(gd_self)]
119/// pub fn wait_three_secs(this: Gd<Self>) -> Variant {
120/// godot_async! {
121/// godot_print!("Before Timer! (2)");
122/// let timer = this.get_tree().create_timer(3.0);
123/// timer.signals().timeout().to_future().await;
124/// godot_print!("After Timer! (3)");
125/// }
126/// }
127/// }
128/// ```
129///
130/// ```gdscript
131/// # GDScript
132/// extends Node
133///
134/// func _ready():
135/// var node = MyRustNode.new()
136/// add_child(node)
137/// print("Before Timer! (1)")
138/// await node.wait_three_secs()
139/// print("After Timer! (4)")
140/// ```
141#[macro_export]
142macro_rules! godot_async {
143 ($($block: tt)+) => {
144 $crate::AsyncAwaitableTask::create(async move {
145 $($block)+
146 }).bind_mut().get_maybe_awaitable_value()
147 }
148}
149
150/// Await (in Rust) a Godot value, using semantics similar to
151/// GDScript's `await`.
152///
153/// GDScript function state objects will be awaited, while any other
154/// value (**including signals**) will be returned verbatim.
155///
156/// # Example Usage
157///
158/// ```rust
159/// # use godot::prelude::*;
160/// # use gdext_async::godot_await;
161/// // Rust
162///
163/// // Note: Ordinary Rust async function, NOT exposed to GDScript.
164/// pub async fn fade_out_enemy(mut enemy_node: Gd<Node2D>) {
165/// godot_print!("Freeing enemy node ...");
166/// godot_await! { enemy_node.call("fade_out", &[]) };
167/// godot_print!("Enemy node freed.");
168/// }
169///
170/// ```
171///
172/// ```gdscript
173/// # GDScript
174/// extends Node2D
175///
176/// func fade_out():
177/// var tween = create_tween()
178/// tween.tween_property(self, "modulate:a", 0.0, 1.0)
179/// await tween.finished
180/// queue_free()
181/// ```
182#[macro_export]
183macro_rules! godot_await {
184 ($($block: tt)+) => {
185 $crate::resolve_gdscript_coroutine({ $($block)+ }).await
186 }
187}