Thoughts on this ECS setup?

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Thoughts on this ECS setup?

Post by airstruck »

Here's what I've been doing for ECS lately. I wanted to get your feedback on it.

In this setup entities are tables, and components are properties of those tables. I have a system.lua that looks something like this:

Code: Select all

--- Run `process` if all varargs are not nil.
local function invoke (process, ...)
    for i = 1, select('#', ...) do
        if select(i, ...) == nil then return end
    end
    process(...)
end

--- Create a System.
--- @tparam function (entity, ...) extract
---         Extracts components from an entity, and returns all components
---         and any other arguments passed the `process` function.
--- @tparam function (...) process
---         Process components and other data returned by `extract`.
return function (extract, process)
    return function (entities, ...)
        for _, entity in ipairs(entities) do
            invoke(process, extract(entity, ...))
        end
    end
end
Individual systems look like this:

Code: Select all

-- system/update/move.lua

return require 'system' (
    function (entity, dt)
        return entity.position, entity.velocity, dt
    end,
    function (p, v, dt)
        p.x = p.x + v.x * dt
        p.y = p.y + v.y * dt
    end
)
In-play gamestates look something like this:

Code: Select all

local game = {}

local updateSystems = {
    require 'system.update.attack',
    require 'system.update.fall',
    require 'system.update.move',
    require 'system.update.sleep',
    require 'system.update.think',
    DEBUG and require 'system.update.log',
    -- more stuff here
}

local drawSystems = {
    require 'system.draw.sprite',
    DEBUG and require 'system.draw.hitbox',
}

function game:update (dt)
    for _, update in ipairs(updateSystems) do
        update(entities, dt)
    end
end

function game:draw ()
    for _, draw in ipairs(drawSystems) do
        draw(entities)
    end
end

return game
That's pretty much it. I realize the select(i, ...) stuff won't JIT and I'll replace it with some code generation at some point. I also realize there's no caching, but I don't care. In almost every situation I've tested, caching actually gave poorer performance than not caching. This has to do with the number of entities and systems; if you don't have many entities (maybe < 10k) and most of your systems apply to most of your entities, you probably won't benefit much from caching (because of the overhead from creating tables and storing results there). If the cache is getting invalidated a lot from stuff getting created or destroyed, caching can slow things down. Other than that, do you guys see any problems with this approach?
User avatar
bakpakin
Party member
Posts: 114
Joined: Sun Mar 15, 2015 9:29 am
Location: Boston

Re: Thoughts on this ECS setup?

Post by bakpakin »

Looks simple and bulletproof. Caching is hard to get right, and to do it right, you really shouldn't have to copy data. This is probably fast and flexible enough for most uses.
((_((_CRAYOLA_((_((_> GitHub <_((_((_CRAYOLA_((_(()
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Thoughts on this ECS setup?

Post by airstruck »

bakpakin wrote:Caching is hard to get right, and to do it right, you really shouldn't have to copy data.
I'm not sure what you mean, isn't a cache pretty much a copy of data?

The problem with caching is you need another layer between the thing that accepts entities and the thing that processes them. You need to store which entities each system should process. When entities are getting added and removed every few frames, this seems to have an impact on performance that caching doesn't always make up for.

Typically you'd also need to manage the list of entities, and the addition and removal of components, so you can invalidate the cache. I think attaching a filter function to each system and allowing the user to memoize that and reconstruct the entity list themselves could work. The systems just return functions right now, but they could return callable tables with a filter method. I'm just not sure I want that extra layer.
User avatar
bakpakin
Party member
Posts: 114
Joined: Sun Mar 15, 2015 9:29 am
Location: Boston

Re: Thoughts on this ECS setup?

Post by bakpakin »

No, your totally right. When I say cache in this context, I mean cache references. But an extra reference to a table isn't that expensive.

The point about not caching systems that are run for every entity is a good, valid point. For tiny-ecs, I recently just added an option to not use caching for certain systems.

There are some cases where you might need caching, though. Like a sorted system. 95% percent of the time, though, the really simple method of just iterating through all entities for each system will work fine. You just probably can't have too many systems.
((_((_CRAYOLA_((_((_> GitHub <_((_((_CRAYOLA_((_(()
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 104 guests