~/readme

PurrSave

PurrSave is a DTO-first save/load framework for Unity.

You define stable save data types, register how they are packed, put values into a GameStream, then persist that stream with SaveFileIO.

Quick Start

1. Define Save Data

using PurrNet.Saving;
using UnityEngine;

[SaveKey("game.player")]
public struct PlayerSave
{
    public Vector3 position;
    public int health;

    [BitLoader(0)]
    public static PlayerSave LoadV0(GamePacker packer)
    {
        return new PlayerSave
        {
            position = packer.Read<Vector3>(),
            health = packer.Read<int>()
        };
    }

    [BitSaver]
    public static void Save(GamePacker packer, PlayerSave value)
    {
        packer.Write(value.position);
        packer.Write(value.health);
    }
}

SaveKey is the stable identity for the data type. Do not rename it casually; old save files depend on it.

2. Save Data

using PurrNet.Saving;
using UnityEngine;

public sealed class PlayerSaveExample : MonoBehaviour
{
    GameStream _stream;
    SaveSlot _slot;

    void Awake()
    {
        _slot = SaveSlot.Create("slot_1");
        _stream = new GameStream();
    }

    public void SavePlayer(int health)
    {
        _stream.Set(new PlayerSave
        {
            position = transform.position,
            health = health
        });

        if (!SaveFileIO.TrySave(_slot, _stream, out var report))
            Debug.LogError(report.GetDebugMessage());
    }

    void OnDestroy()
    {
        _stream?.Dispose();
    }
}

SaveSlot.Create("slot_1") resolves a path under Application.persistentDataPath/Saves and gives you the primary and backup paths.

Manual paths are also supported:

SaveFileIO.TrySave("C:/Temp/save.purrsave", stream, out var report);

For small updates, Write<T> can upsert one DTO without dropping the rest of the file:

SaveFileIO.Write(slot, playerSave);
SaveFileIO.Write(slot, worldSettings);

If the save file already exists, Write<T> loads the whole stream first, replaces only T, then saves the full stream back. If the existing file is corrupt, the write fails instead of overwriting it.

3. Load Data

using var stream = new GameStream();
var slot = SaveSlot.Create("slot_1");

if (!SaveFileIO.TryLoadPrimary(slot, stream, out var report))
{
    Debug.LogWarning(report.GetDebugMessage());

    if (report.hasBackup)
    {
        // Prompt the player before loading backup.
        // A backup may represent older progress.
        SaveFileIO.TryLoadBackup(slot, stream, out var backupReport);
    }
}

if (stream.TryGet<PlayerSave>(out var player))
{
    transform.position = player.position;
}

For a quick single-DTO read:

if (SaveFileIO.TryRead(slot, out PlayerSave player, out var report))
{
    transform.position = player.position;
}

TryLoadPrimary never loads a backup automatically. Backup loading is explicit so the game can communicate possible lost progress.

Versioning

When a save data format changes, add a new loader and update the saver.

[BitLoader(1)]
public static PlayerSave LoadV1(GamePacker packer)
{
    return new PlayerSave
    {
        position = packer.Read<Vector3>(),
        health = packer.Read<int>()
    };
}

Keep old loaders when possible. They are how old files migrate into the current DTO shape.

Recovery

Normal loads are strict. They reject corrupt entries.

Partial recovery is explicit:

if (SaveFileIO.TryRecoverPrimary(slot, stream, out var fileReport, out var recoveryReport))
{
    Debug.Log($"Recovered entries: {recoveryReport.recoveredCount}");
    Debug.Log($"Skipped entries: {recoveryReport.skippedCount}");
}

Recovery can skip entries with checksum mismatches or decompression failures when the stream structure is still readable.

Roslyn Support

PurrSave ships a Roslyn analyzer/source generator that:

  • Registers valid [BitLoader], [BitSaver], and [SaveKey] methods.
  • Reports invalid loader/saver signatures.
  • Reports missing or invalid SaveKey attributes.
  • Offers quick fixes and generation/refactoring actions where supported by the IDE.

Docs

~/versions

Log in and subscribe to the Studio plan to access this package.

Log In