Entity Command Buffer Helper
Critical Instructions
- •Never Structural Change in Job Directly: KHÔNG gọi
EntityManager.AddComponent()trong job — phải dùng ECB. - •Choose Right Playback Point: Mỗi ECB system playback tại thời điểm khác nhau. Chọn sai = race condition.
- •Parallel Writer Safety: Dùng
.AsParallelWriter()trong parallel jobs, vớisortKey=unfilteredChunkIndexhoặcentityInQueryIndex. - •Project Convention: Pattern ECB chuẩn hóa cho toàn bộ VisualWorld project.
Core Concepts
What is EntityCommandBuffer?
ECB ghi lại ("record") các thao tác structural change, rồi "playback" chúng tại 1 sync point an toàn trên main thread. Điều này cho phép jobs ghi structural changes mà không gây conflict.
Structural Changes (cần ECB)
- •
CreateEntity()/DestroyEntity() - •
AddComponent<T>()/RemoveComponent<T>() - •
SetSharedComponent<T>() - •
Instantiate()(clone entity/prefab) - •
AddBuffer<T>()/SetBuffer<T>()
Non-Structural Changes (KHÔNG cần ECB)
- •
SetComponent<T>()trên component đã tồn tại → dùngRefRW<T>trực tiếp - •Write vào
NativeArray,DynamicBuffer→ trực tiếp trong job
ECB Playback Points
Built-in ECB Systems
code
Frame timeline: ├── BeginInitializationEntityCommandBufferSystem │ └── ECB playback → structural changes TRƯỚC initialization ├── InitializationSystemGroup │ └── ... your init systems ... ├── EndInitializationEntityCommandBufferSystem │ ├── BeginSimulationEntityCommandBufferSystem ← COMMON CHOICE │ └── ECB playback → structural changes TRƯỚC simulation ├── SimulationSystemGroup │ ├── ... physics, game logic ... │ ├── YOUR SYSTEMS HERE │ └── EndSimulationEntityCommandBufferSystem ← COMMON CHOICE │ └── ECB playback → structural changes SAU simulation │ ├── BeginPresentationEntityCommandBufferSystem ├── PresentationSystemGroup │ └── ... rendering ... └── EndPresentationEntityCommandBufferSystem
Choosing the Right Point
| Scenario | Recommended ECB System | Reason |
|---|---|---|
| Spawn entity cần xử lý trong frame này | BeginSimulationECBSystem | Entity sẵn sàng cho simulation systems |
| Destroy entity sau khi xử lý xong | EndSimulationECBSystem | Đảm bảo tất cả systems đã đọc entity |
| Spawn entity cho rendering | BeginPresentationECBSystem | Entity sẵn sàng cho render systems |
| Init setup (one-time spawn) | BeginInitializationECBSystem | Chạy sớm nhất |
| Cleanup/despawn sau frame | EndSimulationECBSystem | Cuối frame, an toàn |
Usage Patterns
Pattern 1: Single-Threaded ECB (SystemAPI.foreach)
csharp
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
/// <summary>
/// Spawn entity từ requests. ECB single-threaded, inline trong System.
/// </summary>
[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
public partial struct SimpleSpawnSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<SpawnRequest>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// Lấy ECB singleton — KHÔNG tạo bằng new EntityCommandBuffer()
var ecbSingleton = SystemAPI
.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
foreach (var (request, entity) in
SystemAPI.Query<RefRO<SpawnRequest>>().WithEntityAccess())
{
// Instantiate prefab
var spawned = ecb.Instantiate(request.ValueRO.Prefab);
// Set component trên entity MỚI (chưa tồn tại → phải dùng ECB)
ecb.SetComponent(spawned, LocalTransform.FromPosition(
request.ValueRO.Position
));
// Add tag
ecb.AddComponent<ActiveFlag>(spawned);
// Consume request
ecb.DestroyEntity(entity);
}
// ECB tự playback tại BeginSimulationECBSystem sync point
}
}
Pattern 2: Parallel ECB (IJobEntity)
csharp
/// <summary>
/// Job xử lý song song với ECB.ParallelWriter.
/// QUAN TRỌNG: sortKey phải unique per entity để đảm bảo deterministic.
/// </summary>
[BurstCompile]
public partial struct ParallelDestroyJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter ECB;
void Execute(
[ChunkIndexInQuery] int sortKey, // ← sortKey cho determinism
Entity entity,
in HealthComponent health)
{
if (health.Current <= 0)
{
ECB.DestroyEntity(sortKey, entity);
// Spawn debris
var debris = ECB.CreateEntity(sortKey);
ECB.AddComponent(sortKey, debris, new DebrisData
{
Position = /* ... */,
Velocity = /* ... */
});
}
}
}
// Trong System:
[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
public partial struct DeathSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var ecbSingleton = SystemAPI
.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbSingleton
.CreateCommandBuffer(state.WorldUnmanaged)
.AsParallelWriter(); // ← PARALLEL WRITER
state.Dependency = new ParallelDestroyJob
{
ECB = ecb
}.ScheduleParallel(state.Dependency);
}
}
Pattern 3: ECB with DynamicBuffer
csharp
/// <summary>
/// Thêm buffer elements vào entity mới qua ECB.
/// </summary>
[BurstCompile]
public void SpawnWithBuffer(EntityCommandBuffer ecb, float3 position)
{
var entity = ecb.CreateEntity();
ecb.AddComponent(entity, LocalTransform.FromPosition(position));
// Add buffer và populate
var buffer = ecb.AddBuffer<NeighborElement>(entity);
buffer.Add(new NeighborElement { Value = Entity.Null });
}
Pattern 4: Deferred Companion (Tag + Process)
csharp
// Bước 1: Job đánh tag (nhanh, parallel)
[BurstCompile]
public partial struct TagForProcessingJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter ECB;
void Execute([ChunkIndexInQuery] int sortKey, Entity e, in SomeCondition cond)
{
if (cond.ShouldProcess)
ECB.AddComponent<NeedsProcessingTag>(sortKey, e);
}
}
// Bước 2: System xử lý tagged entities (frame sau)
[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(TaggingSystem))]
public partial struct ProcessTaggedSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NeedsProcessingTag>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var ecb = SystemAPI
.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>()
.CreateCommandBuffer(state.WorldUnmanaged);
foreach (var (data, entity) in
SystemAPI.Query<RefRW<SomeData>>()
.WithAll<NeedsProcessingTag>()
.WithEntityAccess())
{
// Process...
data.ValueRW.Processed = true;
// Remove tag khi xong
ecb.RemoveComponent<NeedsProcessingTag>(entity);
}
}
}
Project-Specific Patterns (VisualWorld)
Voxel Destruction → Debris Spawn
csharp
/// <summary>
/// Khi voxel bị phá hủy (structural collapse), spawn Physics Entity debris.
/// Gom voxels bị phá thành clusters, mỗi cluster = 1 debris entity.
/// </summary>
[BurstCompile]
public partial struct DebrisSpawnJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter ECB;
public Entity DebrisPrefab;
void Execute(
[ChunkIndexInQuery] int sortKey,
Entity chunkEntity,
in ChunkCoordinate coord,
in DirtyChunkTag dirty)
{
// Scan voxels có Stress > MaxLoad
// Gom thành clusters
// Mỗi cluster → ECB.Instantiate(DebrisPrefab)
// Set position, mass, velocity
}
}
Phase Change (Solid → Liquid)
csharp
/// <summary> /// Khi voxel đạt MeltingPoint → đổi State từ Solid sang Liquid. /// Cần ECB vì thay đổi SharedComponent (material group có thể đổi). /// </summary> // Dùng EndSimulation ECB vì thermodynamics chạy trong Simulation
Anti-Patterns (TRÁNH)
csharp
// ❌ SAI: Tạo ECB bằng constructor → phải tự Dispose, dễ quên
var ecb = new EntityCommandBuffer(Allocator.TempJob);
ecb.Playback(entityManager);
ecb.Dispose(); // Nếu quên → memory leak
// ✅ ĐÚNG: Dùng Singleton pattern → tự động playback & dispose
var ecb = SystemAPI.GetSingleton<BeginSimulationECBSystem.Singleton>()
.CreateCommandBuffer(state.WorldUnmanaged);
// ❌ SAI: Dùng EntityManager trực tiếp trong Job → crash
void Execute(int i) {
entityManager.CreateEntity(); // KHÔNG ĐƯỢC!
}
// ❌ SAI: sortKey = 0 cho tất cả → non-deterministic
ECB.DestroyEntity(0, entity); // Luôn dùng chunkIndex hoặc entityInQueryIndex
// ❌ SAI: Structural change trên main thread giữa 2 jobs → sync point ngầm
state.EntityManager.AddComponent<Tag>(entity); // Forces sync → kills perf
Validation Checklist
- • ECB lấy từ Singleton, không
new EntityCommandBuffer() - • Parallel jobs dùng
.AsParallelWriter()với sortKey - • sortKey =
[ChunkIndexInQuery]hoặc tương đương, không hardcode 0 - • Playback point phù hợp (Begin/End + Init/Sim/Present)
- • Không gọi
EntityManagerstructural methods trong jobs - • Không gọi
EntityManagerstructural methods giữa job scheduling - • ECB commands referencing newly created entities sử dụng returned Entity handle
- • System có
[UpdateInGroup]và ordering đúng relative to ECB system