Overseer
This package supplies a lightweight, performant and julian implementation of the Entity Component System (ECS) programming paradigm. It is most well known for its applications in game development, but I believe it's a programming paradigm that can benefit a broad range of applications. It results in a very clean and flexible way to gradually build up applications in well separated blocks, while remaining inherently performant due to the way data is structured and accessed.
The API and performance of this package are being thoroughly tested in practice in the development of:
- Glimpse.jl: a mid level rendering toolkit
- Trading.jl: a comprehensive realtime trading and backtesting framework
- RomeoDFT.jl: a robust global DFT based energy optimizer
Illustrative Example
You can think of Entities as simply an identifier into Components which are essentially Vectors of data. The key observation is that a System doesn't particularly care which Entity it is handling, only that it has the right data, i.e. that it has entries in the right Components.
We illustrate the concept of ECS with a very basic example were a Mover system will change the position of Entities based on their velocity.
We start by defining a Position and Velocity Component:
using Overseer
@component mutable struct Position
position::Vector{Float64}
end
@component struct Velocity
velocity::Vector{Float64}
endSystems are represented by a subtype of System, usually these are empty since they should signify the purely functional part of ECS.
struct Mover <: System end
function Overseer.update(::Mover, l::AbstractLedger)
dt = 0.01
for e in @entities_in(l, Position && Velocity)
e.position .+= e.velocity .* dt
end
endWe see that the Mover system iterates through all entities that have both the Position and Velocity Component (i.e. have data in both components), and updates their position.
Now we can create a Ledger which holds all the Entities, Systems and Components:
l = Ledger(Stage(:basic, [Mover()]))Ledger
Components:
Total entities: 0
a Stage is essentially a way to group a set of Systems with some additional DAG-like features.
We can then add a couple of Entities to our ledger
e1 = Entity(l, Position([1,0,0]), Velocity([1,1,1]))
e2 = Entity(l, Position([2,0,1]), Velocity([1,-1,1]))
e3 = Entity(l, Position([2,0,1]))
lLedger
Components:
3-element Component{Main.Position}
2-element Component{Main.Velocity}
Total entities: 3
Then, we can execute all the Systems by calling update on the ledger
update(l)and look at the final positions
l[Position][e1], l[Position][e2], l[Position][e3](Main.Position([1.01, 0.01, 0.01]), Main.Position([2.01, -0.01, 1.01]), Main.Position([2.0, 0.0, 1.0]))We see that the position of e3 did not get updated since it did not have the Velocity component and as such was not touched by the Mover system.