Systems

Ark provides no systems as they are widely known in ECS implementations. This is a deliberate decision, based on these reasons:

  • Systems can be hard to integrate into frameworks, like a game engine's update loop. Ark wants to stay flexible and is completely engine-agnostic.
  • Systems are usually tied to queries in a 1:1 relation, while it can be very useful to combine multiple queries.
  • Systems and a scheduler are easy to implement, so this is left to the user.

Below, we provide an example for how to implement systems and a scheduler.

Systems example

Components

We start by defining our component types:

using Ark

struct Position
    x::Float64
    y::Float64
end
struct Velocity
    dx::Float64
    dy::Float64
end

Abstract system type

We write an abstract system type. This is optional, but useful for clarity and to avoid boilerplate.

abstract type System end

function initialize!(::System, ::World) end
function update!(::System, ::World) end
function finalize!(::System, ::World) end

Scheduler

Next, we build a (type-stable) scheduler:

struct Scheduler{ST<:Tuple}
    world::World
    systems::ST
end

function run!(s::Scheduler, steps::Int)
    # initialize all systems
    for sys in s.systems
        initialize!(sys, s.world)
    end

    # update loop
    for _ in 1:steps
        # update all systems
        for sys in s.systems
            update!(sys, s.world)
        end
    end
    
    # finalize all systems
    for sys in s.systems
        finalize!(sys, s.world)
    end
end

Initializer system

Now we can write some systems. First one that creates some entities:

struct InitializerSystem <: System
    count::Int
end

function initialize!(s::InitializerSystem, w::World)
    for (entities, positions, velocities) in new_entities!(w, s.count, (Position, Velocity))
        @inbounds for i in eachindex(entities)
            positions[i] = Position(rand() * 100, rand() * 100)
            velocities[i] = Velocity(randn(), randn())
        end
    end
end

As we have the abstract type, we only need to implement the functions that are actually required for the system.

Movement system

And here the classical movement system:

struct MovementSystem <: System end

function update!(s::InitializerSystem, w::World)
    for (entities, positions, velocities) in Query(world, (Position, Velocity))
        @inbounds for i in eachindex(entities)
            pos = positions[i]
            vel = velocities[i]
            positions[i] = Position(pos.x + vel.dx, pos.y + vel.dy)
        end
    end
end

Putting it together

Finally, we can plug everything together:

world = World(Position, Velocity)

scheduler = Scheduler(
    world,
    (
        InitializerSystem(100),
        MovementSystem(),
    ),
)

run!(scheduler, 1000)