I was trying to figure out what a minimal ECS might look like in Lua; here's what I came up with. This is based very loosely on darkf/microecs.
local function createSystem(components, process)
local function handle(entity)
for i, component in ipairs(components) do
if entity[component] == nil then return end
end
process(entity)
end
return function(entities)
for i, entity in ipairs(entities) do
handle(entity)
end
end
end
local function addComponent(entity, component, instance)
local meta = { __index = component }
entity[component] = setmetatable(instance or {}, meta)
return entity
end
local function createEntity(instance)
local meta = { __index = { addComponent = addComponent } }
return setmetatable(instance or {}, meta)
end
return {
createEntity = createEntity,
addComponent = addComponent,
createSystem = createSystem,
}
Here's how you could use it:
local ecs = require 'ecs'
local thing = ecs.createEntity()
local positionComponent = { x = 0, y = 0 }
local velocityComponent = { vx = 0, vy = 0 }
thing:addComponent(positionComponent, { x = 50, y = 60 })
thing:addComponent(velocityComponent, { vx = 5, vy = 6 })
local velocitySystem = ecs.createSystem(
{ positionComponent, velocityComponent },
function (entity)
local pos = entity[positionComponent]
local vel = entity[velocityComponent]
pos.x = pos.x + vel.vx
pos.y = pos.y + vel.vy
end)
Interestingly, the entities and components aren't nearly as important as the systems. The createEntity
function only exists so that you can use addComponent
in an object-oriented way, but you can just as easily write it like this:
local thing = {}
-- ...
ecs.addComponent(thing, positionComponent, { x = 50, y = 60 })
ecs.addComponent(thing, velocityComponent, { vx = 5, vy = 6 })
You can also ignore addComponent
if you don't care about the "safety" of having "typed" components.
local thing = {
position = { x = 50, y = 60 },
velocity = { vx = 5, vy = 6 },
}
local velocitySystem = ecs.createSystem(
{ 'position', 'velocity' },
function (entity)
local pos = entity.position
local vel = entity.velocity
pos.x = pos.x + vel.vx
pos.y = pos.y + vel.vy
end)
This means systems are useful on arbitrary data structures, with the table representing the entity and its properties representing components. Keying properties with strings like this seems more idiomatic from a Lua standpoint.
To have a system process entities, simply call the function returned from createSystem
, passing it a table of entities to process. Only the entities having the components specified in the first argument to createSystem
will be processed.
function tick()
velocitySystem({ thing, anotherThing })
anotherSystem({ thing, somethingElse })
end
All comments are welcome, but I'm more interested in hearing about the implementation itself than stylistic choices (like whether I should have used _
in place of i
, or 4 space indents instead of 2).