Skip to content

script ​

Script lifecycle, update callbacks, timers, and cleanup.

Lifecycle reference ​

script.name ​

Entry filename as a string.

text
script.name

script.api_version ​

Integer 2 in this implementation.

text
script.api_version

script.on_tick ​

Registers a recurring callback with zero delay; returns nothing. Multiple registrations accumulate.

text
script.on_tick(callback)

script.every ​

Registers a recurring callback; returns nothing.

text
script.every(delay_ms, callback)

script.after ​

Registers a one-shot callback; returns nothing.

text
script.after(delay_ms, callback)

script.on_stop ​

Sets the single cleanup callback, replacing any previous one; returns nothing.

text
script.on_stop(callback)

script.stop ​

Requests shutdown of this script; returns nothing.

text
script.stop()

All callbacks above receive no arguments. Delay values are integer milliseconds in 0..86,400,000, inclusive. A zero-delay recurring callback runs at most once per script update. Timers are driven by game updates; they are not independent threads, exact wall-clock alarms, or a promise of a particular frame rate.

The initial chunk, menu callbacks, timer callbacks, native calls, and Social Club completion callbacks execute on Harmony's script owner. Rendering and network hooks do not execute Lua callbacks directly. Work from those systems is copied or queued for the script owner.

Timer behavior ​

Recurring timers schedule their next deadline from the current update time. They do not replay every missed interval after a pause. Timers created inside a timer callback become eligible on a later update. A zero-delay timer registered during initial loading or a menu callback can run during the later timer pass of that same update.

There is no timer handle or public timer-cancellation function. Use a boolean to make an existing recurring callback idle; use script.stop() to stop the entire script. Do not register a new recurring callback every frame.

lua
local page = menu.root("Timer example")
local enabled = false
local elapsed = 0

page:toggle("Count seconds", false, function(value)
    enabled = value
end)

script.every(1000, function()
    if enabled then
        elapsed = elapsed + 1
        util.log("Active seconds: " .. tostring(elapsed))
    end
end)

script.after(3000, function()
    util.log("The one-shot timer has run")
end)

Use these scheduling functions instead of busy loops, sleeps, or BUILTIN.WAIT. The WAIT native is explicitly rejected by the Lua native bridge.

Stop, reload, and errors ​

script.stop() sets a shutdown request; it does not abort the currently executing Lua function. Return from your callback after requesting a stop. Reload tears down the previous instance and creates a fresh VM with fresh menu state.

An uncaught initial-chunk or callback error fails that script. Other scripts continue running. Cleanup removes its menu controls, draw commands, timers, callback references, network rules, native hooks, event subscriptions, HTTP callbacks, and owned input/account requests. Pending Social Club work may finish in the shared service, but its discarded callback is no longer delivered to the stopped script.

script.on_stop() is the place to restore resources or game changes your script owns. Native effects and ordinary Pointer:write_*() operations are not automatically undone. Tracked memory.patch() operations have separate conditional restoration behavior, described below. Network rules and listeners are removed before the stop callback. Creating new menu entries, timers, or requests while stopping is rejected.

lua
local page = menu.root("Lifecycle example")

script.on_stop(function()
    util.log("Stopped " .. script.name)
    -- Restore any game state or resources this script explicitly owns here.
end)

page:button("Stop this script", function()
    script.stop()
    return
end)

Each protected Lua invocation has an accidental-runaway budget of 1,000,000 instructions or 20 ms, checked every 10,000 Lua instructions. This is cooperative protection: it cannot interrupt a blocking native/C++ call and is not a hard real-time or security guarantee. Keep callbacks short and divide work across updates.