Upgrading an old save

Migrations for a save written by an older build of your game — rename a key, add a default, convert a value, preview the change, and never half-write it.

Data and safety NexusForge Persistence 1.0.0 Updated

The shape of a migration#

A save file records the version of the data inside it. When that version is older than the one your build writes, PersistKit walks it forward — one step at a time, in memory — and a version with no migration covering it is refused rather than guessed at.

csharp
class="tok-com">// Once, during startup.
Persist.Migrations.Add(
    new SaveMigration(2, 3, class="tok-str">"score became points, and the hero gained a name")
        .Rename(class="tok-str">"score", class="tok-str">"points")
        .SetDefault(class="tok-str">"heroName", ValueKind.Text, class="tok-str">"unnamed"));

Persist.Migrations.SchemaVersion = 3;

That is the whole setup. A save written by version 2 now loads as version 3, and the file is rewritten in the new shape the next time the game saves.

The four changes, and one for anything else#

ChangeWhat it does
.Rename("score", "points")Gives a key a new name. If the new name is already in the save it is refused rather than overwriting one of them
.SetDefault("heroName", ValueKind.Text, "unnamed")Writes a value for a key the save does not have. An existing value always wins
.Remove("oldScore")Drops a key the game no longer saves
.Convert("points", ValueKind.Text, "why", …)Changes a value's kind with your own code
.Custom("why", entries => …)Anything else: reordering a list, merging two keys, rebuilding a dictionary

A conversion returns the new value, or sets a problem and returns null to refuse the whole migration. A half-converted save is worse than one that was left alone.

Preview, then apply#

csharp
SaveMigrationReport report = Persist.Migrations.Preview();
Debug.Log(report.ToText());

A preview writes nothing, and it is the same report a real migration produces, so the button and the dialog never disagree. To update the file immediately instead of waiting for the next save:

csharp
Persist.Migrations.Apply(confirm: true);

The order is fixed, and each step is there for a reason:

  1. The file is read and validated with the same code every load uses.
  2. A copy is taken and kept, so the original is recoverable even if everything after this fails.
  3. Every step runs in memory, and the result is checked.
  4. Only then is the file written, through the same safe-write path as any save.
  5. If anything failed, the copy is put back and the file is exactly as it was.

In the editor, the same two operations are on the menu: Tools → PersistKit → Migrations → Preview upgrading the current slot and Upgrade the current slot….

When it goes wrong#

CodeWhat it means
PK-MIGRATION-001No migration is registered for a step in the walk. Nothing was loaded and nothing was changed
PK-MIGRATION-002A step failed — usually a conversion refusing a value. The save is exactly as it was
PK-MIGRATION-003The migrations ran, but what they produced is not a save this build can read. Nothing was committed
PK-MIGRATION-004The migrated save could not be written, so the previous file was put back

What is deliberately not done#

  • A save from a newer build is still refused before any migration is considered (PK-VERSION-001).
  • A key a migration does not mention is left exactly where it was — unknown data is not thrown away.
  • A migration never runs against a file that has not passed the format, version and integrity checks first.

Next#

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 · Error codes · Platforms and limitations