Class EntityCommandBuffer
The tick's deferred entity commands, one segment per worker slot (#1099). A parallel system queues a spawn or a destroy through
ctx.Commands without a transaction; the engine applies the whole buffer in one fence phase.
public sealed class EntityCommandBuffer
- Inheritance
-
EntityCommandBuffer
- Inherited Members
Remarks
Per-worker segments, no atomics on the push path. Exactly EventQueue<T>'s arrangement and for the same measured reason: a worker only ever touches its own segment, so queueing a command is a bounds check and a few plain increments. Correctness rests on slot disjointness, which DispatcherWorkerId establishes. The one atomic in the whole write path is the single Add(ref long, long) per archetype per tick that reserves a key generation — see Typhon.Engine.Internals.EntityKeyBlocks.
Structure of arrays. 24-byte headers in one buffer, 128-byte ComponentValue payloads in another, so the apply's grouping pass walks headers and never pulls a payload into cache merely to sort it.
The arena is GC-backed, and that is safe here — said explicitly because the project rule has a crash behind it. Both buffers are managed
arrays and are only ever a copy SOURCE: ReadOnlySpan<ComponentValue>.CopyTo writes into them and the apply reads out of them by
ref. No byte*, no fixed, no GCHandle and no Unsafe.AsPointer is ever taken over them. That holds by construction
because ComponentValue carries no reference and no pointer, and it is the same arrangement EventQueue<T> already relies
on. The pointer lives entirely on the far side of the handoff, where the native staging arena is the DESTINATION of a store.
Overflow is tolerated and counted, never thrown. A command that does not fit returns Null and increments an exact counter. The per-tick budget is a number an application has to guess before it has met its own worst tick, and the worst tick is the mass-death event nobody sizes correctly the first time — so losing the tick would be worse than losing the commands, provided the loss is exact and loud. A reported zero means nothing was lost.
Properties
Capacity
Commands this buffer is sized for across all slots per tick, as declared. A skewed tick may exceed it; see OverflowCount.
public int Capacity { get; }
Property Value
Count
Commands accepted this tick, summed across every slot.
public int Count { get; }
Property Value
IsEmpty
True when nothing has been queued this tick. O(1).
public bool IsEmpty { get; }
Property Value
OverflowCount
Commands dropped this tick because a segment was at its ceiling or an archetype had used its key-block generations. Exact, not sampled: the tolerate-and-count verdict rests on a reported zero meaning nothing was lost.
public uint OverflowCount { get; }
Property Value
PeakDepth
The deepest any one slot's segment got this tick, summed across slots — what to size Capacity against.
public int PeakDepth { get; }
Property Value
Remarks
A sum of per-slot high waters, which is the right figure HERE and would be wrong for an event queue: nothing drains this buffer mid-tick, so every slot's peak is its end-of-tick depth and the sum is a real simultaneous total. EventQueue<T> cannot say that, which is why it stamps a queue-level peak before draining instead.
PendingEntities
Entities this tick's accepted spawns will create — the sum over accepted commands of their entity counts, so a SpawnMany counts once per entity.
public int PendingEntities { get; }
Property Value
RejectedCount
Commands refused at the call because they could not be valid — an unregistered archetype, a realm that cannot hold the entity, too many values, or a producer outside the key-block stride. Distinct from OverflowCount: that is the engine out of room, this is an impossible request.
public uint RejectedCount { get; }