TimerLib — Timer Control Library

Non-visual controls that execute a callback function at regular intervals. Timers are the backbone of animations, periodic UI updates, background polling, auto-save features, countdowns, and any time-based behavior. Unlike blocking loops, timers work cooperatively with the event loop so the UI stays responsive. 15 functions.

CategoryCountDescription
Error Handling2timer_error, timer_error$
Creation & Destruction2timer#, timer_free#
Properties6enabled, interval, tag (get/set)
Control3start#, stop#, restart#
Event2ontimer# (set), ontimer$ (get)

Cross-Platform Support

PlatformStatusNotes
Windows✅ Full SupportWin32/Win64
Linux✅ Full SupportGTK-based
Android✅ Full SupportMain thread timer

Error Handling

FunctionSignatureDescription
timer_error()timer_error@Last error code (0 = no error)
timer_error$()timer_error$@Last error message as string
ⓘ Note: TimerLib uses a simplified two-function error system. Check timer_error() after operations; 0 means success.

Creation & Destruction

FunctionSignatureDescription
timer#()timer#@Create a new timer (disabled by default)
timer_free#(tmr#)timer_free#@#Destroy timer and release resources
╯ plan9basic
' Create a timer, configure, and start
let tmr# = timer#()
timer_interval#(tmr#, 1000)       ' Fire every 1 second
timer_ontimer#(tmr#, "OnTimer")   ' Set callback
timer_start#(tmr#)                 ' Begin

' When done
timer_stop#(tmr#)
timer_free#(tmr#)
ⓘ Note: Timers are disabled by default. You must call timer_start# (or timer_enabled#(tmr#, 1)) to begin firing events.
⚠ Warning: Always call timer_stop# or timer_free# when a timer is no longer needed. Orphaned running timers consume CPU and may fire callbacks after controls have been freed.

Properties

FunctionSignatureDescription
timer_enabled(tmr#)timer_enabled@#Get enabled state (0=stopped, 1=running)
timer_enabled#(tmr#, n)timer_enabled#@#nSet enabled state (0=stop, 1=start)
timer_interval(tmr#)timer_interval@#Get interval in milliseconds
timer_interval#(tmr#, ms)timer_interval#@#nSet interval in milliseconds
timer_tag(tmr#)timer_tag@#Get user-defined integer tag
timer_tag#(tmr#, n)timer_tag#@#nSet user-defined integer tag

Recommended Intervals

Intervalms ValueUse Case
~60 FPS16Smooth animation (shapes, sprites, movement)
~30 FPS33Moderate animation (sufficient for most visual effects)
10× per second100Stopwatch display, fast UI updates
1× per second1000Clocks, countdowns, status polling
Several seconds3000–5000Auto-save, background sync
Minutes60000Periodic cleanup, session keep-alive
⚠ Warning: Never use a blocking loop for animation or delays. A FOR loop that waits will freeze the entire UI. Always use a timer with the appropriate interval instead.
╯ animation-pattern.bas
' ❌ WRONG - freezes the UI
for i = 1 to 100
    circle_x#(ball#, i * 3)
    ' UI is frozen during this loop!
next

' ✅ CORRECT - timer-driven animation
let tmr# = timer#()
timer_interval#(tmr#, 16)          ' ~60 FPS
timer_ontimer#(tmr#, "Animate")
timer_start#(tmr#)

function Animate(sender#)
    posX = posX + speed
    circle_x#(ball#, posX)
endfunction

Control

FunctionSignatureDescription
timer_start#(tmr#)timer_start#@#Start the timer (equivalent to timer_enabled#(tmr#, 1))
timer_stop#(tmr#)timer_stop#@#Stop the timer (equivalent to timer_enabled#(tmr#, 0))
timer_restart#(tmr#)timer_restart#@#Stop and immediately restart the timer
╯ plan9basic
' Start/stop pattern
timer_start#(tmr#)   ' Begin firing
timer_stop#(tmr#)    ' Pause firing
timer_start#(tmr#)   ' Resume from where interval was

' Restart resets the interval clock
timer_restart#(tmr#) ' Guarantees full interval before next fire
ⓘ Note: timer_restart# is useful when you want to reset the countdown. For example, in an auto-save feature, restart the timer whenever the user types — this ensures the save fires only after the user has been idle for the full interval.

Event Callback

FunctionSignatureDescription
timer_ontimer#(tmr#, func$)timer_ontimer#@#$Set the OnTimer callback function name
timer_ontimer$(tmr#)timer_ontimer$@#Get the current callback function name

Callback Signature

╯ plan9basic
function OnTimer(sender#)
    ' sender# is the timer pointer that fired
    ' Use timer_tag(sender#) to identify which timer
endfunction
ⓘ Note: Use timer_tag# to distinguish multiple timers that share the same callback. Read it in the handler with timer_tag(sender#).

Common Timer Patterns

PatternIntervalDescription
Animation16–33 msMove shapes, update positions each frame. Stop timer when animation completes.
Clock / Countdown1000 msUpdate a label every second. Decrement counter, stop at zero.
Stopwatch100 msAccumulate elapsed time. Display minutes:seconds.tenths.
Auto-save3000–5000 msCheck a “dirty” flag; save only if content changed. Use restart# on each edit.
Polling1000–5000 msPeriodically check external state (file, network, sensor).
Debounce300–500 msStart a one-shot timer on user input. Restart on each keystroke. Fire only when user stops typing.

One-Shot Timer (Fire Once)

╯ oneshot.bas
' Timer that fires once then stops itself
function OnDelayComplete(sender#)
    timer_stop#(sender#)
    timer_free#(sender#)
    println "Delayed action executed!"
endfunction

let delay# = timer#()
timer_interval#(delay#, 2000)       ' 2-second delay
timer_ontimer#(delay#, "OnDelayComplete")
timer_start#(delay#)
ⓘ Note: Plan9Basic timers are repeating by default. For a one-shot timer, call timer_stop#(sender#) inside the callback to stop it after the first fire.

Debounce Pattern (Auto-Save on Idle)

╯ debounce.bas
' Restart timer on every edit; fires only after 3s of inactivity
let tmrSave# = timer#()
timer_interval#(tmrSave#, 3000)
timer_ontimer#(tmrSave#, "OnAutoSave")
timer_start#(tmrSave#)

function OnTextChange(sender#)
    dirty = 1
    timer_restart#(tmrSave#)   ' Reset the 3s countdown
endfunction

function OnAutoSave(sender#)
    if dirty = 1 then
        dirty = 0
        println "Saved at " + formatdatetime$("hh:nn:ss", now())
    endif
endfunction

Complete Examples

Countdown Timer

╯ countdown.bas
let timeLeft = 10
let running = 0

function OnTick(sender#)
    timeLeft = timeLeft - 1
    label_text#(lblTime#, str$(timeLeft))
    if timeLeft <= 0 then
        timer_stop#(tmr#)
        label_text#(lblTime#, "Done!")
        running = 0
    endif
endfunction

function OnStart(sender#)
    if running = 0 then
        if timeLeft > 0 then
            running = 1
            timer_start#(tmr#)
        endif
    else
        running = 0
        timer_stop#(tmr#)
    endif
endfunction

function OnReset(sender#)
    timer_stop#(tmr#)
    running = 0
    timeLeft = 10
    label_text#(lblTime#, str$(timeLeft))
endfunction

let frm# = form#("Countdown", 300, 200)
form_position#(frm#, 4)

let lblTime# = label#(frm#, str$(timeLeft))
label_bounds#(lblTime#, 100, 40, 100, 50)
label_fontsize#(lblTime#, 36)
label_textalign#(lblTime#, 0)

let btnStart# = button#(frm#, "Start", 60, 120, 80, 35)
let btnReset# = button#(frm#, "Reset", 160, 120, 80, 35)

let tmr# = timer#()
timer_interval#(tmr#, 1000)
timer_ontimer#(tmr#, "OnTick")

button_onclick#(btnStart#, "OnStart")
button_onclick#(btnReset#, "OnReset")

form_show(frm#)

while form_visible(frm#) = 1
    processmessages()
end while

Digital Clock

╯ clock.bas
function UpdateClock(sender#) local t$
    t$ = formatdatetime$("hh:nn:ss", now())
    label_text#(lblClock#, t$)
endfunction

let frm# = form#("Digital Clock", 300, 150)
form_position#(frm#, 4)

let lblClock# = label#(frm#, "")
label_bounds#(lblClock#, 20, 40, 260, 50)
label_fontsize#(lblClock#, 36)
label_fontfamily#(lblClock#, "Consolas")
label_textalign#(lblClock#, 0)

let tmr# = timer#()
timer_interval#(tmr#, 1000)
timer_ontimer#(tmr#, "UpdateClock")
timer_start#(tmr#)
UpdateClock(tmr#)  ' Initial display

form_show(frm#)

while form_visible(frm#) = 1
    processmessages()
end while

Bouncing Ball Animation

╯ bounce.bas
let posX = 50
let direction = 1
let speed = 5

function Animate(sender#)
    posX = posX + (speed * direction)
    if posX >= 350 then direction = -1
    if posX <= 10 then direction = 1
    circle_x#(ball#, posX)
endfunction

let frm# = form#("Animation", 400, 300)
form_position#(frm#, 4)

let ball# = circle#(frm#, 50, 130, 40, 40)
circle_fillcolor#(ball#, "#FF0000")

let tmr# = timer#()
timer_interval#(tmr#, 16)   ' ~60 FPS
timer_ontimer#(tmr#, "Animate")
timer_start#(tmr#)

form_show(frm#)

while form_visible(frm#) = 1
    processmessages()
end while

Stopwatch with Laps

╯ stopwatch.bas
let elapsed = 0
let running = 0

function FormatTime$(ms) local mins, secs, tenths
    mins = int(ms / 60000)
    secs = int((ms mod 60000) / 1000)
    tenths = int((ms mod 1000) / 100)
    return right$("0" + str$(mins), 2) + ":" + right$("0" + str$(secs), 2) + "." + str$(tenths)
endfunction

function OnTick(sender#)
    elapsed = elapsed + 100
    label_text#(lblTime#, FormatTime$(elapsed))
endfunction

function OnStartStop(sender#)
    if running = 0 then
        running = 1
        timer_start#(tmr#)
        button_text#(btnSS#, "Stop")
    else
        running = 0
        timer_stop#(tmr#)
        button_text#(btnSS#, "Start")
    endif
endfunction

function OnReset(sender#)
    timer_stop#(tmr#)
    running = 0
    elapsed = 0
    label_text#(lblTime#, "00:00.0")
    button_text#(btnSS#, "Start")
endfunction

function OnLap(sender#)
    if running = 1 then
        println "Lap: " + FormatTime$(elapsed)
    endif
endfunction

let frm# = form#("Stopwatch", 350, 200)
form_position#(frm#, 4)

let lblTime# = label#(frm#, "00:00.0")
label_bounds#(lblTime#, 75, 30, 200, 50)
label_fontsize#(lblTime#, 36)
label_fontfamily#(lblTime#, "Consolas")
label_textalign#(lblTime#, 0)

let btnSS# = button#(frm#, "Start", 50, 110, 80, 35)
let btnReset# = button#(frm#, "Reset", 140, 110, 80, 35)
let btnLap# = button#(frm#, "Lap", 230, 110, 80, 35)

let tmr# = timer#()
timer_interval#(tmr#, 100)
timer_ontimer#(tmr#, "OnTick")

button_onclick#(btnSS#, "OnStartStop")
button_onclick#(btnReset#, "OnReset")
button_onclick#(btnLap#, "OnLap")

form_show(frm#)

while form_visible(frm#) = 1
    processmessages()
end while

Multiple Timers with Shared Callback

╯ multitimer.bas
let counter1 = 0
let counter2 = 0

function OnTimer(sender#) local tag
    tag = timer_tag(sender#)
    if tag = 1 then
        counter1 = counter1 + 1
        label_text#(lbl1#, "Fast: " + str$(counter1))
    elseif tag = 2 then
        counter2 = counter2 + 1
        label_text#(lbl2#, "Slow: " + str$(counter2))
    endif
endfunction

function OnStart(sender#)
    timer_start#(tmr1#)
    timer_start#(tmr2#)
endfunction

function OnStop(sender#)
    timer_stop#(tmr1#)
    timer_stop#(tmr2#)
endfunction

let frm# = form#("Multiple Timers", 400, 200)
form_position#(frm#, 4)

let lbl1# = label#(frm#, "Fast: 0")
label_bounds#(lbl1#, 50, 30, 200, 30)

let lbl2# = label#(frm#, "Slow: 0")
label_bounds#(lbl2#, 50, 70, 200, 30)

let tmr1# = timer#()
timer_interval#(tmr1#, 100)
timer_tag#(tmr1#, 1)
timer_ontimer#(tmr1#, "OnTimer")

let tmr2# = timer#()
timer_interval#(tmr2#, 500)
timer_tag#(tmr2#, 2)
timer_ontimer#(tmr2#, "OnTimer")

let btnStart# = button#(frm#, "Start All", 50, 130, 100, 35)
let btnStop# = button#(frm#, "Stop All", 160, 130, 100, 35)
button_onclick#(btnStart#, "OnStart")
button_onclick#(btnStop#, "OnStop")

form_show(frm#)

while form_visible(frm#) = 1
    processmessages()
end while

Best Practices

PracticeWhy
Never use blocking loops for animationFOR loops freeze the UI. Always use a timer with 16–33 ms interval.
Use the smallest practical interval16 ms for smooth animation, 100–1000 ms for UI updates. Faster timers use more CPU.
Stop timers when not neededRunning timers consume CPU even if the callback does nothing.
Use timer_restart# for debouncingResets the countdown — perfect for auto-save after idle.
Use timer_tag# for shared callbacksIdentify which timer fired with timer_tag(sender#).
Keep callbacks fastLong-running callbacks block the UI thread. For heavy work, set a flag and process it in the main loop.
Call timer_free# when donePrevents orphaned timers from firing after controls are freed.
Call the callback manually for initial statee.g., UpdateClock(tmr#) before timer_start# to avoid a 1-second blank.

Quick Reference

FunctionSignatureDescription
timer_error() / timer_error$()variousError handling (2)
timer#()timer#@Create timer (disabled by default)
timer_free#(tmr#)timer_free#@#Destroy timer
timer_enabled (get/set)variousEnabled state (2)
timer_interval (get/set)variousInterval in ms (2)
timer_tag (get/set)variousUser-defined tag (2)
timer_start#(tmr#)timer_start#@#Start timer
timer_stop#(tmr#)timer_stop#@#Stop timer
timer_restart#(tmr#)timer_restart#@#Stop and restart
timer_ontimer# / timer_ontimer$variousEvent callback set/get (2)

15 functions. Part of the Plan9Basic GUI library system.