Timers & Event Loop
StarLang has built-in timer constructs for periodic and delayed execution.
every
Repeating timer. Executes the block at a fixed interval. Returns a timer ID.
timer
One-shot timer. Executes the block once after a delay. Returns a timer ID.
schedule
Delayed repeating timer. Syntax: schedule <delay> every <interval> { block } — waits delay ms, then repeats every interval ms. Returns a timer ID.
runtime.keepAlive()
Enters the event loop. Blocks execution until runtime.exit() is called or all timers have completed.
runtime.exit()
Exits the event loop. Typically called from inside a timer callback.
runtime.cancel(id)
Cancels a timer by its ID. The timer stops firing and is deactivated.
var t:number = every 1000 {
console.log("tick")
}
timer 5000 {
runtime.cancel(t)
}
runtime.keepAlive()
Timer IDs
All timer constructs (every, timer, schedule) return a numeric ID. Capture it with a variable to cancel the timer later.
When used as a standalone statement (without assignment), the ID is discarded automatically.
Full Example
package main
fn main():void {
var t:number = every 100 {
console.log("tick")
}
timer 500 {
console.log("cancelling")
runtime.cancel(t)
}
timer 700 {
console.log("done")
runtime.exit()
}
runtime.keepAlive()
console.log("event loop ended")
}
Output:
Notes
- All time values are in milliseconds
- Timer callbacks run sequentially (no concurrent execution)
- Timer callbacks can access enclosing local variables
- Timer IDs are
numbertype (noti32) — this is intentional for consistency with timer interval values which are alsonumber
Timer Limit
Up to 16 timers can be active simultaneously. Exceeding this limit is a runtime error that halts the program:
On ESP32, 16 is sufficient for most use cases. If you need more, cancel inactive timers with runtime.cancel() to free slots.
keepAlive Requirement
Without runtime.keepAlive(), the program exits immediately after main returns — registered timers never fire:
fn main():void {
every 1000 { console.log("tick") }
# no keepAlive → program exits, no ticks printed
}
Always call runtime.keepAlive() after registering timers.
Runtime Guards
Use runtime.guard() to protect against runaway timer callbacks (infinite loops, memory exhaustion). See runtime.guard() for details.
Error Handling in Callbacks
An unhandled raise inside a timer callback stops the event loop and halts the program. Use try inside callbacks to catch errors gracefully: