Command buffer

The CommandBuffer allows deferring structural changes and applying them later in batch. This is useful when you need to record changes during Query iteration (when the World is locked), or when you want to amortize the cost of structural changes across many operations.

Creating a buffer

Create a CommandBuffer by providing the World and a tuple of operation specs:

world = World(Position, Velocity, Health);
buf = CommandBuffer(world, (
    NewEntityCommand((Position, Velocity)),
    RemoveEntityCommand(),
    AddComponentsCommand((Velocity,)),
    RemoveComponentsCommand((Velocity,)),
    ExchangeComponentsCommand(add=(Health,), remove=(Velocity,)),
));

# output

CommandBuffer{World{Ark._WorldStorage{Tuple{Ark._ComponentStorage{Position, Vector{Position}}, Ark._ComponentStorage{Velocity, Vector{Velocity}}, Ark._ComponentStorage{Health, Vector{Health}}}, (0x0000000000000000,)}, Ark._WorldState{1, 0}}, Union{AddComponentsCommand{Tuple{Velocity}}, ExchangeComponentsCommand{Tuple{Health}, Tuple{Velocity}}, NewEntityCommand{Tuple{Position, Velocity}}, RemoveComponentsCommand{Tuple{Velocity}}, RemoveEntityCommand}}(World(entities=0, comp_types=(Position, Velocity, Health)), Union{AddComponentsCommand{Tuple{Velocity}}, ExchangeComponentsCommand{Tuple{Health}, Tuple{Velocity}}, NewEntityCommand{Tuple{Position, Velocity}}, RemoveComponentsCommand{Tuple{Velocity}}, RemoveEntityCommand}[])

Each spec corresponds to one command type. The component types are captured at construction time so the buffer's internal storage is specialized and allocation-free. Arbitrary command types can also be included in the specs and recorded with record!.

Recording commands

All recording methods mirror the World API but take the buffer as an extra argument.

Creating entities

Use new_entity! to stage entity creation. An Entity ID is pre-allocated immediately and returned, allowing it to be used in subsequent commands before apply! is called. The returned entity is not considered alive until the buffer is applied.

world = World(Position, Velocity);
buf = CommandBuffer(world, (NewEntityCommand((Position, Velocity)),));

e = new_entity!(buf, (Position(1.0, 2.0), Velocity(10.0, 20.0)));
apply!(buf)

# output

CommandBuffer{World{Ark._WorldStorage{Tuple{Ark._ComponentStorage{Position, Vector{Position}}, Ark._ComponentStorage{Velocity, Vector{Velocity}}}, (0x0000000000000000,)}, Ark._WorldState{1, 0}}, NewEntityCommand{Tuple{Position, Velocity}}}(World(entities=1, comp_types=(Position, Velocity)), NewEntityCommand{Tuple{Position, Velocity}}[])

Applying commands

Call apply! to execute all staged commands in FIFO order:

world = World(Position, Velocity, Health);
buf = CommandBuffer(world, (
    NewEntityCommand((Position, Velocity)),
    AddComponentsCommand((Health,)),
));

e = new_entity!(buf, (Position(1.0, 2.0), Velocity(10.0, 20.0)));
add_components!(buf, e, (Health(1.0),));

apply!(buf)

# output

CommandBuffer{World{Ark._WorldStorage{Tuple{Ark._ComponentStorage{Position, Vector{Position}}, Ark._ComponentStorage{Velocity, Vector{Velocity}}, Ark._ComponentStorage{Health, Vector{Health}}}, (0x0000000000000000,)}, Ark._WorldState{1, 0}}, Union{AddComponentsCommand{Tuple{Health}}, NewEntityCommand{Tuple{Position, Velocity}}}}(World(entities=1, comp_types=(Position, Velocity, Health)), Union{AddComponentsCommand{Tuple{Health}}, NewEntityCommand{Tuple{Position, Velocity}}}[])

After apply! the buffer is cleared and can be reused.

Recording arbitrary commands

To coordinate world changes with non-world state, include a custom command type in the command specs, define apply!(world, command), and record command values with record!:

world = World(Position, Velocity, Health)

struct PushOnGridCommand
    grid::Matrix{Vector{Entity}}
    entity::Entity
end

function Ark.apply!(world, cmd::PushOnGridCommand)
    pos, = get_components(world, cmd.entity, (Position,))
    push!(cmd.grid[Int(pos.x), Int(pos.y)], cmd.entity)
    return
end

buf = CommandBuffer(world, (
    NewEntityCommand((Position,)),
    PushOnGridCommand,
))

grid = [Entity[] for _ in 1:2, _ in 1:2]

entity = new_entity!(buf, (Position(1.0, 1.0),))
record!(buf, PushOnGridCommand(grid, entity))

apply!(buf)

grid[1] # now it contains the entity created in the buffer

# output

1-element Vector{Entity}:
 Entity(2, 1)