Skip to content

Frame‐Based Behavior with Coroutines

Hadrian Tang edited this page Sep 14, 2025 · 7 revisions

Coroutines in Nu

Nu has it's own specialized implementation of coroutines via F# computation expressions, similar to those as provided by Unity. Nu's coroutines provides a simple yet expressive way to compose frame-based game behavior.


Overview

The Nu coroutine's let you describe actions that unfold over several frames. Whether you need to delay an action (a "sleep"), run code on the next frame ("pass"), execute arbitrary side-effects, or combine multiple operations into a single behavior, the coroutine abstraction provides the building blocks to do so. This system helps maintain clean sequential flow in scenarios where the underlying behavior is naturally time-stepped.


Example Usage

Below is a pretty arbitrary example of how you might use this system in a Nu-based world:

coroutine (world.LauncherWhile entity.GetExists) { // coroutine is launched and continues while the given entity exists
    do! Coroutine.sleep 10L // wait for 10 frames (in StaticFrameRate mode)
    for i in 0 .. dec 30 do // do the following 30 times...
        do! Coroutine.pass // once per frame...
        if i % 2 = 0 then // on every other frame...
            World.playSound 0.25f Assets.Default.Sound world // play this default sound quietly.
    do! Coroutine.sleep 10L // wait another 10 frames
    Log.info "Wheee!" // execute an arbitrary effect (log info)
    do! Coroutine.cancel // end the coroutine...
    World.playSound 1.0f Assets.Default.Sound world } // because we're already cancelled, this last line never happens!

Explanation

  1. Launching with a World Predicate:
    coroutine (world.LauncherWhile entity.GetExists) { ... }
  • The coroutine is launched using a custom launcher that continues executing as long as entity.GetExists returns true. This ensures that all actions within the coroutine occur only if the associated entity is still present in the game world. If the entity is removed at any point, the predicate check will cancel the coroutine.
  1. Initial Delay:
    do! Coroutine.sleep 10L // wait for 10 frames (in StaticFrameRate mode)
  • The coroutine first pauses for 10 frames using the sleep command. In a StaticFrameRate environment, 10L corresponds to 10 full frames. This delay sets the stage, allowing any subsequent actions to occur after a controlled waiting period.
  1. Looping Through Frame-by-Frame Actions:
    for i in 0 .. dec 30 do // do the following 30 times...
        do! Coroutine.pass // once per frame...
        if i % 2 = 0 then // on every other frame...
            World.playSound 0.25f Assets.Default.Sound world // play this default sound quietly.
  • The for loop iterates 30 times (with dec 30 ensuring a proper count by decrementing 30 as needed).
  • Frame Progression:
    Each iteration begins with do! Coroutine.pass, which effectively delays the execution until the next frame. This construct enables per-frame processing.
  • Conditional Sound Playback:
    If the loop counter i is even (if i % 2 = 0), a quiet sound is played by calling World.playSound with a volume of 0.25f and referencing the default sound asset. This setup shows how the coroutine can interleave condition-based side effects along with frame-by-frame progression.
  1. Additional Delay and Logging Action:
    do! Coroutine.sleep 10L // wait another 10 frames
    Log.info "Wheee!" // execute an arbitrary effect (log info)
  • After the loop, the coroutine pauses again for 10 frames, allowing a brief period of inactivity or synchronization.
  • Following the delay, an arbitrary action is executed - logging the message "Wheee!". This is done through a lambda expression, demonstrating that the coroutine can seamlessly integrate any custom effect or side-effect, not just those related to gameplay timing.
  1. Cancellation and Unreachable Code:
    do! Coroutine.cancel // end the coroutine...
    World.playSound 1.0f Assets.Default.Sound world // because we're already cancelled, this last line never happens!
  • The coroutine explicitly calls cancel to terminate its own execution. This cancellation is important, as it is intended to stop any further processing of actions in the sequence.
  • Although a final sound play command is written after the cancellation, it is never reached. This underscores the mechanism’s ability to prevent unintended actions once a cancellation condition is triggered.

Nu's coroutines provide a powerful yet accessible way to control frame-based behavior. With the combination of clearly defined coroutine primitives and an expressive computation expression builder, developers can write readable, maintainable, and robust game logic that spans multiple frames. This provides a solid foundation for orchestrating complex interactions in dynamic game worlds and interactive systems.

Clone this wiki locally