Skip to content

Script Lifecycle

Aimsense isolates scripts into private sandboxes with automatic resource tracking and UI state serialization.


Each script runs inside its own lua_State sandbox:

  • Globals declared in one script do not pollute or conflict with other scripts.
  • Engine namespaces (render, entity, ui, client) are globally accessible across all scripts.
  • Memory and state are fully scoped per script instance.

When a script unloads:

  1. UI Widgets: All custom tabs, groupboxes, widgets, and popup gears are removed from the menu.
  2. Callbacks: Callbacks registered via client.add_callback are detached.
  3. Timers: Delayed calls scheduled via client.delay_call are cancelled.
  4. Network Requests: Pending async network and http requests are cancelled to avoid invoking dead callback scopes.

Menu widgets created by scripts are automatically saved to disk:

  • Path: aimsense/scripts/data/<script_name>.cfg
  • Trigger: Serialized whenever the script unloads or when saving a cheat config in Settings.
  • Key Index: Values are mapped by Groupbox Name + Widget Name + Widget Type.

  • When a script reloads or unloads, the engine defers freeing the Lua environment by 5 seconds.
  • This prevents game crashes if in-flight callbacks finish executing during the unload operation.

Triggering reloads programmatically:

-- Unload current script
client.unload_script()
-- Reload current script
client.reload_script()

Use the unload callback to restore engine state or external changes that cannot be cleaned up automatically:

  • Restoring modified ConVars (cvar.*)
  • Releasing custom materials (material:decrement_ref_count())
  • Freeing animated GIF memory (gif:release())
local sv_cheats = cvar.sv_cheats
local original_val = sv_cheats:int()
-- Enable sv_cheats
sv_cheats:int(1)
-- Restore on unload
client.add_callback("unload", function()
sv_cheats:int(original_val)
end)