Skip to content

Gameplay world storage

Ludus::GameplayWorld supplies identity and typed sparse pools for a game-owned world. It depends on FoundationBase and FoundationContainers and is exported in the installed SDK. Gameplay, input, serialization, scheduling and rendering remain in the consuming game.

Initialize an EntityRegistry with a fixed entity capacity, then initialize each ComponentPool<T> against it during construction. Both operations return explicit allocation/configuration errors. Pool components must be trivially copyable and trivially destructible plain state. Initialization reserves dense values, dense owners and the sparse slot mapping for the whole registry limit. Insertion and removal subsequently allocate nothing.

TryCreate changes its output only on success. EntityId contains a nonzero world identity, slot and generation. IsAlive validates all three; IsActive also excludes pending destruction. Release advances the generation and retires an exhausted slot. World identities and creation sequences fail on exhaustion. Moves transfer registry ownership and leave the source registry without a world identity.

The game's construction/commit facade owns mutable registry and pool access. Systems receive a const registry, component values and command recording APIs. SetStructuralPhase(false) prevents registry creation/release and pool membership changes. These checks remain enabled in release builds. Remove an entity from every pool before releasing its registry slot. Registry release cannot discover a game's component roster.

Find validates pool ownership and the complete dense owner. Its pointer and the matching owner/value spans are borrowed until structural mutation, pool move or world replacement. Always check IsActive before updating an entity. Pending entities deliberately retain their values until commit. Persist handles across phases and asynchronous work. Dense order changes after swap removal; use creation sequence or authored identity when gameplay priority depends on order.

Durable binding table

WorldBindingTable (header ludus/gameplay/world/binding_table.h) is the World-boundary resolution layer for durable identity. It holds two derived mappings, published and removed together with object activation and destruction: a sorted contiguous PersistentObjectKey -> EntityId forward map, and a slot-indexed reverse mapping validated by the handle's owner and generation so a recycled slot never inherits a former object's UUID. The authored records and the registry stay authoritative; the table is rebuilt, never a second source of truth.

Size the table once with TryInitialize(registry.GetCapacity()). Form each PersistentObjectKey from the activating instance scope's InstanceId and an authored SourceObjectId, then publish a batch with TryActivate: it validates every candidate against the registry and the existing bindings, reserves forward capacity through the allocation-failure seam, and publishes both mappings while advancing the binding revision once. A failed batch publishes nothing and leaves the active world unchanged. DetachBinding removes an entity's binding inside the registry destruction order, before the slot is released.

TryResolve(registry, key, outEntity) performs no I/O and no implicit loading. It returns a ResolveStatus (Resolved, NotResident, Missing, TypeMismatch, InvalidReference or Conflict), validates the stored incarnation (owner and generation) on a hit, and drops a stale binding reporting Missing when the incarnation is gone — so unloading and recreating the same durable key yields a fresh handle while the key stays resolvable. A cached negative result is invalidated by comparing GetBindingRevision(), a monotonic counter advanced on every publish or removal.

See the architecture, object handles and persistent UUIDs and the reference game for composition, transactional candidate loading and phased tick examples.