Saving settings
One option per control, and the value survives between sessions.
One option#
Give any stateful control Save = true. Ember writes the value to disk and
reads it back the next time your script runs.
main:Toggle({
Text = "God mode",
Save = true,
Callback = function(on) godmode = on end,
})You do not name the key. Ember builds one from where the control sits —
Window/Section/Group/Kind/Text — which is unique without you having to invent
anything or remember what you already used.
A restored value calls your Callback#
When a control's value comes off disk, Ember calls that control's Callback
once with the restored value. The switch comes back on and your variable holds
true, so the control and your script cannot disagree.
This is separate from FireOnStart, which fires the callback with whatever the
control holds even when nothing was saved.
The initial calls run in creation order, one control at a time, so a control
built later sees the state an earlier restore already applied. Each call is
wrapped, and a callback that errors warns with [Ember] initial callback:
rather than stopping the rest of the window from building.
Controls that get the call: Toggle, Slider, Input, Dropdown,
ColorPicker, Palette and Status.
Keybind is the exception
A Keybind's Callback means "the bound key was pressed", not "here is a new
value". A saved keybind is restored to its key without the callback running.
What can be saved#
Toggle, Slider, Input, Dropdown, Keybind, ColorPicker, Palette and
Status. Buttons, titles and separators hold no value, so Save does nothing
on them.
main:Slider({ Text = "Speed", Min = 16, Max = 250, Save = true })
main:Dropdown({ Text = "Theme", Options = Ember.ThemeNames(), Save = true })
main:ColorPicker({ Text = "ESP", Save = true })What the derived key looks like#
Window/Section/Group/Kind/Text. The group segment is left out for a control
that is not in one, and lower-cased when it is there. The window segment is the
window's Name, or its Title when there is no Name.
local win = Ember.new({ Title = "My Script" })
local main = win:Section("Main", "home")
main:Toggle({ Text = "God mode", Save = true })
-- key: "My Script/Main/Toggle/God mode"
local advanced = main:Group({ Text = "Advanced" })
advanced:Toggle({ Text = "Debug logging", Save = true })
-- key: "My Script/Main/advanced/Toggle/Debug logging"Being built from the window means two scripts on one executor cannot overwrite each other, and being built from the section and group means the same label in two places stays two values.
When to write the key yourself#
There is one thing the derived key cannot survive: renaming. Change the window,
the section, the group or the label and the key changes with it, so the old
value is orphaned and the control comes back at its Default. Nothing breaks,
but the setting quietly resets.
So write a key for the handful of settings that would annoy someone to lose,
and leave the rest on Save = true:
main:Toggle({ Text = "God mode", Save = "godmode" })A string means exactly itself — no window, section or label goes into it — so you can rename or move the control freely and rearrange the whole window without anyone losing what they had set.
Where it goes#
By default a folder called ember in your executor's workspace, holding
settings.json. Change either with Ember.Configure:
Ember.Configure({
Storage = {
Folder = "myscript", -- workspace/myscript/
File = "config", -- workspace/myscript/config.json
},
})Call Configure before Ember.new, so the first window already knows where
to look.
| Option | Type | Default | What it does |
|---|---|---|---|
Folder | string | "ember" | Folder under the executor's workspace. |
File | string | "settings" | File name inside that folder, written as .json. |
Enabled, Debounce, Unique and Owner are in the
configuration reference.
Two scripts, one folder#
Every Ember script on the executor shares one settings file unless you say otherwise, so give each script its own folder:
Ember.Configure({
Storage = { Folder = "bloxburg-helper", Unique = true },
})Unique = true claims the folder by writing a .owner file into it. If another
owner already holds it, yours becomes bloxburg-helper_2, then _3, up to 50
attempts. Owner sets the identity written into that file and defaults to the
folder name.
Claiming needs isfolder, makefolder and writefile. Without them the claim
fails and nothing is saved that session.
Writes are debounced#
Dragging a saved slider does not write on every frame. Writes are collected and flushed 0.4 seconds after the last change. Raise it if you are writing a lot:
Ember.Configure({ Storage = { Debounce = 1.0 } })Destroying a window flushes any pending write first, so re-running your script within the debounce does not lose the last change.
When there is no filesystem#
Saving needs writefile, readfile and isfile. If the executor is missing
any of the three, Ember runs without persistence. The window works and controls
still fire; nothing is kept between runs.
Ember.Store.available() --> true when those three existisfolder and makefolder are used when they exist. delfile is needed to
delete anything.
To turn saving off:
Ember.Configure({ Storage = { Enabled = false } })
Ember.Configure({ Storage = false }) -- the same thing, shorterEnabled = false stops reads as well as writes, so controls start from their
Default.
When a write fails anyway#
Store.available() says the functions exist, not that they work. A failed read
or write warns once per session and stores the reason:
Ember.Store.lastError --> nil, or why the last read or write failedA settings file that will not parse is copied to settings.json.corrupt before
anything else touches it. The controls fall back to their defaults for that run.
Reading and writing directly#
For state with no control behind it, like a list of favourites or a session
counter, Ember.Store writes a file of its own in the same folder, one file per
name:
Ember.Store.set("favourites", { "Kitchen", "Attic" })
local favourites = Ember.Store.get("favourites") --> table
Ember.Store.clear("favourites") -- deletes favourites.jsonValues must be JSON-encodable: strings, numbers, booleans, and tables of those.
Anything outside A-Z a-z 0-9 _ - in the name becomes _ in the file name.
Inside the settings file#
Ember.Persist is the layer every Save key goes through. Control values are
keys inside one file, so Store.clear cannot reach an individual control and
Persist can.
| Call | What it does |
|---|---|
Ember.Persist.get(key, fallback) | Reads one saved value |
Ember.Persist.set(key, value) | Writes one, debounced. nil marks it cleared, so it reads back as nil instead of falling through to a Default. |
Ember.Persist.flush() | Writes now instead of waiting out the debounce |
Ember.Persist.forget() | Deletes the settings file and empties the in-memory copy |
Ember.Persist.key(opts, section, kind) | The key a control's options would produce |
Ember.Persist.wasRestored(key) | Whether that key came off disk, rather than falling back to a default |
Writes merge over what is on disk, so one script saving does not drop the keys another script wrote to the same file.
A pair of housekeeping buttons:
settings:Button({
Text = "Flush saved settings",
ButtonText = "Flush",
Callback = function() Ember.Persist.flush() end,
})
settings:Button({
Text = "Clear saved settings",
ButtonText = "Forget",
Danger = true,
Callback = function() Ember.Persist.forget() end,
})Controls already on screen keep their values after forget(). The file is what
was emptied. They reload from Default on the next run.