Entity component system in Codea

Have anyone tried to make an ecs system work with Codea? Made an attempt using tiny-ecs, but it seems a bit limited compared to Bevy in rust for example.


--# Main
-- ECS
function setup()   
    -- download tinyecs if needed
    if readProjectTab("Tinyecs") == "" then
        http.request("https://raw.githubusercontent.com/bakpakin/tiny-ecs/master/tiny.lua", function (data) 
            data = string.gsub(data, "local tiny =", "tiny =")
            saveProjectTab("Tinyecs", data)
            print("please restart")
        end)
    else
        viewer.mode = FULLSCREEN
        setupWorld()
    end
end
    
function setupWorld()  
    -- system to bounce all entities with pos and vel
    local bounce = tiny.processingSystem()
    bounce.filter = tiny.requireAll("pos", "vel")
    function bounce:process(e, dt)
        e.pos = e.pos + e.vel * dt
        if e.pos.x < 0 or e.pos.x > WIDTH then
            e.vel.x = -e.vel.x
        end
        if e.pos.y < 100 or e.pos.y > (HEIGHT-100) then
            e.vel.y = -e.vel.y
        end
    end
    
    -- system to draw all entities with pos
    local drawSystem = tiny.processingSystem()
    drawSystem.filter = tiny.requireAll("pos")
    function drawSystem:process(e, dt)
        rect(e.pos.x, e.pos.y, 5,5)
    end
    
    world = tiny.world(bounce, drawSystem)
    
    -- add 20 random particles
    for i = 1, 20 do
        tiny.addEntity(world, {
            pos=vec2(100+i*5, HEIGHT/2),
            vel=vec2(math.random(150,300),0):rotate(i*10)
        })
    end
end

function draw()
    background(40, 40, 50)
    if world then
        world:update(DeltaTime)
    end
end

function touched(touch)
    if touch.state ~= ENDED then
        tiny.addEntity(world, {
            pos=touch.pos,
            vel=vec2(math.random(150,300),math.random(150,300))
        })
    end
end

--# Tinyecs
1 Like

Made another version with my own implementation of a simple ecs and some example code drawing sprites and particles that fade away.


--# Main
-- Cecs
function setup()
    viewer.mode = FULLSCREEN
    local bounce = ecs.System("pos and vel")
    function bounce:process(e, dt)
        e.pos = e.pos + e.vel * dt
        if e.pos.x < 0 or e.pos.x > WIDTH then
            e.vel.x = -e.vel.x
        end
        if e.pos.y < 100 or e.pos.y > (HEIGHT-100) then
            e.vel.y = -e.vel.y
        end
    end
    
    local drawPixels = ecs.System("pos and fill")
    function drawPixels:process(e, dt)
        fill(e.fill)
        rect(e.pos.x, e.pos.y, 8, 8)
    end
    
    local drawSprites = ecs.System("pos and (not fill)")
    function drawSprites:process(e, dt)
        sprite(asset.builtin.Space_Art.Asteroid_Small, e.pos.x, e.pos.y, 40, 40)
    end
    
    local fadeSystem = ecs.System("fill")
    function fadeSystem:process(e, dt)
        e.fill = e.fill:mix(color(0, 0), 1-dt*.1)
        if e.fill.a <= 150 then
            -- delete entity when faded
            return ecs.DELETE
        end
    end
    
    world = ecs.World(bounce, fadeSystem, drawPixels, drawSprites)
    
    -- add 20 random sprites
    for i = 1, 20 do
        world:add({
            pos=vec2(100+i*5, HEIGHT/2),
            vel=vec2(math.random(150,300),0):rotate(i*10),
        })
    end
end

function draw()
    background(40, 40, 50) 
    world:update(DeltaTime)
end

function touched(touch)
    if touch.state ~= ENDED then
        world:add({
            pos=touch.pos,
            vel=vec2(math.random(-150,150),math.random(-150,150)),
            fill=color(255, 176, 0)
        })
    end
end

--# Ecs
local DEBUG = true
local BOOLS = {}
BOOLS["and"] = true
BOOLS["or"] = true
BOOLS["not"] = true

-- compile a boolean expression to check if
-- keys exists in a table
-- filter("a not b")({a=1}) => true
local function filter(expr)
    local code = string.gsub(expr,
    "(%w+)", function (n)
        if BOOLS[n] then
            return n
        end
        return "(e['" .. n .. "'] ~= nil)"
    end)
    code = "return function(e) return (" .. code .. ") end"
    local fn, err = load(code)
    return fn()
end

local World = class()
function World:init(...)
    self.systems = {...}
    self.entities = {}
end
function World:add(...)
    for i, e in ipairs({...}) do
        table.insert(self.entities, e)
    end
end
function World:update(...)
    if DEBUG then
        fill(233, 80, 219)
        text("entities:" .. #self.entities, 50, HEIGHT-50)
    end
    for i,system in ipairs(self.systems) do
        for j,e in ipairs(self.entities) do
            if system.filter(e) then
                local res = system:process(e, ...)
                if res == ecs.DELETE then
                    table.remove(self.entities, j)
                end
            end
        end
    end
end

local System = class()
function System:init(expr)
    self.filter = filter(expr)
end

local function tests()
    m = filter("a and b")   
    assert(m({a=1,b=3}) == true)   
end
tests()

ecs = {
    World=World,
    System=System,
    filter=filter,
    DELETE={}
}

@tnlogy - interesting, your second post (your own) fired up an error in the 5th line, which I resolved by adding a — pair in to remark as the character you have there is something else? Your version is a neat little demo.

Looking at the first version you posted it wouldn’t run for me. I noted the heading for a separate tab posting at the bottom of the code so I assumed it needed the PD Tinyecs code included. Your call earlier on in that post didn’t work for me so I followed the link and copied the code there into a new tab. Ran it but it failed to run. I tried making a slimmed down Tinyecs from the code I had downloaded (taking out most of the comments) but that didn’t work either. The code looks a lot more involved than yours.

Thanks for the post, I’ll have a play to see if I can add anything to it after I finish my latest project.

Seems like my attempt to download tinyecs and remove ’local’ from the variable tiny didnt work. Strange, it worked locally here.

Anyway, I think it is a quite nice way to separate different concerns of a program. I will also try it out a bit more, just been experimenting with it the last day or so.

Tinyecs have been developed for quite some time, so it surely have some more features than my little hack :). I think it’s been used with Löve.

And I removed the strange comment in my code. Seems like this forum converts comments into hyphens —

@tnology - Aaaha ! It’s the forums fault. Doesn’t surprise me. Switching forums always seems to come with a period of adaption to each one’s eccentricities. I liked the Vanilla forum but this one is slowly growing on me.

Please post any progress you make with Tinyecs.

i made my own framework that basically works this way, i’ll be sharing it soon once i’ve recorded some videos on how it works

1 Like