Houtamelo / gdext_coroutines

"Run Rust coroutines in Godot 4.2+ (through GDExtension), inspired on Unity's Coroutines design."
MIT License
18 stars 4 forks source link

proposal: make it possible to immediatly execute a coroutine in the context of the caller #5

Open 0x53A opened 2 weeks ago

0x53A commented 2 weeks ago

Currently, spawning a new coroutine creates a new godot node, and the coroutine is executed for the first time on the next invocation of [physics_]process.

I noticed this behavior doing something stupid:


let bullet = bullet_scene.instantiate_as::<Node2D>();
parent.add_child(bullet);

// pseudo code
start_coroutine(|| {
    bullet.set_position(/* ... */)
});

this would cause flickering, with the bullet being at position (0|0) for a single frame before being moved to where it's supposed to be.

Of course, there are two simple workarounds, either set the initial position outside of the coroutine, or spawn the bullet inside the coroutine.

In my fork I added a way to immediatly run the coroutine up until the first yield point by replacing auto_start: bool with auto_start: CoroutineStartMode

#[derive(Copy, Clone, PartialEq)]
pub enum CoroutineStartMode {
    /// the coroutine will be created in a paused state and needs to be unpaused at a later point.
    Paused,
    /// the coroutine will be created and attached to the godot node tree. It will be started on the next (physics_)process cycle.
    OnNextProcess,
    /// the coroutine will be created and immediatly run in the context of the caller.
    Immediatly
}

https://github.com/Houtamelo/gdext_coroutines/commit/ea23c8ca5db91fa5f8cdfb9fac9dab85b093ea00

Conceptually that would be similar to the way c# async works.

What do you think? I think it's useful but I also don't necessarily want to bloat your project with all my weird features.

Houtamelo commented 1 week ago

Currently, spawning a new coroutine creates a new godot node, and the coroutine is executed for the first time on the next invocation of [physics_]process.

I noticed this behavior doing something stupid:

let bullet = bullet_scene.instantiate_as::<Node2D>();
parent.add_child(bullet);

// pseudo code
start_coroutine(|| {
    bullet.set_position(/* ... */)
});

this would cause flickering, with the bullet being at position (0|0) for a single frame before being moved to where it's supposed to be.

Of course, there are two simple workarounds, either set the initial position outside of the coroutine, or spawn the bullet inside the coroutine.

In my fork I added a way to immediatly run the coroutine up until the first yield point by replacing auto_start: bool with auto_start: CoroutineStartMode

#[derive(Copy, Clone, PartialEq)]
pub enum CoroutineStartMode {
  /// the coroutine will be created in a paused state and needs to be unpaused at a later point.
  Paused,
  /// the coroutine will be created and attached to the godot node tree. It will be started on the next (physics_)process cycle.
  OnNextProcess,
  /// the coroutine will be created and immediatly run in the context of the caller.
  Immediatly
}

ea23c8c

Conceptually that would be similar to the way c# async works.

What do you think? I think it's useful but I also don't necessarily want to bloat your project with all my weird features.

@0x53A I'm not sure if I like this, the reason I made the choice to only run in the next _process was to avoid errors with double bind_mut, since you can accidentally attempt to bind "already-bound" objects (like the parent) when creating the coroutine.