Coroutines
Coroutines let scripts do work across multiple frames without blocking the host. Any function that contains yield is implicitly a coroutine — no special declaration or wrapping needed.
The host application sets up a tick source once during initialization — after that, coroutines advance automatically. If you’re embedding Writ yourself, see Runtime & Memory for the one-time setup.
Starting a coroutine
Section titled “Starting a coroutine”Use start to launch a coroutine in the background. Execution returns to the caller immediately:
func openDoor() { playAnimation("door_open") yield waitForSeconds(2.0) // wait 2 seconds, then continue setCollider(false) playAnimation("door_idle")}
start openDoor() // non-blockingdoSomethingElse() // runs right awayYield variants
Section titled “Yield variants”yield // wait one frameyield waitForSeconds(2.5) // wait N secondsyield waitForFrames(10) // wait N framesyield waitUntil(() => enemy == null) // wait for a condition
yield otherCoroutine() // wait for another coroutine to finishWaiting for a coroutine
Section titled “Waiting for a coroutine”yield on a coroutine call suspends the current coroutine until the child finishes:
func spawnAndWait() { yield spawnEnemy() // wait for spawnEnemy to complete print("Spawn done")}
func spawnEnemy() { playAnimation("spawn") yield waitForSeconds(1.0) setActive(true)}Return values
Section titled “Return values”Coroutines can return values. Use yield at the call site to receive them:
func getInput() -> string { yield waitForKeyPress() return lastKey}
let key = yield getInput()print("Pressed: " .. key)Structured lifetime
Section titled “Structured lifetime”Coroutines are tied to their owning object. When the object is destroyed, all its coroutines are automatically cancelled — including any child coroutines they started.
class Door extends Entity { func interact() { start openSequence() // tied to this Door }
func openSequence() { yield waitForSeconds(0.5) playSound("creak") yield waitForSeconds(1.5) setCollider(false) }}If the Door entity is destroyed while openSequence is waiting, the coroutine is cancelled cleanly. No callbacks, no manual cleanup.
Common patterns
Section titled “Common patterns”Countdown
Section titled “Countdown”func countdown(from: int) { var n = from while n > 0 { print(n) yield waitForSeconds(1.0) n -= 1 } print("Go!")}
start countdown(from: 3)Delayed action
Section titled “Delayed action”func delayedHeal(amount: float, after: float) { yield waitForSeconds(after) health += amount}
start delayedHeal(amount: 20.0, after: 3.0)Wait for condition
Section titled “Wait for condition”func waitForDeath() { yield waitUntil(() => health <= 0) playDeathAnimation() yield waitForSeconds(2.0) despawn()}
start waitForDeath()Sequential steps
Section titled “Sequential steps”func bossIntro() { yield fadeIn() yield waitForSeconds(1.0) yield roar() yield waitForSeconds(0.5) setPhase(1)}