Storage Modes
Pick durability and write cost per component type — from microsecond ACID to nanosecond scratch memory, in one engine.
Status: ✅ Implemented · Visibility: Public · Level: 🟢 Start Here · Category: Ecs
🎯 What it solves
Game and server workloads mix data with wildly different needs: a position updated every tick can tolerate
losing a few frames, while inventory or currency can never be lost or read half-written. Forcing all of it
through one storage tier means either paying full ACID cost for high-frequency data or risking real state on a
fast-but-loose tier. Storage Modes let each component type — not the whole database — declare its own point
on the durability/performance spectrum, so a single entity can mix a Versioned wallet with a SingleVersion
position and a Transient animation cursor.
⚙️ How it works (in brief)
StorageMode is set via [Component(StorageMode = ...)] and is fixed for a given (component name, revision):
changing how a component is stored requires bumping the [Component] revision — re-using the same revision with a
different mode throws InvalidOperationException on reopen. Versioned keeps a full MVCC revision chain
(snapshot isolation, zero loss); SingleVersion stores one in-place HEAD slot with WAL tick-fence durability
(≤1 tick loss); Transient is heap-only and never persisted. All three are read and written through the same
EntityRef.Read<T>() / Write<T>() calls — only the cost and guarantees differ, not the API. A runtime
durability discipline (Committed) layers commit-time, zero-loss atomicity onto the SingleVersion layout
without paying for a revision chain.
Sub-features
| Sub-feature | Use it for | Write cost (Zen 4) | Durability |
|---|---|---|---|
| Versioned | Inventory, economy, progression, anything needing snapshot isolation or AS-OF reads | ~250 ns | Zero loss, full ACID |
| SingleVersion (Tick-Fence Durability) | Position, velocity, health, cooldowns — high-frequency, loss-tolerant | ~40 ns | ≤1 tick loss |
| Transient | Animation state, input buffers, pathfinding scratch, targeting info | ~40 ns | None — gone on crash |
| Committed Durability Discipline | A SingleVersion write that must be atomic and zero-loss without MVCC (teleport, item pickup, currency debit) |
~40 ns + commit publish | Zero loss, atomic, no chain |
⚠️ Guarantees & limits
StorageModeis fixed for a given(name, revision); changing a component's mode requires a new[Component]revision (there is no in-place mode migration yet — tracked in #546).- An entity can freely mix component types across all three modes (
Spawn/Open/OpenMut/Destroywork uniformly); rollback semantics differ per mode — see each sub-feature. - Indexes, spatial queries, and ECS lifecycle (Spawn/Destroy/Enable) are available in all three modes, but with different freshness/synchronicity guarantees — check the Storage Mode Feature Matrix (linked below) before committing to a mode.
- The mode choice is silent at the API level — picking
Versionedfor scratch data overpays, pickingSingleVersionfor currency under-protects. There is no compiler or runtime guard for "wrong mode for this data."
🧪 Tests
- StorageModeReadWriteTests — an entity mixing all three modes, uniform
Read/Writeacross them, per-mode rollback/dirty-bitmap behavior - StorageModeInfrastructureTests —
[Component(StorageMode = ...)]attribute defaults, per-mode segment allocation differences - StorageModeRevisionLockTests — a
(name, revision)re-declared with a differentStorageModeon reopen throws