Automatic play back and disposal of entity command buffers
To play back and dispose of entity command buffers (ECBs), you can use EntityCommandBufferSystem
, rather than manually doing it yourself. To do this:
- Get the singleton instance of the
EntityCommandBufferSystem
which you want to do the playback. - Use the singleton to create an
EntityCommandBuffer
instance. - Records commands to the
EntityCommandBuffer
.
For example:
// ... in a system
// Assume an EntityCommandBufferSystem exists named FooECBSystem.
// This call to GetSingleton automatically registers the job so that
// it gets completed by the ECB system.
var singleton = SystemAPI.GetSingleton<FooECBSystem.Singleton>();
// Create a command buffer that will be played back
// and disposed by MyECBSystem.
EntityCommandBuffer ecb = singleton.CreateCommandBuffer(state.WorldUnmanaged);
// An IJobEntity with no argument to Schedule implicitly
// assigns its returned JobHandle to this.Dependency
new MyParallelRecordingJob() { ecbParallel = ecb.AsParallelWriter() }.Schedule();
Important
Don't manually play back or dispose of an EntityCommandBuffer
that you've created with an EntityCommandBufferSystem
. The EntityCommandBufferSystem
does both for you when it runs.
In each update, an EntityCommandBufferSystem
:
- Completes all registered jobs, plus all jobs scheduled against its singleton component. This ensures that any relevant jobs have finished their recording.
- Plays back all ECBs created via the system in the same order they were created.
- Disposes of the
EntityCommandBuffer
instances.
Default EntityCommandBufferSystem systems
The default world has the following default EntityCommandBufferSystem
systems:
BeginInitializationEntityCommandBufferSystem
EndInitializationEntityCommandBufferSystem
BeginFixedStepSimulationEntityCommandBufferSystem
EndFixedStepSimulationEntityCommandBufferSystem
BeginVariableRateSimulationEntityCommandBufferSystem
EndVariableRateSimulationEntityCommandBufferSystem
BeginSimulationEntityCommandBufferSystem
EndSimulationEntityCommandBufferSystem
BeginPresentationEntityCommandBufferSystem
Because structural changes can't happen in the frame after Unity gives the rendering data to the renderer, there's no EndPresentationEntityCommandBufferSystem
system. You can use BeginInitializationEntityCommandBufferSystem
instead: the end of one frame is the beginning of the next frame.
The EntityCommandBufferSystem
systems update at the beginning and end of the standard system groups, and at the beginning and end of the fixed and variable rate simulation groups. For more information, refer to the documentation on System update order.
If you can't use the default systems for your application, then you can create your own EntityCommandBufferSystem
:
// You should specify where exactly in the frame this ECB system should update.
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(FooSystem))]
public partial class MyECBSystem : EntityCommandBufferSystem
{
// The singleton component data access pattern should be used to safely access
// the command buffer system. This data will be stored in the derived ECB System's
// system entity.
public unsafe struct Singleton : IComponentData, IECBSingleton
{
internal UnsafeList<EntityCommandBuffer>* pendingBuffers;
internal AllocatorManager.AllocatorHandle allocator;
public EntityCommandBuffer CreateCommandBuffer(WorldUnmanaged world)
{
return EntityCommandBufferSystem
.CreateCommandBuffer(ref *pendingBuffers, allocator, world);
}
// Required by IECBSingleton
public void SetPendingBufferList(ref UnsafeList<EntityCommandBuffer> buffers)
{
var ptr = UnsafeUtility.AddressOf(ref buffers);
pendingBuffers = (UnsafeList<EntityCommandBuffer>*)ptr;
}
// Required by IECBSingleton
public void SetAllocator(Allocator allocatorIn)
{
allocator = allocatorIn;
}
// Required by IECBSingleton
public void SetAllocator(AllocatorManager.AllocatorHandle allocatorIn)
{
allocator = allocatorIn;
}
}
protected override void OnCreate()
{
base.OnCreate();
this.RegisterSingleton<Singleton>(ref PendingBuffers, World.Unmanaged);
}
}
Deferred entities
The EntityCommandBuffer
methods CreateEntity
and Instantiate
record commands that create entities. These methods only record commands and don't create entities. As such, they return Entity
values with negative indices that represent placeholder entities that don't exist yet. These placeholder Entity
values are only meaningful in recorded commands of the same ECB:
// ... in a system
EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob);
Entity placeholderEntity = ecb.CreateEntity();
// Valid to use placeholderEntity in later commands of same ECB.
ecb.AddComponent<FooComp>(placeholderEntity);
// The real entity is created, and
// FooComp is added to the real entity.
ecb.Playback(state.EntityManager);
// Exception! The placeholderEntity has no meaning outside
// the ECB which created it, even after playback.
state.EntityManager.AddComponent<BarComp>(placeholderEntity);
ecb.Dispose();
Values recorded in an AddComponent
, SetComponent
, or SetBuffer
command might have Entity
fields. In playback, Unity remaps any placeholder Entity
values in these components or buffers to the corresponding actual entities:
// ... in a system
EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob);
// For all entities with a FooComp component...
foreach (var (f, e) in SystemAPI.Query<FooComp>().WithEntityAccess())
{
// In playback, an actual entity will be created
// that corresponds to this placeholder entity.
Entity placeholderEntity = ecb.CreateEntity();
// (Assume BarComp has an Entity field called TargetEnt.)
BarComp bar = new BarComp { TargetEnt = placeholderEntity };
// In playback, TargetEnt will be assigned the
// actual Entity that corresponds to placeholderEntity.
ecb.AddComponent(e, bar);
}
// After playback, each entity with FooComp now has a
// BarComp component whose TargetEnt references a new entity.
ecb.Playback(state.EntityManager);
ecb.Dispose();