RuntimeGridAllocator
Opt-in worker-side allocator for a game built on RuntimeGrid: keeps a ring of cells requested around every player-owned authoritative entity (and any fixed anchor coordinates, e.g.
Nebula.World · Packages/com.1by3.nebula/Runtime/World/RuntimeGridAllocator.cspublic sealed class RuntimeGridAllocatorOpt-in worker-side allocator for a game built on RuntimeGrid: keeps a ring of cells requested around every player-owned authoritative entity (and any fixed anchor coordinates, e.g. the origin), re-touches them every tick so a neighbor's owner does not retire them, and retires cells this worker owns once nobody has wanted them for a while and nothing occupies them. This packages the allocation policy that procedural-world games otherwise implement themselves; a game adopting RuntimeGrid can use it instead of writing the same logic again.
A cell counts as occupied while a client owns an entity anywhere inside it, including one riding in a vehicle's carried container (a pilot in a ship parked in the cell, or a passenger in a shuttle inside that ship). A cell is also kept while an authoritative entity still assigned to it stands in another cell that this allocator wants, or whose lease row any worker touched within RetireAfterSeconds. This happens when a fast vehicle leaves every leased cell behind: the vehicle stays in the last cell it left until a cell is registered where it is.
With NebulaConfig.ChunkedWorld enabled, NebulaChunkedWorld constructs and updates the allocator. For a custom allocation workflow, construct this class and call Tick from your worker's update loop.
With LeadSeconds set, a moving pawn also has a ring leased around every cell on the line to where its velocity takes it in that time: a capsule ahead of it rather than a wider ring all round. The velocity is measured from the pawn's position in the grid's own coordinates between two policy ticks, so it is the same for a pawn on foot, a pilot aboard a ship and a ship crossing into or out of a physics frame.
For a grid hosted by a container (RuntimeGrid.IsHosted, docs/container-tree.md D22) the ring is computed in the host's own coordinates, every chunk is requested as a leased child of the host, and only pawns inside the host's box, or within Reach metres of it, count. Nothing is requested while the host is not in this process.
Constructors
RuntimeGridAllocator
public RuntimeGridAllocator(NebulaWorker worker, RuntimeGrid grid);Properties
Ring
public int Ring { get; set; } = 1;Ring radius (Chebyshev distance in cells) requested around every anchor and owned entity.
RetireAfterSeconds
public float RetireAfterSeconds { get; set; } = 60f;How long an owned, unwanted, unoccupied cell sits idle before it is released. Also how recently another cell's lease row must have been touched for an entity standing in that cell to keep its own cell leased.
TickIntervalSeconds
public float TickIntervalSeconds { get; set; } = 0.25f;How often Tick actually recomputes interest; calls between are no-ops.
LeadSeconds
public float LeadSeconds { get; set; }Seconds of travel leased ahead of a moving pawn (ChunkGridDefinition.LeadSeconds): a ring of Ring around every cell on the line from the pawn to its position this many seconds ahead. 0 (the default) leases the ring around the pawn alone, as before.
MaxLeadSpeed
public float MaxLeadSpeed { get; set; } = 2000f;Speeds above this, in metres per second, are read as a jump (a teleport, a respawn) rather than travel, and lead nothing.
Reach
public float Reach { get; set; }A hosted grid only (ChunkGridDefinition.Reach): how far outside the host's box, in metres, a pawn (or the point its lead reaches) still counts. 0 counts pawns inside the box only. The pawn must also be within this distance of the box of the cell it is over (IChunkGridGeometry.BoundsOf): where the cells' boxes fill the host, as a lattice's columns do, that is the same rule; where they don't, as a sphere's cells inside its bounding cube, a pawn high over the cells leases nothing.
Grid
public RuntimeGrid Grid { get; }The grid this allocator works in.
ScopeKey
public string ScopeKey { get; }The scope this allocator leases chunks in ("" for the public world).
WantedIds
public IReadOnlyCollection<ulong> WantedIds { get; }The interest set as of the last Tick: every cell id currently requested.
Methods
AddAnchor
public void AddAnchor(Vector3Int coord);Fixed coordinates kept wanted regardless of where entities are, e.g. the origin cell(s).
RemoveAnchor
public void RemoveAnchor(Vector3Int coord);ClearAnchors
public void ClearAnchors();AddPin
public void AddPin(Vector3Int coord);Keep one chunk wanted without a ring around it: a scope's anchor chunk, which must not be retired because it is where a client routed by key arrives, but which does not need its neighbours leased on every worker that has ever seen the scope.
RemovePin
public void RemovePin(Vector3Int coord);IsWanted
public bool IsWanted(Vector3Int coord);
public bool IsWanted(ulong id);Tick
public void Tick(float unscaledTime);Recompute interest and touch/retire containers accordingly. Cheap to call every frame: the actual work only runs every TickIntervalSeconds. unscaledTime is the caller's own clock (e.g. Time.unscaledTime); passed in rather than read here so this stays testable without a running player loop.
EnsureContainer
public void EnsureContainer(Vector3Int coord, Action<Container> onReady);Request the container at coord and call onReady once it is registered (immediately, if it already is). Event-driven, not polling: replaces the request-then-poll-every-0.1s loops a game otherwise writes by hand at every call site that needs a container to exist before acting on it. ContainerRegistry.RuntimeRegistered is a static, mesh-wide event, so this fires for a container any worker or the control plane registers, not only ones this allocator itself requested.