"Run Rust coroutines and async code in Godot 4.4+ (through GDExtension), inspired on Unity's Coroutines design."
This crate uses 4 nightly(unstable) features:
#![feature(coroutines)]
#![feature(coroutine_trait)]
#![feature(stmt_expr_attributes)]
#![feature(unboxed_closures)]
It also requires GdExtension's experimental_threads feature
Add the dependency to your Cargo.toml file:
[dependencies]
gdext_coroutines = "1.0.0"
Allows you to execute code in an asynchronous manner, the coroutines of this crate work very much like Unity's.
It also allows you to execute async code(futures) via gdext-rust's task system (godot::task::spawn).
```rust ignore
use gdext_coroutines::prelude::; use godot::prelude::;
fn run_some_routines(node: Gd) { node.start_coroutine( #[coroutine] || { godot_print!("Starting coroutine");
godot_print!("Waiting for 5 seconds...");
yield seconds(5.0);
godot_print!("5 seconds have passed!");
godot_print!("Waiting for 30 frames");
yield frames(30);
godot_print!("30 frames have passed!");
godot_print!("Waiting until pigs start flying...");
let pig: Gd<Node2D> = create_pig();
yield wait_until(move || pig.is_flying());
godot_print!("Wow! Pigs are now able to fly! Somehow...");
godot_print!("Waiting while pigs are still flying...");
let pig: Gd<Node2D> = grab_pig();
yield wait_while(move || pig.is_flying());
godot_print!("Finally, no more flying pigs, oof.");
godot_print!("Waiting for a signal...");
let signal = Signal::from_object_signal(&node, "some_signal");
yield wait_for_signal_untyped(signal);
godot_print!("Signal received!");
});
node.start_async_task(
async {
godot_print!("Executing async code!");
some_async_fn().await;
godot_print!("Async function finished!");
});
}
For more examples, check the `integration_tests` folder in the repository.
---
# How does this do?
A Coroutine is a struct that derives `Node`
```rust ignore
#[derive(GodotClass)]
#[class(no_init, base = Node)]
pub struct SpireCoroutine { /* .. */ }
When you invoke start_coroutine(), start_async_task(), or spawn(), a SpireCoroutine node is created, then added as a child of the caller.
Then, on every frame:
- Rust Coroutines(start_coroutine): polls the current yield to advance its inner function.
- Rust Futures(start_async_task): polls until the spawned gdext-rust task finishes.
```rust ignore
impl INode for SpireCoroutine { fn process(&mut self, delta: f64) { if !self.paused && self.poll_mode == PollMode::Process { self.run(delta); } }
fn physics_process(&mut self, delta: f64) {
if !self.paused && self.poll_mode == PollMode::Physics {
self.run(delta);
}
}
}
Then it automatically destroys itself after finishing:
```rust ignore
fn run(&mut self, delta_time: f64) {
if let Some(result) = self.poll(delta_time) {
self.finish_with(result);
}
}
pub fn finish_with(&mut self, result: Variant) {
/* .. */
self.base_mut().emit_signal(SIGNAL_FINISHED.into(), &[result]);
self.de_spawn();
}
Since the coroutine is a child node of whoever created it, the behavior is tied to its parent:
- If the parent exits the scene tree, the coroutine pauses running (since it requires _process/_physics_process to run).
- If the parent is queued free, the coroutine is also queued free, and its finished signal never triggers.
frames(n) - Wait for N frames```rust ignore yield frames(5);
### `seconds(n)` - Wait for N seconds (engine time)
```rust ignore
yield seconds(2.5);
wait_until(f) - Resume when f returns true```rust ignore yield wait_until(move || some_condition());
### `wait_while(f)` - Keep waiting while `f` returns true
```rust ignore
yield wait_while(move || still_busy());
wait_for_signal(signal) - Wait for a typed signal emission```rust ignore let sig = node.signals().some_signal(); yield wait_for_signal(&sig);
For untyped signals:
```rust ignore
let sig = Signal::from_object_signal(&node, "some_signal");
yield wait_for_signal_untyped(sig);
wait_until_finished() - Wait for another coroutine to finish```rust ignore let other = node.start_coroutine(#[coroutine] || { yield frames(10); }); yield other.wait_until_finished();
---
# Notes
### 1 - You can await coroutines from GdScript, using the signal `finished`
```js
var coroutine: SpireCoroutine = ..
var result = await coroutine.finished
result contains the return value of your coroutine/future.
KeepWaiting```rust ignore pub trait KeepWaiting { /// The coroutine calls this to check if it should keep waiting fn keep_waiting(&mut self, delta_time: f64) -> bool; }
Then you can use that trait like this:
```rust ignore
let my_custom_yield: dyn KeepWaiting = ...;
yield Yield::Dyn(Box::new(my_custom_yield));
Otherwise, this crate's godot classes will not be registered in Godot.
This is a known issue in gdext-rust, it's not related to gdext-coroutines.
$ claude mcp add gdext_coroutines \
-- python -m otcore.mcp_server <graph>