Here's a followup to Minimal entity component system. Please see that question for background.
-- ecs.lua
local unpack = table.unpack or _G.unpack
local function addComponent(entity, component, instance)
entity[component] = setmetatable(instance or {}, { __index = component })
return entity
end
local function getComponentsTable(entity, components)
local values = {}
for i, component in ipairs(components) do
local value = entity[component]
if value == nil then return end
table.insert(values, value)
end
return values
end
local function getComponents(...)
return unpack(getComponentsTable(...) or {})
end
local entityPrototype = {
addComponent = addComponent,
getComponents = getComponents,
getComponentsTable = getComponentsTable,
}
local function createEntity(instance)
return setmetatable(instance or {}, { __index = entityPrototype })
end
local function createSystem(components, process)
local function handle(entity)
local values = getComponentsTable(entity, components)
if values then
process(entity, unpack(values))
end
end
return function(entities)
for i, entity in ipairs(entities) do
handle(entity)
end
end
end
local function each(entities, components)
local i = 0
return function()
while true do
i = i + 1
local entity = entities[i]
if not entity then return end
local values = getComponentsTable(entity, components or {})
if values then return entity, unpack(values) end
end
end
end
return {
createEntity = createEntity,
addComponent = addComponent,
getComponents = getComponents,
getComponentsTable = getComponentsTable,
createSystem = createSystem,
each = each,
}
This version adds the following features:
System callbacks now receive not only the entity, but also any components listed in the first argument to
createSystem
. In other words you can do this now:local rectangleRenderingSystem = ecs.createSystem( { 'position', 'rectangle' }, function (entity, pos, rect) ui.drawRect(pos, rect) end)
This is nice, because if you switch from string-keyed components to table-keyed components (using
addComponent
), nothing in the function body needs to change.There is a new function
getComponents
which does something similar.local foo, bar = ecs:getComponents(someEntity, { 'foo', 'bar' }) -- or local foo, bar = someEntity:getComponents({ FooComponent, BarComponent })
There is a new function
each
which is pretty useful. It takes an array of entities as its first argument, and an array of components as the second argument. It returns a function which iterates over all of the entities having matching components, returning the entity followed by each component in the list, just like the argument lists the systems callbacks receive. In other words you can do something like this:-- stop all the monsters -- we don't care about the value of the monster component, only that it exists for entity, vel in ecs.each(entities, { 'velocity', 'monster' }) do vel.vx = 0 vel.vy = 0 end
For a usage example, see Pong game built on a minimal entity component system.