💰 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
GetBalanceon 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.SetBalancewhen 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:
- Finds all
StableIdcomponents in the scene (including inactive) - Captures wallets via
CurrencyPersistence.Capture(...) - Serialises snapshots to JSON
- 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
currencyIdsis non-empty → use that list - Else if
currencySetis assigned → usecurrencySet.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¶
StableIdrequired — owners without a validStableIdcannot be saved or restored.- Audit is best-effort — restore propagates
reason/sourceIdonly 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
CurrencySetto define which currencies should be persisted. - Tag restores clearly (
"LoadGame","Autosave","Migration"). - Keep save files scoped and small.
- Test save/load regularly — missing
StableIdvalues silently skip owners unlessverboseis 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.
Related¶
- Abstractions — public contracts
- Core — factories, batching orchestration
- Decorators — audit, idempotency, batch wrappers
- Definitions —
CurrencySetfor id selection - Exchange — optional post-load conversion