Entity Command Buffers
The EntityCommandBuffer
(ECB) class solves two important problems:
- When you're in a job, you can't access the
EntityManager
. - When you perform a structural change (like creating an entity), you create a sync point and must wait for all jobs to complete.
The EntityCommandBuffer
abstraction allows you to queue up changes (from either a job or from the main thread) so that they can take effect later on the main thread.
Entity command buffer systems
Entity command buffer systems allow you to play back the commands queued up in ECBs at a clearly defined point in a frame. These systems are usually the best way to use ECBs. You can acquire multiple ECBs from the same entity command buffer system and the system will play back all of them in the order they were created when it is updated. This creates a single sync point when the system is updated instead of one sync point per ECB and ensures determinism.
The default World initialization provides three system groups, for initialization, simulation, and presentation, that are updated in order each frame. Within a group, there is an entity command buffer system that runs before any other system in the group and another that runs after all other systems in the group. Preferably, you should use one of the existing command buffer systems rather than creating your own in order to minimize synchronization points. See Default System Groups for a list of the default groups and command buffer systems.
If you want to use an ECB from a parallel job (e.g. in an Entities.ForEach
), you must ensure that you convert it to a concurrent ECB first by calling ToConcurrent
on it. To ensure that the sequence of the commands in the ECB does not depend on how the work is distributed across jobs, you must also pass the index of the entity in the current query to each operation.
You can acquire and use an ECB like this:
struct Lifetime : IComponentData
{
public byte Value;
}
class LifetimeSystem : JobComponentSystem
{
EndSimulationEntityCommandBufferSystem m_EndSimulationEcbSystem;
protected override void OnCreate()
{
base.OnCreate();
// Find the ECB system once and store it for later usage
m_EndSimulationEcbSystem = World
.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
// Acquire an ECB and convert it to a concurrent one to be able
// to use it from a parallel job.
var ecb = m_EndSimulationEcbSystem.CreateCommandBuffer().ToConcurrent();
var jobHandle = Entities
.ForEach((Entity entity, int entityInQueryIndex, ref Lifetime lifetime) =>
{
// Track the lifetime of an entity and destroy it once
// the lifetime reaches zero
if (lifetime.Value == 0)
{
// pass the entityInQueryIndex to the operation so
// the ECB can play back the commands in the right
// order
ecb.DestroyEntity(entityInQueryIndex, entity);
}
else
{
lifetime.Value -= 1;
}
}).Schedule(inputDeps);
// Make sure that the ECB system knows about our job
m_EndSimulationEcbSystem.AddJobHandleForProducer(jobHandle);
return default;
}
}