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.
| Category | Count | Description |
|---|---|---|
| Error Handling | 2 | timer_error, timer_error$ |
| Creation & Destruction | 2 | timer#, timer_free# |
| Properties | 6 | enabled, interval, tag (get/set) |
| Control | 3 | start#, stop#, restart# |
| Event | 2 | ontimer# (set), ontimer$ (get) |
Cross-Platform Support
| Platform | Status | Notes |
|---|---|---|
| Windows | ✅ Full Support | Win32/Win64 |
| Linux | ✅ Full Support | GTK-based |
| Android | ✅ Full Support | Main thread timer |
Error Handling
| Function | Signature | Description |
|---|---|---|
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
| Function | Signature | Description |
|---|---|---|
timer#() | timer#@ | Create a new timer (disabled by default) |
timer_free#(tmr#) | timer_free#@# | Destroy timer and release resources |
' 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
| Function | Signature | Description |
|---|---|---|
timer_enabled(tmr#) | timer_enabled@# | Get enabled state (0=stopped, 1=running) |
timer_enabled#(tmr#, n) | timer_enabled#@#n | Set enabled state (0=stop, 1=start) |
timer_interval(tmr#) | timer_interval@# | Get interval in milliseconds |
timer_interval#(tmr#, ms) | timer_interval#@#n | Set interval in milliseconds |
timer_tag(tmr#) | timer_tag@# | Get user-defined integer tag |
timer_tag#(tmr#, n) | timer_tag#@#n | Set user-defined integer tag |
Recommended Intervals
| Interval | ms Value | Use Case |
|---|---|---|
| ~60 FPS | 16 | Smooth animation (shapes, sprites, movement) |
| ~30 FPS | 33 | Moderate animation (sufficient for most visual effects) |
| 10× per second | 100 | Stopwatch display, fast UI updates |
| 1× per second | 1000 | Clocks, countdowns, status polling |
| Several seconds | 3000–5000 | Auto-save, background sync |
| Minutes | 60000 | Periodic 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.' ❌ 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
| Function | Signature | Description |
|---|---|---|
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 |
' 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
| Function | Signature | Description |
|---|---|---|
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
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
| Pattern | Interval | Description |
|---|---|---|
| Animation | 16–33 ms | Move shapes, update positions each frame. Stop timer when animation completes. |
| Clock / Countdown | 1000 ms | Update a label every second. Decrement counter, stop at zero. |
| Stopwatch | 100 ms | Accumulate elapsed time. Display minutes:seconds.tenths. |
| Auto-save | 3000–5000 ms | Check a “dirty” flag; save only if content changed. Use restart# on each edit. |
| Polling | 1000–5000 ms | Periodically check external state (file, network, sensor). |
| Debounce | 300–500 ms | Start a one-shot timer on user input. Restart on each keystroke. Fire only when user stops typing. |
One-Shot Timer (Fire Once)
' 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)
' 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
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
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
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
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
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
| Practice | Why |
|---|---|
| Never use blocking loops for animation | FOR loops freeze the UI. Always use a timer with 16–33 ms interval. |
| Use the smallest practical interval | 16 ms for smooth animation, 100–1000 ms for UI updates. Faster timers use more CPU. |
| Stop timers when not needed | Running timers consume CPU even if the callback does nothing. |
Use timer_restart# for debouncing | Resets the countdown — perfect for auto-save after idle. |
Use timer_tag# for shared callbacks | Identify which timer fired with timer_tag(sender#). |
| Keep callbacks fast | Long-running callbacks block the UI thread. For heavy work, set a flag and process it in the main loop. |
Call timer_free# when done | Prevents orphaned timers from firing after controls are freed. |
| Call the callback manually for initial state | e.g., UpdateClock(tmr#) before timer_start# to avoid a 1-second blank. |
Quick Reference
| Function | Signature | Description |
|---|---|---|
timer_error() / timer_error$() | various | Error handling (2) |
timer#() | timer#@ | Create timer (disabled by default) |
timer_free#(tmr#) | timer_free#@# | Destroy timer |
timer_enabled (get/set) | various | Enabled state (2) |
timer_interval (get/set) | various | Interval in ms (2) |
timer_tag (get/set) | various | User-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$ | various | Event callback set/get (2) |
15 functions. Part of the Plan9Basic GUI library system.