04 - Command Buffer
Docs / Samples / 04 - Command Buffer
Use command buffers to safely batch structural changes (entity creation, component add/remove) and apply at safe boundaries.
Prerequisites
- .NET SDK 8.0+ or Unity 2021.3+
- ZenECS Core package installed
- Understanding of Command Buffer
Run It
.NET Standalone
cd Packages/com.zenecs.core/Samples~/04-CommandBuffer
dotnet run
Unity
- Open Unity project
- Open scene:
Packages/com.zenecs.core/Samples~/04-CommandBuffer/Scene.unity - Press Play
Code Walkthrough
Why Command Buffers?
Command buffers ensure structural changes are applied at safe boundaries:
- Thread Safety: Safe in multi-threaded environments
- Determinism: Applied at predictable points
- Batching: Efficient batch operations
- Safety: Prevents modification during iteration
Basic Usage
using (var cmd = world.BeginWrite())
{
// Create entity
var entity = cmd.CreateEntity();
// Add components
cmd.AddComponent(entity, new Position(0, 0));
cmd.AddComponent(entity, new Velocity(1, 0));
// Remove component
cmd.RemoveComponent<Velocity>(entity);
// Destroy entity
cmd.DestroyEntity(entity);
} // All changes applied here
In Systems
[FixedGroup]
public class SpawnSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
// Safe to create entities during system execution
foreach (var msg in world.ConsumeMessages<SpawnRequest>())
{
var entity = cmd.CreateEntity();
cmd.AddComponent(entity, new Position(msg.X, msg.Y));
cmd.AddComponent(entity, new Velocity(0, 0));
}
}
}
Complete Example
using ZenECS.Core;
using ZenECS.Core.Systems;
// Components
public struct Position { public float X, Y; }
public struct Velocity { public float X, Y; }
public struct Health { public float Current, Max; }
// Message
public struct SpawnRequest
{
public float X, Y;
}
// System
[FixedGroup]
public sealed class SpawnSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
// Spawn entities from requests
foreach (var req in world.ConsumeMessages<SpawnRequest>())
{
var entity = cmd.CreateEntity();
cmd.AddComponent(entity, new Position(req.X, req.Y));
cmd.AddComponent(entity, new Velocity(0, 0));
cmd.AddComponent(entity, new Health(100, 100));
}
}
}
[FixedGroup]
[OrderAfter(typeof(SpawnSystem))]
public sealed class CleanupSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
// Destroy dead entities
foreach (var (entity, health) in world.Query<Health>())
{
if (health.Current <= 0)
{
cmd.DestroyEntity(entity);
}
}
}
}
// Usage
var world = kernel.CreateWorld(null, "GameWorld");
world.AddSystems([new SpawnSystem(), new CleanupSystem()]);
// Request spawn
world.Publish(new SpawnRequest { X = 0, Y = 0 });
// Process (spawn happens here)
kernel.PumpAndLateFrame(dt, fixedDelta, maxSubStepsPerFrame: 4);
Command Buffer API
Entity Operations
using var cmd = world.BeginWrite();
// Create entity
var entity = cmd.CreateEntity();
// Destroy entity
cmd.DestroyEntity(entity);
Component Operations
// Add component
cmd.AddComponent(entity, new Position(0, 0));
// Remove component
cmd.RemoveComponent<Position>(entity);
// Replace component
cmd.ReplaceComponent(entity, new Position(1, 1));
// Add or replace
cmd.AddOrReplaceComponent(entity, new Position(2, 2));
Best Practices
✅ Do
- Always use command buffers for structural changes
- Use in systems when creating/destroying entities
- Batch operations for efficiency
- Dispose properly with
usingstatement
❌ Don't
- Don't modify during queries - Use command buffers
- Don't create entities directly - Use command buffers
- Don't forget to dispose - Use
usingstatement
Common Patterns
Spawn Pattern
[FixedGroup]
public class SpawnSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
foreach (var req in world.ConsumeMessages<SpawnRequest>())
{
var entity = cmd.CreateEntity();
// Configure entity
}
}
}
Cleanup Pattern
[FixedGroup]
public class CleanupSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
foreach (var (entity, component) in world.Query<Component>())
{
if (ShouldDestroy(component))
{
cmd.DestroyEntity(entity);
}
}
}
}
What to Try Next
Experiment 1: Batch Spawning
Spawn multiple entities efficiently:
[FixedGroup]
public class BatchSpawnSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
var requests = world.ConsumeMessages<SpawnRequest>().ToList();
foreach (var req in requests)
{
var entity = cmd.CreateEntity();
// Configure
}
}
}
Experiment 2: Conditional Operations
Conditional structural changes:
[FixedGroup]
public class ConditionalSystem : ISystem
{
public void Run(IWorld world, float deltaTime)
{
using var cmd = world.BeginWrite();
foreach (var (entity, health) in world.Query<Health>())
{
if (health.Current <= 0 && !world.HasComponent<Dead>(entity))
{
cmd.AddComponent(entity, new Dead());
cmd.RemoveComponent<Health>(entity);
}
}
}
}
Next Samples
- 05 - World Reset - State management
- 06 - World Hook - Lifecycle hooks
- Command Buffer Guide - Detailed guide
See Also
- Command Buffer - Detailed documentation
- Systems Guide - System design
- World Guide - World management