Your first save
Save your own model with SaveRequestOptions, learn why a save is awaited, and what the result tells you.
The explicit API, in four lines#
using NexusForge.Persistence.Slots;
using NexusForge.Persistence.Versioning;
var slot = new SaveSlotId(class="tok-str">"default");
var request = new SaveRequestOptions { SchemaVersion = new SchemaVersion(1) };
var saved = await NexusForgePersistence.Slots.SaveAsync(slot, mySaveModel, request);
if (!saved.IsSuccess)
{
class="tok-com"> // Your game decides what the player sees; the code is stable, so it is safe to branch on.
ShowErrorToPlayer(saved.Error.Message, saved.Error.Code);
}The convenience call NexusForgePersistence.Save("player", data) is the same pipeline with the defaults filled in. Use the explicit form when you need to choose the slot, the schema version, the playtime or custom metadata.
Await it, and know why#
A save is I/O, not a frame's work. SaveAsync returns a Task<PersistenceResult<SaveCommit>>:
- What happened is in
saved.Value— bytes written, the new revision, the promotion strategy the platform reported, whether a backup was created, and whether the write was verified by reading it back. - What went wrong is in
saved.Error— a stable code such asNF.PERSIST.STORAGE.NOT_FOUNDorNF.PERSIST.STORAGE.PATH_TOO_LONG, plusError.IsRetryable.
What a save actually does#
- Project your model into a neutral document — reflection is cached per type, not per save.
- Serialize the document (JSON by default, replaceable).
- Compress, if you enabled it, then encrypt, if you enabled it — compression first, always.
- Write atomically: stage → flush → verify → promote → rotate. An interruption cannot leave a half-written save, and the previous version is retained as a backup.
Everything after step 1 is pure work over bytes, which is why Storage.OffloadWritesToThreadPool can move it off your thread without changing what a save means. See Threading.
Choosing a slot and a schema version#
var slot = new SaveSlotId(class="tok-str">"slot-03"); // validated: lowercase, digits, dot, dash, underscore
var request = new SaveRequestOptions
{
SchemaVersion = new SchemaVersion(3), // your game's version, not the framework's
CustomMetadata = new Dictionary<string, string> { [class="tok-str">"chapter"] = class="tok-str">"four" },
Playtime = TimeSpan.FromMinutes(42)
};SaveSlotId refuses a name that would be unsafe as a path segment, so a slot cannot escape the save root. Use SaveSlotId.TryCreate when the name comes from outside your code, such as a filename a player typed.
Next#
- Your first load — reading it back and handling "no save yet".
- Save and load — the full surface, including export and import.
- Schema versions — what the number means and when to change it.
Something wrong on this page? Every page here describes behaviour that is checked in the repository. If a page and the package disagree, the package wins.
Report a documentation problem · Frequently asked questions · Troubleshooting