Skip to content

t1k:unity:dots-core:entity-command-buffer

FieldValue
Moduledots-core
Version2.3.2
Efforthigh
Tools

Keywords: deferred, DOTS, ECB, entity command buffer

/t1k:unity:dots-core:entity-command-buffer

Handles ECB creation, playback, parallel usage, and structural change deferral. Does NOT handle general ECS patterns (→ dots-ecs-core) or job/Burst patterns (→ dots-jobs-burst).

// Option 1: from system state (recommended — auto-disposed)
var ecb = new EntityCommandBuffer(Allocator.TempJob);
// Option 2: from ECB system singleton (plays back at system group boundary)
var ecb = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>()
.CreateCommandBuffer(state.WorldUnmanaged);
// Option 3: from EntityManager (manual playback)
var ecb = new EntityCommandBuffer(Allocator.Temp);
ecb.Playback(state.EntityManager);
ecb.Dispose();

Use Option 2 for fire-and-forget spawning/destruction across systems.

ecb.CreateEntity(); // returns placeholder Entity
ecb.DestroyEntity(entity);
ecb.AddComponent<T>(entity, value);
ecb.RemoveComponent<T>(entity);
ecb.SetComponent<T>(entity, value);
ecb.SetComponentEnabled<T>(entity, false); // → see dots-enableable-components
ecb.SetName(entity, "SpawnedUnit"); // debug only
  • Within one system: commands play back in the order they were recorded — deterministic
  • Across systems: determined by system update order in the world’s system group
  • BeginSimulationEntityCommandBufferSystem — plays at start of SimulationSystemGroup frame
  • EndSimulationEntityCommandBufferSystem — plays at end of SimulationSystemGroup frame

Choose BeginSim when spawned entities must be visible to systems in the SAME frame. Choose EndSim when destruction must happen AFTER all simulation logic this frame.

// Acquire parallel writer
var ecbParallel = ecb.AsParallelWriter();
// In IJobEntity — pass sortKey from [ChunkIndexInQuery]
[BurstCompile]
partial struct SpawnJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter Ecb;
void Execute([ChunkIndexInQuery] int chunkIndex, in SpawnRequest req)
{
var e = Ecb.CreateEntity(chunkIndex);
Ecb.AddComponent(chunkIndex, e, new Health { Value = 100 });
}
}

sortKey (chunkIndex) ensures deterministic playback across threads. Never use entity index as sort key — not stable across frames.

// Entity returned is a PLACEHOLDER — not valid until after Playback
var placeholder = ecb.CreateEntity();
ecb.AddComponent(placeholder, new MyData { Value = 42 });
// Can use placeholder for further ECB commands in same recording — they resolve together

Never pass a deferred entity to code that runs BEFORE ECB playback — it is invalid outside ECB context.

  • ECB entity before playback: using a placeholder entity in EntityManager queries or SystemAPI before playback = invalid entity access → exception
  • Multiple ECBs in same system: each ECB plays back independently — order between them is not guaranteed unless you control the system order explicitly
  • ECB in IJobEntity: must pass ECB (or ParallelWriter) as a job field — cannot capture via closure
  • Structural changes in foreach: never call EntityManager.AddComponent inside SystemAPI.Query foreach — use ECB to defer the change
  • SetComponentEnabled on missing component: silently ignored — always ensure the component exists in the archetype before enabling/disabling via ECB
  • SetComponent on ECB-instantiated prefab: ecb.SetComponent(instantiatedEntity, data) fails at playback if the prefab doesn’t have the component. Use ecb.AddComponent(instantiatedEntity, data) instead — idempotent: overwrites if exists, adds if not. Critical for perspective-agnostic systems (e.g., SpriteColor exists on 2D prefabs but not 3D)
  • Manual new ECB(Allocator.Temp) + ecb.Playback() is an anti-pattern — 6 demos use this instead of EndSimulationEntityCommandBufferSystem.Singleton. The Singleton ECB plays back at the correct frame boundary and avoids manual ordering issues. Canonical pattern: EndSimulationEntityCommandBufferSystem.SystemHandle. Source: review-260520-round1-burst.md §X2
  • Batch destroys: prefer ecb.DestroyEntity(query) over per-entity loop
  • Avoid ECB in every-frame hot paths — prefer structural stability, use IEnableableComponent for toggles (→ dots-enableable-components)
  • ECB allocation uses Allocator.TempJob (1-4 frames lifetime) or Allocator.Persistent for long-lived buffers
Cross-ReferenceContent
→ See dots-ecs-coreGeneral ECS, SystemAPI, IJobEntity
→ See dots-jobs-burstBurst-compiled jobs, parallel safety
→ See dots-enableable-componentsIEnableableComponent as ECB alternative