Timers
Introduction
Section titled “Introduction”SLua provides the LLTimers system for managing timers in your scripts. Unlike LSL’s single global timer, LLTimers allows you to create multiple independent timers with different intervals and callbacks, giving you precise control over scheduled execution.
Creating Timers
Section titled “Creating Timers”Recurring Timers with every()
Section titled “Recurring Timers with every()”Create a timer that fires repeatedly at a specified interval:
local handler = LLTimers:every(0.1, function(scheduled_time, interval) ll.Say(0, "Timer fires every 0.1 seconds")end)The every() method takes two parameters:
- Interval (number): Time in seconds between timer fires
- Callback function: Function called each time the timer fires
One-Time Timers with once()
Section titled “One-Time Timers with once()”Create a timer that fires only once after a delay:
local handler = LLTimers:once(2.0, function(scheduled_time) ll.Say(0, "This fires once after 2 seconds")end)One-time timers automatically remove themselves after firing. Note that the interval parameter is not passed to once() callbacks since they only fire once.
Stopping Timers
Section titled “Stopping Timers”All timer creation methods return a handler reference that you can use to stop the timer:
local handler = LLTimers:every(1.0, function() ll.Say(0, "Tick")end)
-- Later, stop the timerlocal success = LLTimers:off(handler)The off() method returns:
trueif the timer was found and removedfalseif the timer wasn’t found (already removed or never existed)
Timer Callback Parameters
Section titled “Timer Callback Parameters”Timer callbacks receive two parameters:
LLTimers:every(1.0, function(scheduled_time, interval) local current_time = os.clock() local delay = current_time - scheduled_time
ll.Say(0, "Scheduled: " .. scheduled_time) ll.Say(0, "Interval: " .. interval) ll.Say(0, "Actual delay: " .. delay)end)- scheduled_time (number): The time when the timer was logically scheduled to fire
- interval (number or nil): The timer interval (for recurring timers), or
nilfor one-time timers
The scheduled_time parameter lets you detect if your timer is running late by comparing it to the current time.
Timer Intervals
Section titled “Timer Intervals”Normal Intervals
Section titled “Normal Intervals”Any positive number specifies the interval in seconds:
LLTimers:every(0.1, callback) -- 100 millisecondsLLTimers:every(1.0, callback) -- 1 secondLLTimers:every(5.5, callback) -- 5.5 secondsZero Interval (Immediate Execution)
Section titled “Zero Interval (Immediate Execution)”An interval of 0 creates a timer that fires as soon as possible:
LLTimers:every(0, function() ll.Say(0, "Fires immediately on next tick")end)This is useful for deferring execution to the next frame while avoiding unnecessary delays.
Advanced Features
Section titled “Advanced Features”Callable Tables as Handlers
Section titled “Callable Tables as Handlers”Like LLEvents, you can use tables with __call metamethods as timer callbacks:
local counter = { count = 0}
setmetatable(counter, { __call = function(self, scheduled_time, interval) self.count = self.count + 1 ll.Say(0, "Timer fired " .. self.count .. " times") end})
LLTimers:every(1.0, counter)Removing Timers from Within Callbacks
Section titled “Removing Timers from Within Callbacks”Timers can remove themselves or other timers during execution:
local handler
handler = LLTimers:every(1.0, function() ll.Say(0, "Firing once, then stopping") LLTimers:off(handler)end)You can also modify other timers:
local timer1, timer2
timer1 = LLTimers:every(1.0, function() ll.Say(0, "Timer 1 stopping timer 2") LLTimers:off(timer2)end)
timer2 = LLTimers:every(0.5, function() ll.Say(0, "Timer 2 tick")end)Yielding to the Simulator
Section titled “Yielding to the Simulator”Timer callbacks can use coroutine.yield() to yield execution back to the simulator:
LLTimers:every(1.0, function() ll.Say(0, "Starting") coroutine.yield() -- Yields to simulator, resumes on next frame ll.Say(0, "Continued on next frame")end)This yields control back to the simulator and resumes on the next server frame, not at some arbitrary later time.
Catch-Up Behavior
Section titled “Catch-Up Behavior”If a timer is delayed by more than 2 seconds (e.g., due to script lag), it fires only once per tick instead of rapidly catching up:
-- If this timer is delayed by 10 seconds, it won't fire 100 times rapidly-- Instead, it fires once and resumes normal schedulingLLTimers:every(0.1, function() ll.Say(0, "Protected from catch-up spam")end)This prevents timer callbacks from overwhelming your script after a lag spike.
Multiple Timers
Section titled “Multiple Timers”You can create as many timers as you need, each with different intervals:
-- Fast update for smooth animationsLLTimers:every(0.05, function() -- Update animation frameend)
-- Medium update for game logicLLTimers:every(0.5, function() -- Check game stateend)
-- Slow update for housekeepingLLTimers:every(5.0, function() -- Clean up old dataend)
-- One-time delayed actionLLTimers:once(10.0, function() -- Do something after 10 secondsend)All timers run independently and maintain their own schedules.
Practical Examples
Section titled “Practical Examples”Countdown Timer
Section titled “Countdown Timer”local count = 10
local countdown = LLTimers:every(1.0, function() ll.Say(0, tostring(count)) count = count - 1
if count <= 0 then ll.Say(0, "Blast off!") LLTimers:off(countdown) endend)Delayed Execution
Section titled “Delayed Execution”function delayedSay(message, delay) LLTimers:once(delay, function() ll.Say(0, message) end)end
delayedSay("Hello in 2 seconds", 2.0)delayedSay("Hello in 5 seconds", 5.0)Periodic Health Check
Section titled “Periodic Health Check”local health = 100
LLTimers:every(1.0, function(scheduled_time, interval) health = health - 1
if health <= 0 then ll.Say(0, "Health depleted!") -- Timer continues running end
if health % 10 == 0 then ll.Say(0, "Health: " .. health) endend)Repeating Action with Pause
Section titled “Repeating Action with Pause”local active = truelocal handler
handler = LLTimers:every(1.0, function() if not active then return -- Skip execution but keep timer running end
ll.Say(0, "Active tick")end)
-- Later, pause without removing timeractive = false
-- Resumeactive = trueTimer Manager Pattern
Section titled “Timer Manager Pattern”local timers = {}
function startTimer(name, interval, callback) -- Stop existing timer with this name if timers[name] then LLTimers:off(timers[name]) end
-- Create and store new timer timers[name] = LLTimers:every(interval, callback)end
function stopTimer(name) if timers[name] then LLTimers:off(timers[name]) timers[name] = nil endend
function stopAllTimers() for name, handler in pairs(timers) do LLTimers:off(handler) end timers = {}end
-- UsagestartTimer("health", 1.0, function() ll.Say(0, "Health tick")end)
startTimer("animation", 0.05, function() -- Update animationend)
-- LaterstopTimer("health")stopAllTimers()Best Practices
Section titled “Best Practices”- Store handler references: Always store the return value from timer creation if you might need to stop it later
- Clean up timers: Remove timers you no longer need to avoid unnecessary processing
- Use appropriate intervals: Don’t use very short intervals (< 0.05s) unless necessary
- Consider one-time timers: Use
once()instead ofon()+ manual removal when appropriate - Handle delays gracefully: Use the
scheduled_timeparameter to detect and handle late execution - Avoid heavy processing: Keep timer callbacks lightweight to prevent lag
Common Patterns
Section titled “Common Patterns”Debouncing
Section titled “Debouncing”Execute an action only after activity has stopped for a certain period:
local debounceTimer
function onActivity() -- Cancel pending timer if debounceTimer then LLTimers:off(debounceTimer) end
-- Schedule new action debounceTimer = LLTimers:once(2.0, function() ll.Say(0, "Activity stopped for 2 seconds") debounceTimer = nil end)end
-- Call this whenever activity occursLLEvents:on("touch_start", function(detected) onActivity()end)Throttling
Section titled “Throttling”Limit how often an action can occur:
local canExecute = true
LLEvents:on("touch_start", function(detected) if not canExecute then ll.Say(0, "Please wait...") return end
-- Execute action ll.Say(0, "Action executed!")
-- Disable and re-enable after delay canExecute = false LLTimers:once(1.0, function() canExecute = true end)end)Timeout Pattern
Section titled “Timeout Pattern”Perform an action if something doesn’t happen within a time limit:
local timeoutTimer
function startOperation() ll.Say(0, "Operation started")
timeoutTimer = LLTimers:once(5.0, function() ll.Say(0, "Operation timed out!") timeoutTimer = nil end)end
function completeOperation() if timeoutTimer then LLTimers:off(timeoutTimer) timeoutTimer = nil ll.Say(0, "Operation completed successfully") endend
-- Start operationstartOperation()
-- If this is called within 5 seconds, no timeoutLLEvents:on("touch_start", function(detected) completeOperation()end)Retry Logic
Section titled “Retry Logic”function attemptAction(maxRetries) local retries = 0
local function tryAction() local success = math.random() > 0.7 -- Simulate success/failure
if success then ll.Say(0, "Action succeeded!") else retries = retries + 1 if retries < maxRetries then ll.Say(0, "Attempt " .. retries .. " failed, retrying in 1 second...") LLTimers:once(1.0, tryAction) else ll.Say(0, "Max retries reached, giving up") end end end
tryAction()end
attemptAction(3) -- Try up to 3 timesCommon Pitfalls
Section titled “Common Pitfalls”Forgetting to Store Handler References
Section titled “Forgetting to Store Handler References”-- ✗ Can't stop this timer laterLLTimers:every(1.0, function() ll.Say(0, "Tick")end)
-- ✓ Store the handlerlocal handler = LLTimers:every(1.0, function() ll.Say(0, "Tick")end)Creating Multiple Timers Without Cleanup
Section titled “Creating Multiple Timers Without Cleanup”-- ✗ Creates new timer on each touch, never cleans up old onesLLEvents:on("touch_start", function(detected) LLTimers:every(1.0, function() ll.Say(0, "Timer tick") end)end)
-- ✓ Manage timer lifecycle properlylocal handler
LLEvents:on("touch_start", function(detected) if handler then LLTimers:off(handler) end handler = LLTimers:every(1.0, function() ll.Say(0, "Timer tick") end)end)Using Very Short Intervals Unnecessarily
Section titled “Using Very Short Intervals Unnecessarily”-- ✗ Excessive processing for most use casesLLTimers:every(0.01, function() -- This fires 100 times per second!end)
-- ✓ Use reasonable intervalsLLTimers:every(0.1, function() -- 10 times per second is usually enoughend)Comparison with LSL timer Event
Section titled “Comparison with LSL timer Event”Unlike LSL’s single global timer:
- Multiple timers: Create as many timers as you need
- Independent intervals: Each timer has its own interval
- Easy management: Start and stop individual timers without affecting others
- No state changes needed: Timers work without state machinery
-- In SLua, these all run independentlyLLTimers:every(1.0, function() ll.Say(0, "Timer 1") end)LLTimers:every(2.0, function() ll.Say(0, "Timer 2") end)LLTimers:every(5.0, function() ll.Say(0, "Timer 3") end)Summary
Section titled “Summary”The LLTimers system provides flexible timer management in SLua:
- Use
LLTimers:every()for recurring timers - Use
LLTimers:once()for one-time delayed execution - Use
LLTimers:off()to stop timers - Multiple independent timers can run simultaneously
- Timer callbacks receive
scheduled_timeandintervalparameters - Timers can be modified during execution
- Catch-up protection prevents timer spam after lag
This system gives you precise control over scheduled execution without the limitations of LSL’s single timer.
This guide covers the LLTimers system for timer management in SLua. For event handling, see the Events documentation.