xiao.workspace New

One persistent table per name, saved as JSON under C:\xiaohook\lua\workspace and written back automatically on Clear VM.

xiao.workspace keeps a Lua table across runs and game restarts, as a JSON file per name.

load returns the table of a name, you change it like any table, and it is written to C:\xiaohook\lua\workspace\<name>.json when you call save and automatically when the VM is cleared. The disk is only touched by load (the first time for a name), save and the automatic save: changing the table costs nothing. Do not call save every frame. For free form files, use xiao.fs.

At a glance

Function Description
xiao.workspace.load(name?) the persistent table of name, read from the disk the first time
xiao.workspace.save(name?, t?) write it now, or replace it with t and write it
Rule Value
Folder C:\xiaohook\lua\workspace, created on the first save
Default name file name of the calling script without folder and extension: aimbot.lua gives aimbot; other characters become _, cut at 64 characters
Valid name 1 to 64 characters among A-Z a-z 0-9 _ -, not a Windows device name (con, prn, aux, nul, com0 to com9, lpt0 to lpt9)
Case ignored: Profile and profile are the same table, the file uses lower case
Size at most 1 MiB (1048576 bytes) of JSON per name, for reading and writing
Format JSON indented by 2 spaces, keys sorted, final newline, so the file is readable and diffs well
Atomic write the text goes to <name>.json.tmp, then replaces <name>.json in one rename
Automatic save every time the VM is cleared (Clear VM, unload of the client, a VM cleared by a time limit), after on_unload and the unload signal
Older folder a <name>.json left in C:\xiaohook\lua\storage by an earlier build is moved here, see Moving older files

Serialization follows the conversion rules of xiao.json: values must be booleans, numbers, strings, tables or xiao.json.null. Use xiao.json.array() for a list that can be empty, and expect arrays read back to carry the xiao.json.array mark.


Functions

xiao.workspace.load(name): table

xiao.workspace.load(name?: string): table

Returns the persistent table of name. The first call of a VM reads the file, or returns a new empty table when the file does not exist; later calls return the same table without touching the disk.

Name Type Description
name string optional, defaults to the name of the calling script

Returns: the table (an object or an array, as stored). Errors:

  • invalid workspace name 'my save': 1 to 64 letters, digits, '_' or '-'
  • invalid workspace name 'con': reserved Windows device name
  • xiao.workspace: no script file to name the workspace after, pass a name: the caller is code without a file, such as a loadstring chunk run from a timer
  • workspace 'x' is not valid JSON: parse error at line 1, column 2: ...: the file is broken. It is left untouched and is never overwritten by the automatic save
  • workspace 'x' does not hold a JSON object or array
  • workspace 'x' is 2000000 bytes, the limit is 1048576
  • workspace 'x' cannot be read
  • workspace 'x' cannot be moved from lua/storage to lua/workspace: see Moving older files
  • xiao.workspace: file system unavailable: no workspace: no C:\xiaohook folder
  • xiao.workspace is not available inside a packet hook or a cmd hook
local settings = xiao.workspace.load()
settings.runs = (settings.runs or 0) + 1
print("run number", settings.runs)

xiao.workspace.save(name, t)

xiao.workspace.save(name?: string, t?: table)

Writes the table of name now. With t, t becomes the table of name first, and the next load(name) returns it. The file is replaced atomically: a crash during the write keeps the previous file.

Name Type Description
name string optional, defaults to the name of the calling script; pass nil to give t with the default name
t table optional replacement table

Returns: nothing. Errors:

  • the naming errors of load, and xiao.workspace is not available inside a packet hook or a cmd hook
  • bad argument #2 to 'save' (table expected, got number)
  • workspace 'x' is not loaded: call xiao.workspace.load first or pass a table
  • workspace 'x' not saved: cannot encode a function at $.callback and the other encode errors, without the json encode: prefix
  • workspace 'x' not saved: 1100017 bytes, the limit is 1048576
  • workspace 'x' not saved: cannot write the file, workspace 'x' not saved: cannot replace the file
xiao.workspace.save("waypoints", {home = {x = 10, y = 64, z = -20}})

Automatic save

Every time the VM is cleared, every table loaded or saved in the VM is written, after on_unload and the unload signal, so they can still change it. A table that cannot be written prints workspace 'x' not saved: ... in the console and the others are still saved. Files that failed to load are not part of it. The automatic save has its own 100 ms budget: keep tables small.

Moving older files

Earlier builds kept these files in C:\xiaohook\lua\storage. The first load or save of a name moves the file over, once:

  • C:\xiaohook\lua\storage\<name>.json is moved to C:\xiaohook\lua\workspace\<name>.json when the new file does not exist yet, with its content unchanged
  • when C:\xiaohook\lua\workspace\<name>.json already exists, it wins and the older file is left untouched
  • once the older folder is empty, it is removed; any other file keeps it in place
  • when the move fails, for example because the file is open in another program, the call raises workspace 'x' cannot be moved from lua/storage to lua/workspace and both files stay as they are

Complete example

Waypoints that survive restarts, shared by every script that uses the name waypoints.

local workspace = xiao.workspace
local points = workspace.load("waypoints")

local function add_here(label)
    local me = xiao.game.player()
    if me == nil then return false end
    points[label] = {x = math.floor(me.x), y = math.floor(me.y), z = math.floor(me.z), at = os.date("%Y-%m-%d")}
    workspace.save("waypoints")
    xiao.notify("Waypoint", label .. " saved", "success")
    return true
end

xiao.signal.add("chat", "waypoints", function(message)
    local label = message:match("^!wp (%w+)$")
    if label ~= nil then add_here(label) end
end)

for label, point in pairs(points) do
    print(label, point.x, point.y, point.z, point.at)
end