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//!
10//! # Features
11//!
12//! * `macros` - Exposes the attribute macro `#[gdext_async]`. Enabled
13//! by default. This requires the `syn` crate as a dependency.
14
15use godot::prelude::*;
16
17use std::cell::OnceCell;
18use std::rc::Rc;
19
20#[derive(GodotClass, Debug)]
21#[class(no_init)]
22#[doc(hidden)] // Only public for use in macro.
23pub struct AsyncAwaitableTask {
24 base: Base<RefCounted>,
25 known_result: Rc<OnceCell<Variant>>,
26}
27
28impl AsyncAwaitableTask {
29 pub fn create<F>(future: F) -> Gd<Self>
30 where F: IntoFuture + 'static,
31 <F as IntoFuture>::Output: ToGodot {
32 let known_result = Rc::new(OnceCell::new());
33 let inst = Gd::from_init_fn(|base| {
34 AsyncAwaitableTask { base, known_result: Rc::clone(&known_result) }
35 });
36 {
37 let inst = inst.clone(); // RefCounted: Cheap clone
38 let _task_handle = godot::task::spawn(async move {
39 let res = future.into_future().await;
40 known_result.set(res.to_variant())
41 .expect("known_result was set twice!");
42 inst.signals().completed().emit(&res.to_variant());
43 });
44 }
45 inst
46 }
47
48 /// If the task is finished already, return its result. Else, return
49 /// a signal that will fire when it finishes.
50 pub fn get_maybe_awaitable_value(&mut self) -> Variant {
51 if let Some(res) = self.known_result.get() {
52 res.clone()
53 } else {
54 self.signals().completed().to_untyped().to_variant()
55 }
56 }
57}
58
59#[godot_api]
60impl AsyncAwaitableTask {
61 #[signal]
62 pub fn completed(result: Variant);
63}
64
65/// If the object is a `GDScriptFunctionState`, await it. If not,
66/// return it unmodified. Note that, unlike GDScript, this function
67/// does NOT attempt to detect and await signals. Only actual
68/// `GDScriptFunctionState` objects will be awaited.
69///
70/// This is the function used under-the-hood by the [`godot_await!`]
71/// macro, but it can also be used independently, for instance, if you
72/// need to store the future (as a [`Future`]) and not evaluate it
73/// yet.
74pub async fn resolve_gdscript_coroutine(variant: Variant) -> Variant {
75 // This function is adapted from
76 // [https://github.com/godot-rust/gdext/pull/1645/changes] and uses
77 // undocumented Godot trickery to adapt GDScript's `await` keyword
78 // into Rust.
79 if let Some(state) = to_gdscript_function_state(&variant) {
80 let signal = Signal::from_object_signal(&state, "completed");
81 let (result,) = signal.to_future::<(Variant,)>().await;
82 drop(state);
83 result
84 } else {
85 variant
86 }
87}
88
89fn to_gdscript_function_state(variant: &Variant) -> Option<Gd<Object>> {
90 let state = variant.try_to::<Gd<Object>>().ok()?;
91 if state.get_class() == "GDScriptFunctionState" {
92 Some(state)
93 } else {
94 None
95 }
96}
97
98/// Convert a block of Rust async code into a Godot task whose
99/// completion can safely be awaited from GDScript.
100///
101/// This macro returns a `Variant` which can be awaited on the
102/// GDScript side. If this macro is the entire function body, your
103/// function must return `Variant`.
104///
105/// It is strongly recommended that functions which use this macro
106/// take `this: Gd<Self>` via `#[func(gd_self)]` (as opposed to
107/// `&self` or `&mut self`) in order to avoid holding a `Gd` binding
108/// across an `await` point.
109///
110/// # Example Usage
111///
112/// ```rust
113/// # use godot::prelude::*;
114/// # use gdext_async::godot_async;
115/// // Rust
116///
117/// #[derive(GodotClass)]
118/// #[class(init, base=Node)]
119/// struct MyRustNode {}
120///
121/// #[godot_api]
122/// impl MyRustNode {
123/// #[func(gd_self)]
124/// pub fn wait_three_secs(this: Gd<Self>) -> Variant {
125/// godot_async! {
126/// godot_print!("Before Timer! (2)");
127/// let timer = this.get_tree().create_timer(3.0);
128/// timer.signals().timeout().to_future().await;
129/// godot_print!("After Timer! (3)");
130/// }
131/// }
132/// }
133/// ```
134///
135/// ```gdscript
136/// # GDScript
137/// extends Node
138///
139/// func _ready():
140/// var node = MyRustNode.new()
141/// add_child(node)
142/// print("Before Timer! (1)")
143/// await node.wait_three_secs()
144/// print("After Timer! (4)")
145/// ```
146#[macro_export]
147macro_rules! godot_async {
148 ($($block: tt)+) => {
149 $crate::AsyncAwaitableTask::create(async move {
150 $($block)+
151 }).bind_mut().get_maybe_awaitable_value()
152 }
153}
154
155/// Await (in Rust) a Godot value, using semantics similar to
156/// GDScript's `await`.
157///
158/// GDScript function state objects will be awaited, while any other
159/// value (**including signals**) will be returned verbatim.
160///
161/// # Example Usage
162///
163/// ```rust
164/// # use godot::prelude::*;
165/// # use gdext_async::godot_await;
166/// // Rust
167///
168/// // Note: Ordinary Rust async function, NOT exposed to GDScript.
169/// pub async fn fade_out_enemy(mut enemy_node: Gd<Node2D>) {
170/// godot_print!("Freeing enemy node ...");
171/// godot_await! { enemy_node.call("fade_out", &[]) };
172/// godot_print!("Enemy node freed.");
173/// }
174///
175/// ```
176///
177/// ```gdscript
178/// # GDScript
179/// extends Node2D
180///
181/// func fade_out():
182/// var tween = create_tween()
183/// tween.tween_property(self, "modulate:a", 0.0, 1.0)
184/// await tween.finished
185/// queue_free()
186/// ```
187#[macro_export]
188macro_rules! godot_await {
189 ($($block: tt)+) => {
190 $crate::resolve_gdscript_coroutine({ $($block)+ }).await
191 }
192}
193
194/// Attribute macro that can be applied to `impl` blocks (similar to
195/// [`godot_api`]) to enable optional functionality.
196///
197/// This macro can only be applied to an inherent impl block, not to a
198/// trait impl. This macro generates `#[godot_api(secondary)]` blocks,
199/// so the relevant struct must have a primary (i.e. non-`secondary`)
200/// `#[godot_api]` block.
201///
202/// ## Async Functions (`#[async_func]` / `#[async_func(gd_self)]`)
203///
204/// A function in a `gdext_async`-enabled impl block which is tagged
205/// with `#[async_func]` will be exported to Godot, similar to
206/// `#[func]` on `#[godot_api]`. The exported Godot-side function will
207/// be `await`-able in GDScript.
208///
209/// The annotated function must be `async` and its arguments and
210/// return type must be Godot-compatible. That is, arguments must be
211/// [`AsArg`](godot::meta::AsArg) and the result type must be
212/// [`ToGodot`]. Note that this is a stronger guarantee than
213/// `#[func]`, as it is not currently possible to return, for example,
214/// `Result<T, E>` from a `#[async_func]` since the type does not
215/// implement `ToGodot`.
216///
217/// `#[async_func]` can be called as `#[async_func(gd_self)]`, which
218/// causes the generated GDScript-function to accept `Gd<Self>` rather
219/// than an explicit `&self` or `&mut self`. If you are accepting a
220/// `self` argument, you almost always want `gd_self`, as holding a
221/// `Gd` binding across an `await` point is generally a very bad idea.
222///
223/// The function will be available both from Rust and from GDScript
224/// and can be awaited using the respective `await` keyword in each
225/// language.
226///
227/// `#[rpc]` functions are NOT currently supported by this macro.
228///
229/// ### Example
230///
231/// ```
232/// # use godot::prelude::*;
233/// # use gdext_async_macros::gdext_async;
234///
235/// #[derive(GodotClass)]
236/// #[class(init, base=Node2D)]
237/// pub struct Player {}
238///
239/// #[gdext_async]
240/// #[godot_api]
241/// impl Player {
242/// #[async_func(gd_self)]
243/// pub async fn play_item_get_animation(this: Gd<Self>, item_id: i32) -> i32 {
244/// // Function body ...
245/// unimplemented!()
246/// }
247///
248/// // Also works with static functions
249/// #[async_func]
250/// pub async fn calculate_item_damage(item_id: i32) -> f32 {
251/// unimplemented!()
252/// }
253/// }
254/// ```
255#[doc(alias = "async_func")]
256#[cfg(feature = "macros")]
257pub use gdext_async_macros::gdext_async;