6
\$\begingroup\$

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).

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.