Skip to content

💰 Currency – Persistence

Namespace: RevGaming.RevFramework.Currency.Persistence

The Persistence layer provides helpers and components to save and restore wallet balances.

All persistence operates through the ICurrencyService contract, making it compatible with any composed currency stack (Caps, Audit, Authority, Escrow, Idempotency, BatchEvents, etc.).

Where available, persistence best‑effort propagates audit metadata via internal audit‑aware seams without expanding the public API surface.

Persistence is optional, modular, and independent of storage technology.


Purpose

  • Provide a consistent, service-agnostic way to capture and restore wallet balances.
  • Support both per-owner snapshots and scene-wide save/load workflows.
  • Ensure restores are rollback-safe at the orchestration level (best-effort) and decorator-aware.
  • Integrate cleanly with Audit, BatchEvents, Caps, Authority, RequireEscrow, and Idempotency.

Persistence never bypasses the composed service — it uses it.


Core types

WalletSnapshot

Serializable value object representing absolute balances for a single owner.

  • Stores an array of (CurrencyId, balance) pairs.
  • Lightweight and storage-agnostic.
  • Used by save files, migration tools, and editor utilities.

This type contains data only — no logic.


CurrencyPersistence

Static utility for capturing and restoring wallet state for a single owner.

Capture

WalletSnapshot Capture(
    ICurrencyService svc,
    GameObject owner,
    IReadOnlyList<CurrencyId> ids);
  • Builds a snapshot for the requested currency ids.
  • Uses GetBalance on the provided service.
  • Ignores currencies not listed.

Restore

CurOpResult Restore(
    ICurrencyService svc,
    GameObject owner,
    WalletSnapshot snapshot,
    string reason = "RestoreSnapshot",
    string sourceId = null);

Restore behaviour:

  • Wraps the operation in an internal batch capture.
  • Applies balances using audit‑aware flows when available.
  • Falls back to plain ICurrencyService.SetBalance when audit is absent.
  • Operates through the outer composed service, so:
  • Caps
  • Audit
  • Authority
  • RequireEscrow
  • Idempotency

all apply.

Failure handling:

  • On first failure:
  • Roll back all previously applied balances
  • Cancel batch emission
  • Return the failure result -Restore may fail if policy rules, authority checks, or RequireEscrow constraints reject the applied balances.

Success handling:

  • Emit a single batch event when supported
  • Return CurOpResult.Ok()

Scene-wide save / load

CurrencyJsonSave

A ready-to-use MonoBehaviour for scene-wide persistence.

It automatically:

  1. Finds all StableId components in the scene (including inactive)
  2. Captures wallets via CurrencyPersistence.Capture(...)
  3. Serialises snapshots to JSON
  4. Restores wallets later by matching StableId.Id

This component is an integration helper. The supported persistence surface is ICurrencyPersistence + CurrencyPersistence.


Inspector fields

Field Purpose
currencyIds Explicit list of currency ids to persist. When non-empty, used directly.
fileName Relative path under Application.persistentDataPath.
verbose Logs per-owner details during save/load.
currencySet Optional id source when currencyIds is empty.

ID selection logic:

  • If currencyIds is non-empty → use that list
  • Else if currencySet is assigned → use currencySet.ToIds()
  • Else → fallback to ["gold", "gems"]

Usage

var save = FindObjectOfType<CurrencyJsonSave>();

save.SaveNow(); // write JSON
save.LoadNow(); // read + restore

JSON format

{
  "entries": [
    {
      "ownerId": "Player1",
      "snapshot": {
        "lines": [
          { "id": "gold", "balance": 500 },
          { "id": "gems", "balance": 12 }
        ]
      }
    }
  ]
}

This format is: - Human-readable - Stable across versions - Easy to migrate


Gotchas

  • StableId required — owners without a valid StableId cannot be saved or restored.
  • Audit is best-effort — restore propagates reason/sourceId only when the composed stack supports audit-aware flows.
  • Partial snapshots — only listed currencies are restored; others remain unchanged. Snapshot balances are absolute (not delta-based).
  • Escrow + restore — restore uses absolute SetBalance, not escrow holds.
  • Authority — restores must run on the authoritative side in multiplayer.
  • Large scenes — consider async I/O wrappers around JSON read/write.

Best practices

  • Use a CurrencySet to define which currencies should be persisted.
  • Tag restores clearly ("LoadGame", "Autosave", "Migration").
  • Keep save files scoped and small.
  • Test save/load regularly — missing StableId values silently skip owners unless verbose is enabled.
  • In multiplayer, perform persistence only on the authority.

Safe to delete

If your project does not need currency persistence, you can delete this entire folder safely.

No other part of the Currency system depends on Persistence.


  • Abstractions — public contracts
  • Core — factories, batching orchestration
  • Decorators — audit, idempotency, batch wrappers
  • DefinitionsCurrencySet for id selection
  • Exchange — optional post-load conversion