MCPcopy Index your code
hub / github.com/Houtamelo/gdext_coroutines

github.com/Houtamelo/gdext_coroutines @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
60 symbols 223 edges 7 files 15 documented · 25%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

gdext_coroutines

"Run Rust coroutines and async code in Godot 4.4+ (through GDExtension), inspired on Unity's Coroutines design."

Beware

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

Setup

Add the dependency to your Cargo.toml file:

[dependencies]
gdext_coroutines = "1.0.0"

What does this do?

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

![feature(coroutines)]

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

[godot_api]

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.


Yield Types

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.


2 - You can make your own custom types of yields, just implement the trait 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));

3 - Your main crate must have at least one godot class defined in it

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.

Extension points exported contracts — how you extend this code

StartCoroutine (Interface)
(no doc) [3 implementers]
rust/src/start_coroutine.rs
StartAsyncTask (Interface)
(no doc) [3 implementers]
rust/src/start_async_task.rs
KeepWaiting (Interface)
(no doc) [2 implementers]
rust/src/yielding.rs
WaitUntilFinished (Interface)
(no doc) [2 implementers]
rust/src/yielding.rs
IsRunning (Interface)
(no doc) [1 implementers]
rust/src/coroutine.rs

Core symbols most depended-on inside this repo

log
called by 43
rust/integration_tests/src/lib.rs
log_err
called by 15
rust/integration_tests/src/lib.rs
frames
called by 14
rust/src/yielding.rs
spawn
called by 10
rust/src/builder.rs
coroutine
called by 9
rust/src/start_coroutine.rs
start_coroutine
called by 8
rust/src/start_coroutine.rs
auto_start
called by 6
rust/src/builder.rs
seconds
called by 5
rust/src/yielding.rs

Shape

Method 31
Function 15
Interface 7
Class 4
Enum 3

Languages

Rust100%

Modules by API surface

rust/src/coroutine.rs19 symbols
rust/integration_tests/src/lib.rs13 symbols
rust/src/yielding.rs12 symbols
rust/src/builder.rs9 symbols
rust/src/start_coroutine.rs3 symbols
rust/src/start_async_task.rs3 symbols
rust/src/lib.rs1 symbols

For agents

$ claude mcp add gdext_coroutines \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact