Saving data
Carts get a persistent key-value store — the cartridge’s save file. High scores, unlocked levels, settings: anything that should survive closing the console.
fn boot(&mut self, ctx: &mut Context) {
// Once, before the first update: the previous best score, if any.
self.best = ctx.storage_get("best").and_then(|v| v.as_i64()).unwrap_or(0);
}
fn update(&mut self, ctx: &mut Context) {
if self.score > self.best {
self.best = self.score;
let _ = ctx.storage_set("best", self.score);
}
}
Reading saved data is exactly the kind of thing boot is for:
a cart’s opening state is usually a constant, and no constant can ask the store
what happened last time. Whichever game! form made the state, boot runs
once on it, before anything is drawn — so there is no “is this the first
frame?” branch to write and none to pay for on every frame after it.
Keys are &str; values are primitives — integers, floats, bools — anything
that converts into a StorageValue. Reads come back as a StorageValue
with as_i64(), as_f64(), as_bool() and is_null() accessors.
storage_remove(key) deletes one entry, storage_clear() wipes the save.
(dset/dget exist as aliases for PICO-8 fingers.)
The whole API is allocation-free, so it works unchanged in the default
#![no_std] carts.
Where saves live
That’s the console’s business, not the cart’s: the desktop console and player
keep a JSON file in the user’s cache directory, keyed by the cart’s name.
The browser player keeps saves for the session only, and headless verify
keeps them in memory. Your code is the same everywhere.
The cap
The whole store, serialized, is capped at 128 KiB — a storage_set that
would exceed it returns Err(StorageFull) and stores nothing (the old value
under that key is kept). For perspective, PICO-8 gives carts 256 bytes of
save data; if you hit this limit, what you’re storing probably isn’t save
data.
You can see the full pattern in action in the
platformer: the best score is loaded once in boot,
and written back only when a run beats it.