Spawn, Timing, and Cancellation
spawn registers a task with the cooperative scheduler — it does not run
immediately. The scheduler alternates between ready tasks when the caller
invokes tick(), every, after, or at the end of a scope.
The tick() call is equivalent to coroutine.yield(0): it returns control to
the scheduler so another ready task can run. In long loops, inserting tick()
is mandatory to avoid monopolizing the CPU.
Two spawn tasks alternating with tick(); producer/consumer via a shared queue.
// Feature: spawn + tick — parallel cooperative tasks
// Syntax: `spawn { ... }` or `spawn fn_call(...)`; `tick()` yields control.
// When to use: single-threaded cooperative parallelism for
// simulations, games, animations, lightweight agents.
//
// The internal scheduler drains each task. `tick()` is equivalent to
// `coroutine.yield(0)` — hands control back to the scheduler so it
// picks another ready task. Without `tick()`, a task hogs the CPU.
fn counter(id: int, max: int) {
var n = 0
while n < max {
print("counter {id}: {n}")
n += 1
tick()
}
}
print("=== spawn + named function ===")
spawn counter(1, 3)
spawn counter(2, 3)
// `spawn { ... }` — anonymous block, direct.
print("=== spawn block ===")
spawn {
var i = 0
while i < 3 {
print("block A: {i}")
i += 1
tick()
}
}
spawn {
var i = 0
while i < 3 {
print("block B: {i}")
i += 1
tick()
}
}
// Simple producer/consumer with a shared queue.
let queue: [int] = []
var produced = 0
var consumed = 0
spawn {
var i = 0
while i < 5 {
queue.push(i)
produced += 1
tick()
i += 1
}
}
spawn {
while consumed < 5 {
if queue.len() > 0 {
let v = queue.shift()
consumed += 1
print("consumed: {v}")
}
tick()
}
}
// The scheduler drains automatically once the top-level finishes.
To schedule work over time, use every <duration> { ... } (periodic loop)
and after <duration> { ... } (single deferred execution). Both are sugar for
spawn + loop + tick(<dur>) and can coexist in parallel.
Game loop with every 50ms, one-shot firing with after, and two parallel every blocks at different rates.
// Feature: every / after — cooperative temporal loops
// Syntax: `every <duration> { body }`, `after <duration> { body }`
// When to use: games (game loop), animations, polling, lightweight
// scheduling without needing OS threads.
//
// `every` is sugar for `spawn + loop + tick(<dur>)`. `after` schedules
// the body to run a single time after the duration.
// Simple game loop: tick every 50ms until 5 frames.
var frame = 0
every 50ms {
frame += 1
print("frame {frame}")
if frame >= 5 {
break
}
}
// `after` — single deferred execution.
print("before after")
after 10ms {
print("this runs 10ms later")
}
// `every` without a duration = on every tick (most frequent).
var i = 0
every {
i += 1
print("fast tick {i}")
if i >= 3 {
break
}
}
// Combining: two `every` loops in parallel.
var a = 0
var b = 0
every 30ms {
a += 1
print("A {a}")
if a >= 3 { break }
}
every 50ms {
b += 1
print("B {b}")
if b >= 3 { break }
}
Since the model is cooperative, there is no task.kill(). Cancellation is
done via a shared flag that the task checks at each tick(). This pattern
ensures that state is never corrupted mid-way.
Flag-based cancellation and a deadline pattern with after signaling a sentinel.
// Feature: cooperative cancellation of tasks
// Syntax: shared flag/sentinel + check on every `tick()`.
// When to use: long tasks that need to stop when the user
// cancels, the window closes, or a deadline is reached.
//
// Since spawn is cooperative, cancellation is too: the task must
// ASK whether it was canceled. There is no forced kill — that is a
// feature, not a bug (avoids corrupted state).
let cancel = #{requested: false}
fn long_task(id: int) {
var step = 0
while step < 1000 {
if cancel.requested {
print("task {id} canceled at step {step}")
return
}
if step % 3 == 0 {
print("task {id}: step {step}")
}
step += 1
tick()
}
print("task {id} finished normally")
}
spawn long_task(1)
spawn long_task(2)
// After a few iterations, signal cancellation.
var cycles = 0
every 10ms {
cycles += 1
if cycles >= 3 {
cancel.requested = true
print(">> cancellation requested")
break
}
}
// Deadline pattern: cancel after X ms.
let deadline = #{expired: false}
spawn {
var n = 0
while n < 1000 {
if deadline.expired {
print("deadline reached after {n} steps")
return
}
n += 1
tick()
}
}
after 50ms {
deadline.expired = true
}
Challenge
Add a second paused flag to the cancellation example. The task should stop
printing while paused is true, but resume when reactivated.