Entity Command Buffers
An EntityCommandBuffer (ECB) records entity data changes to be enacted later. Commands are recorded with methods of
EntityCommandBuffer which mirror many (but not all) of the
EntityManager methods, for example:
CreateEntity(EntityArchetype): Create a new entity with the specified archetype
DestroyEntity(Entity): Destroy the entity
SetComponent<T>(Entity, T): Set value for component of type
Ton the entity
AddComponent<T>(Entity): Add component of type
Tto the entity
RemoveComponent<T>(EntityQuery): Remove component of type
Tfrom all entities matching the query.
None of the changes recorded in an
EntityCommandBuffer are enacted until its
Playback method is called on the main thread.
After playback of an ECB, attempting to record more changes throws an exception.
Like a native container, an ECB has a job safety handle. While a job that uses an ECB is scheduled but not yet completed, the safety checks will throw an exception if if the main thread tries to:
- Access the ECB (calls its
Dispose, or other methods)
- Schedule another job that accesses the same ECB (unless this new job depends upon the already scheduled job).
ECB use in a single-threaded job
Structural changes cannot be performed in a job, so an ECB is very useful for deferring structural changes until the job is completed. For example:
ECB use in a parallel job
When recording in a parallel job, you need an
EntityCommandBuffer.ParallelWriter, which allows for thread-safe concurrent recording to an ECB:
EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob); // Methods of this writer record commands to // the ECB in a thread-safe way. EntityCommandBuffer.ParallelWriter parallelEcb = ecb.AsParallelWriter();
Be clear that only recording needs to be safe for concurrency: playback is always just single-threaded on the main thread.
Because recording of the commands is split across threads, the order of recorded commands depends upon the happenstance of scheduling and thus is non-deterministic. Indeterminate order of recording cannot be avoided, but we can make the playback order of the commands deterministic:
- Each command records a 'sort key' int (passed as the first argument to each command method).
- Upon playback, the commands are sorted by their sort keys before the commands are enacted.
As long as the recorded sort keys are independent from the happenstance of scheduling, the sorting makes the playback order deterministic.
In a parallel job, the sort key you need for each entity is a number that has a fixed and unique association with that entity in the job's query. The
entityInQueryIndex value provided in a parallel job meets those criteria: in the list of chunks matching the job's query...
- the first entity of the first chunk has
- the second entity of the first chunk has
- the first entity of the second chunk has an
entityInQueryIndexwhich is the count of the first chunk
- the first entity of the third chunk has an
entityInQueryIndexwhich is the sum of the counts of the first two chunks
- ...and so forth.
Determinism isn't always essential, but code which produces deterministic results can be much easier to debug. There are also networking scenarios which require consistent results across different machines. Understand however that determinism can have a cost, so accepting indeterminism may be appropriate in some projects.
Note that the lambda parameter must be called
Entities.ForEach doesn't know what int you're talking about.
For a regular (non-parallel) ECB, it's OK to use the same ECB in multiple jobs as long as those jobs don't overlap in scheduling. For a parallel ECB, however, this can lead to unexpected (and potentially undesirable) sort orders of the commands in playback...unless the sort keys used in each job fall in different ranges. Recording a set of commands across several ECB's has very little overhead compared to recording the same set of commands to a single ECB, so it's generally best to simply give each job its own ECB, especially for parallel jobs.
Playback method throws an exception if called more than once unless the ECB is created with the
The main use case for multi-playback is repeatedly spawning a set of entities: after using an ECB to create and configure a set of new entities, you can repeat playback to respawn another matching set of entities.
After an ECB's first playback, attempting to record additional commands throws an exception.
ECB use on the main thread
You sometimes might want to record ECB commands on the main thread because:
- You want to delay your changes.
- You want to play back a set of changes multiple times.
- It can be more efficient to play back many changes in one consolidated place rather than interspersing the changes across different parts of the frame.
Regarding this last point, keep in mind that every structural change operation triggers a sync point, meaning the operation must wait for some or all currently scheduled jobs to complete. If you consolidate your structural changes with ECB's, your frame can have fewer sync points.
Rather than manually play back and dispose a command buffer yourself, you can have an EntityCommandBufferSystem do those things for you by following these steps:
- Get the instance of the ECB system which you want to do the playback.
- Create an ECB via the system.
- Schedule a job that will write commands to the ECB.
- Register the scheduled job to be completed by the system.
You should not manually play back and dispose an ECB created by an ECB system. The ECB system will do both for you.
In each update, an
- Complete all registered jobs (thereby ensuring that they have finished their recording).
- Playback all ECB's created via the system (in the same order they were created).
- Dispose of the ECB's.
The standard ECB systems
The default World is automatically given five ECB systems:
These update at the begin and end of the three standard system groups. See Default System Groups.
Because structural changes cannot happen in the frame after the rendering data has been handed off to the renderer, there is no
EndPresentationEntityCommandBufferSystem at the end of the frame.
BeginInitializationEntityCommandBufferSystem at the start of the frame should suit most of the same purposes: after all, the end of one frame is the beginning of the next frame.
These standard five ECB systems should suffice for most purposes, but you can create your own ECB systems if necessary:
The ECB methods
Instantiate record commands to create entities. Because these methods just record commands instead of immediately creating entities, they return
Entity values with negative indexes representing placeholder entities that do not yet exist. These placeholder
Entity values are only meaningful in recorded commands of the same ECB.
Values recorded in an
SetBuffer command might have
Entity fields. In playback, any placeholder
Entity values in these components or buffers will be remapped to the corresponding actual entities.