Skip to main content

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}