mirror of
https://github.com/Reuh/ubiquitousse.git
synced 2025-10-27 17:19:31 +00:00
ecs overhaul
Main incompatibility is passing the table entity[system.name] as first argument in a lot of callbacks
This commit is contained in:
parent
394c658d8b
commit
3a94c6b60d
1 changed files with 162 additions and 46 deletions
208
ecs/ecs.can
208
ecs/ecs.can
|
|
@ -5,10 +5,15 @@ if not loaded then scene = nil end
|
||||||
|
|
||||||
--- Entity Component System library, inspired by the excellent tiny-ecs. Main differences include:
|
--- Entity Component System library, inspired by the excellent tiny-ecs. Main differences include:
|
||||||
-- * ability to nest systems;
|
-- * ability to nest systems;
|
||||||
-- * instanciation of systems for each world;
|
-- * instanciation of systems for each world (no shared state);
|
||||||
-- * adding and removing entities is done instantaneously.
|
-- * adding and removing entities is done instantaneously
|
||||||
|
-- * ability to add and remove components from entities after they were added to the world.
|
||||||
|
let ecs
|
||||||
|
|
||||||
-- TODO: Implement a skip list for faster search.
|
-- TODO: Implement a skip list for faster search.
|
||||||
|
-- better control over system order: process, draw, methods? (for lag reasons and dependencies)
|
||||||
|
-- more generic events?
|
||||||
|
-- populate component?
|
||||||
|
|
||||||
--- Recursively remove subsystems from a system.
|
--- Recursively remove subsystems from a system.
|
||||||
let recDestroySystems = (system)
|
let recDestroySystems = (system)
|
||||||
|
|
@ -31,6 +36,17 @@ let recCallOnRemoveFromWorld = (world, systems)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Recursively get a list of systems with a certain method.
|
||||||
|
let recGetSystemsWithMethod = (method, systems, l={})
|
||||||
|
for _, s in ipairs(systems) do
|
||||||
|
if s[method] then
|
||||||
|
table.insert(l, s)
|
||||||
|
end
|
||||||
|
recGetSystemsWithMethod(method, s.systems, l)
|
||||||
|
end
|
||||||
|
return l
|
||||||
|
end
|
||||||
|
|
||||||
--- Iterate through the next entity, based on state s: { previousLinkedListItem }
|
--- Iterate through the next entity, based on state s: { previousLinkedListItem }
|
||||||
let nextEntity = (s)
|
let nextEntity = (s)
|
||||||
if s[1] then
|
if s[1] then
|
||||||
|
|
@ -42,16 +58,32 @@ let nextEntity = (s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Recursively content of a into b if it isn't already present. No cycle detection.
|
||||||
|
let copy = (a, b)
|
||||||
|
for k, v in pairs(a) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
if b[k] == nil then
|
||||||
|
b[k] = {}
|
||||||
|
copy(v, b[k])
|
||||||
|
elseif b[k] == "table" then
|
||||||
|
copy(v, b[k])
|
||||||
|
end
|
||||||
|
elseif b[k] == nil then
|
||||||
|
b[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- System fields and methods.
|
--- System fields and methods.
|
||||||
-- When they are added to a world, a new, per-world self table is created and used for every method call (which we call "instancied system").
|
-- When they are added to a world, a new, per-world self table is created and used for every method call (which we call "instancied system").
|
||||||
-- Instancied systems can be retrieved in system.s or system.systems.
|
-- Instancied systems can be retrieved in system.s or system.systems.
|
||||||
-- Oh, the "world" is just the top-level system.
|
-- Oh, the "world" is just the top-level system, behaving in exactly the same way as other systems.
|
||||||
let system_mt = {
|
let system_mt = {
|
||||||
--- Read-only after creation system options ---
|
--- Read-only after creation system options ---
|
||||||
-- I mean, you can try to change them afterwards. But, heh.
|
-- I mean, you can try to change them afterwards. But, heh.
|
||||||
|
|
||||||
--- Name of the system (optional).
|
--- Name of the system (optional).
|
||||||
-- Used to create a field with the system's name in world.system.
|
-- Used to create a field with the system's name in world.s.
|
||||||
name = nil,
|
name = nil,
|
||||||
|
|
||||||
--- List of subsystems.
|
--- List of subsystems.
|
||||||
|
|
@ -59,7 +91,7 @@ let system_mt = {
|
||||||
systems = nil,
|
systems = nil,
|
||||||
|
|
||||||
--- Returns true if the entity should be added to this system (and therefore its subsystems).
|
--- Returns true if the entity should be added to this system (and therefore its subsystems).
|
||||||
-- If this is a string, it will be converted to a filter function on instanciation using ecs.all.
|
-- If this is a string or a table, it will be converted to a filter function on instanciation using ecs.all.
|
||||||
-- By default, rejects everything.
|
-- By default, rejects everything.
|
||||||
filter = :(e) return false end,
|
filter = :(e) return false end,
|
||||||
--- Returns true if e1 <= e2.
|
--- Returns true if e1 <= e2.
|
||||||
|
|
@ -68,9 +100,9 @@ let system_mt = {
|
||||||
--- Modifiable system options ---
|
--- Modifiable system options ---
|
||||||
|
|
||||||
--- Called when adding an entity to the system.
|
--- Called when adding an entity to the system.
|
||||||
onAdd = :(e) end,
|
onAdd = :(s, e) end,
|
||||||
--- Called when removing an entity from the system.
|
--- Called when removing an entity from the system.
|
||||||
onRemove = :(e) end,
|
onRemove = :(s, e) end,
|
||||||
--- Called when the system is instancied, before any call to :onnAddToWorld (including other systems in the world).
|
--- Called when the system is instancied, before any call to :onnAddToWorld (including other systems in the world).
|
||||||
onInstance = :() end,
|
onInstance = :() end,
|
||||||
--- Called when the system is added to a world.
|
--- Called when the system is added to a world.
|
||||||
|
|
@ -84,9 +116,9 @@ let system_mt = {
|
||||||
--- Called when drawing the system.
|
--- Called when drawing the system.
|
||||||
onDraw = :() end,
|
onDraw = :() end,
|
||||||
--- Called when updating the system, for every entity the system contains. Called after :onUpdate was called on the system.
|
--- Called when updating the system, for every entity the system contains. Called after :onUpdate was called on the system.
|
||||||
process = :(e, dt) end,
|
process = :(s, e, dt) end,
|
||||||
--- Called when drawing the system, for every entity the system contains. Called after :onDraw was called on the system.
|
--- Called when drawing the system, for every entity the system contains. Called after :onDraw was called on the system.
|
||||||
render = :(e) end,
|
render = :(s, e) end,
|
||||||
|
|
||||||
--- If not false, the system will only update every interval seconds.
|
--- If not false, the system will only update every interval seconds.
|
||||||
interval = false,
|
interval = false,
|
||||||
|
|
@ -95,50 +127,75 @@ let system_mt = {
|
||||||
--- The system and its subsystems will only draw if this is true.
|
--- The system and its subsystems will only draw if this is true.
|
||||||
visible = true,
|
visible = true,
|
||||||
|
|
||||||
|
--- Defaults value to put into the entities's system table when they are added. Will recursively fill missing values.
|
||||||
|
default = nil,
|
||||||
|
|
||||||
--- Read-only system options ---
|
--- Read-only system options ---
|
||||||
|
|
||||||
--- The world the system belongs to.
|
--- The world the system belongs to.
|
||||||
world = nil,
|
world = nil,
|
||||||
--- Number of entities in the system.
|
--- Number of entities in the system.
|
||||||
entityCount = 0,
|
entityCount = 0,
|
||||||
--- Map of named systems in the world (not only subsystems).
|
--- Map of named systems in the world (not only subsystems). Same for every system from the same world.
|
||||||
s = nil,
|
s = nil,
|
||||||
|
|
||||||
--- Private fields ---
|
--- Private fields ---
|
||||||
|
|
||||||
--- First element of the linked list of entities.
|
--- First element of the linked list of entities: { entity, next_element }.
|
||||||
_first = nil,
|
_first = nil,
|
||||||
|
--- Associative map of entities in the system and their previous linked list element (or true if first element).
|
||||||
|
-- This make the list effectively a doubly linked list, but with easy access to the previous element using this map (and therefore O(1) deletion).
|
||||||
|
_previous = nil,
|
||||||
--- Amount of time waited since last update (if interval is set).
|
--- Amount of time waited since last update (if interval is set).
|
||||||
_waited = 0,
|
_waited = 0,
|
||||||
|
--- Metatable of entities' m method table. Same for every system from the same world.
|
||||||
|
_entity_m_mt = nil,
|
||||||
|
|
||||||
--- Methods ---
|
--- Methods ---
|
||||||
|
|
||||||
--- Add entities to the system and its subsystems.
|
--- Add entities to the system and its subsystems.
|
||||||
|
-- Will skip entities that are already in the system.
|
||||||
-- Entities are added to subsystems after they were succesfully added to their parent system.
|
-- Entities are added to subsystems after they were succesfully added to their parent system.
|
||||||
-- If this is called on a subsystem instead of the world, be warned that this will bypass all the parent's systems filters.
|
-- If this is called on a subsystem instead of the world, be warned that this will bypass all the parent's systems filters.
|
||||||
-- Since :remove will not search for entities in systems where they should have been filtered out, the added entities will not be removed
|
-- Since :remove will not search for entities in systems where they should have been filtered out, the added entities will not be removed
|
||||||
-- when calling :remove on a parent system or the world. The entity can only be removed by calling :remove on the system :add was called on.
|
-- when calling :remove on a parent system or the world. The entity can be removed by calling :remove on the system :add was called on.
|
||||||
add = :(e, ...)
|
add = :(e, ...)
|
||||||
if e ~= nil and @filter(e) then
|
if e ~= nil and not @_previous[e] and @filter(e) then
|
||||||
|
-- setup entity
|
||||||
|
if not e.m then
|
||||||
|
e.m = setmetatable({ _entity = e }, @_entity_m_mt)
|
||||||
|
end
|
||||||
|
-- add to linked list
|
||||||
if @_first == nil then
|
if @_first == nil then
|
||||||
@_first = { e, nil }
|
@_first = { e, nil }
|
||||||
|
@_previous[e] = true
|
||||||
elseif @compare(e, @_first[1]) then
|
elseif @compare(e, @_first[1]) then
|
||||||
@_first = { e, @_first }
|
let nxt = @_first
|
||||||
|
@_first = { e, nxt }
|
||||||
|
@_previous[e] = true
|
||||||
|
@_previous[nxt[1]] = @_first
|
||||||
else
|
else
|
||||||
let entity = @_first
|
let entity = @_first
|
||||||
while entity[2] ~= nil do
|
while entity[2] ~= nil do
|
||||||
if @compare(e, entity[2][1]) then
|
if @compare(e, entity[2][1]) then
|
||||||
entity[2] = { e, entity[2] }
|
let nxt = entity[2]
|
||||||
|
entity[2] = { e, nxt }
|
||||||
|
@_previous[e] = entity
|
||||||
|
@_previous[nxt[1]] = entity[2]
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
entity = entity[2]
|
entity = entity[2]
|
||||||
end
|
end
|
||||||
if entity[2] == nil then
|
if entity[2] == nil then
|
||||||
entity[2] = { e, nil }
|
entity[2] = { e, nil }
|
||||||
|
@_previous[e] = entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- notify addition
|
||||||
@entityCount += 1
|
@entityCount += 1
|
||||||
@onAdd(e)
|
if (@default and e[@name]) copy(@default, e[@name])
|
||||||
|
@onAdd(e[@name], e)
|
||||||
|
-- add to subsystems
|
||||||
for _, s in ipairs(@systems) do
|
for _, s in ipairs(@systems) do
|
||||||
s:add(e)
|
s:add(e)
|
||||||
end
|
end
|
||||||
|
|
@ -149,35 +206,57 @@ let system_mt = {
|
||||||
return e
|
return e
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
--- Remove entities to the system and its subsystems.
|
--- Refresh an entity's systems.
|
||||||
-- Entities are removed from subsystems after they were succesfully removed from their parent system.
|
-- Behave similarly to :add, but if the entity is already in the system, instead of skipping it, it
|
||||||
-- If you intend to call this on a subsystem instead of the world, please read the warning in :add.
|
-- will check for new and removed components and add and remove from (sub)systems accordingly.
|
||||||
remove = :(e, ...)
|
refresh = :(e, ...)
|
||||||
if e ~= nil and @filter(e) then
|
if e ~= nil then
|
||||||
let found = false
|
if not @_previous[e] then
|
||||||
if @_first == nil then
|
@add(e)
|
||||||
return
|
elseif @_previous[e] then
|
||||||
elseif @_first[1] == e then
|
if not @filter(e) then
|
||||||
@_first = @_first[2]
|
@remove(e)
|
||||||
found = true
|
else
|
||||||
else
|
for _, s in ipairs(@systems) do
|
||||||
let entity = @_first
|
s:refresh(e)
|
||||||
while entity[2] ~= nil do
|
|
||||||
if entity[2][1] == e then
|
|
||||||
entity[2] = entity[2][2]
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
end
|
end
|
||||||
entity = entity[2]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if found then
|
end
|
||||||
for _, s in ipairs(@systems) do
|
if ... then
|
||||||
s:remove(e)
|
return e, @refresh(...)
|
||||||
end
|
else
|
||||||
@entityCount -= 1
|
return e
|
||||||
@onRemove(e)
|
end
|
||||||
|
end,
|
||||||
|
--- Remove entities to the system and its subsystems.
|
||||||
|
-- Will skip entities that are not in the system.
|
||||||
|
-- Entities are removed from subsystems before they are removed from their parent system.
|
||||||
|
-- If you intend to call this on a subsystem instead of the world, please read the warning in :add.
|
||||||
|
-- Returns all removed entities.
|
||||||
|
remove = :(e, ...)
|
||||||
|
if e ~= nil and @_previous[e] then
|
||||||
|
-- remove from subsystems
|
||||||
|
for _, s in ipairs(@systems) do
|
||||||
|
s:remove(e)
|
||||||
end
|
end
|
||||||
|
-- remove from linked list
|
||||||
|
let prev = @_previous[e]
|
||||||
|
if prev == true then
|
||||||
|
@_first = @_first[2]
|
||||||
|
if @_first then
|
||||||
|
@_previous[@_first[1]] = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
prev[2] = prev[2][2]
|
||||||
|
if prev[2] then
|
||||||
|
@_previous[prev[2][1]] = prev
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- notify removal
|
||||||
|
@_previous[e] = nil
|
||||||
|
@entityCount -= 1
|
||||||
|
@onRemove(e[@name], e)
|
||||||
end
|
end
|
||||||
if ... then
|
if ... then
|
||||||
return e, @remove(...)
|
return e, @remove(...)
|
||||||
|
|
@ -185,6 +264,15 @@ let system_mt = {
|
||||||
return e
|
return e
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
--- Returns true if every entity is in the system.
|
||||||
|
has = :(e, ...)
|
||||||
|
let has = e == nil or not not @_previous[e]
|
||||||
|
if ... then
|
||||||
|
return has and @has(...)
|
||||||
|
else
|
||||||
|
return has
|
||||||
|
end
|
||||||
|
end,
|
||||||
--- Returns an iterator that iterate through the entties in this system.
|
--- Returns an iterator that iterate through the entties in this system.
|
||||||
iter = :()
|
iter = :()
|
||||||
return nextEntity, { @_first }
|
return nextEntity, { @_first }
|
||||||
|
|
@ -211,7 +299,7 @@ let system_mt = {
|
||||||
@onUpdate(dt)
|
@onUpdate(dt)
|
||||||
if @process ~= system_mt.process then
|
if @process ~= system_mt.process then
|
||||||
for e in @iter() do
|
for e in @iter() do
|
||||||
@process(e, dt)
|
@process(e[@name], e, dt)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for _, s in ipairs(@systems) do
|
for _, s in ipairs(@systems) do
|
||||||
|
|
@ -229,7 +317,7 @@ let system_mt = {
|
||||||
@onDraw()
|
@onDraw()
|
||||||
if @render ~= system_mt.render then
|
if @render ~= system_mt.render then
|
||||||
for e in @iter() do
|
for e in @iter() do
|
||||||
@render(e)
|
@render(e[@name], e)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for _, s in ipairs(@systems) do
|
for _, s in ipairs(@systems) do
|
||||||
|
|
@ -253,7 +341,9 @@ let recInstanciateSystems = (world, systems)
|
||||||
table.insert(t, setmetatable({
|
table.insert(t, setmetatable({
|
||||||
systems = recInstanciateSystems(world, s.systems or {}),
|
systems = recInstanciateSystems(world, s.systems or {}),
|
||||||
world = world,
|
world = world,
|
||||||
s = world.s
|
s = world.s,
|
||||||
|
_previous = {},
|
||||||
|
_entity_m_mt = world._entity_m_mt
|
||||||
}, {
|
}, {
|
||||||
__index = :(k)
|
__index = :(k)
|
||||||
if s[k] ~= nil then
|
if s[k] ~= nil then
|
||||||
|
|
@ -266,6 +356,8 @@ let recInstanciateSystems = (world, systems)
|
||||||
let system = t[#t]
|
let system = t[#t]
|
||||||
if type(s.filter) == "string" then
|
if type(s.filter) == "string" then
|
||||||
system.filter = (_, e) return e[s.filter] ~= nil end
|
system.filter = (_, e) return e[s.filter] ~= nil end
|
||||||
|
elseif type(s.filter) == "table" then
|
||||||
|
system.filter = ecs.all(unpack(s.filter))
|
||||||
end
|
end
|
||||||
if s.name then
|
if s.name then
|
||||||
world.s[s.name] = system
|
world.s[s.name] = system
|
||||||
|
|
@ -287,15 +379,38 @@ let alwaysTrue = () return true end
|
||||||
let alwaysFalse = () return true end
|
let alwaysFalse = () return true end
|
||||||
|
|
||||||
--- ECS module.
|
--- ECS module.
|
||||||
let ecs = {
|
ecs = {
|
||||||
--- Create and returns a world system based on a list of systems.
|
--- Create and returns a world system based on a list of systems.
|
||||||
-- The systems will be instancied for this world.
|
-- The systems will be instancied for this world.
|
||||||
-- @impl ubiquitousse
|
-- @impl ubiquitousse
|
||||||
world = (...)
|
world = (...)
|
||||||
let world = setmetatable({
|
let world = setmetatable({
|
||||||
filter = ecs.all(),
|
filter = ecs.all(),
|
||||||
s = {}
|
s = {},
|
||||||
|
_previous = {},
|
||||||
|
_entity_m_mt = setmetatable({}, {
|
||||||
|
__index = (_entity_m_mt, k)
|
||||||
|
let s = recGetSystemsWithMethod(k, {world})
|
||||||
|
_entity_m_mt[k] = (m, ...)
|
||||||
|
let e = m._entity
|
||||||
|
let args = {...}
|
||||||
|
for _, sys in ipairs(s) do
|
||||||
|
if sys._previous[e] then
|
||||||
|
let r = { sys[k](sys, e[sys.name], e, unpack(args)) }
|
||||||
|
if r[1] == false then
|
||||||
|
break
|
||||||
|
elseif r[1] == true then
|
||||||
|
args = { select(2, unpack(r)) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return unpack(args)
|
||||||
|
end
|
||||||
|
return _entity_m_mt[k]
|
||||||
|
end
|
||||||
|
})
|
||||||
}, { __index = system_mt })
|
}, { __index = system_mt })
|
||||||
|
world._entity_m_mt.__index = world._entity_m_mt
|
||||||
world.world = world
|
world.world = world
|
||||||
world.systems = recInstanciateSystems(world, {...})
|
world.systems = recInstanciateSystems(world, {...})
|
||||||
recCallOnAddToWorld(world, world.systems)
|
recCallOnAddToWorld(world, world.systems)
|
||||||
|
|
@ -341,6 +456,7 @@ let ecs = {
|
||||||
--- If uqt.scene is available, returns a new scene that will consist of a ECS world with the specified systems and entities.
|
--- If uqt.scene is available, returns a new scene that will consist of a ECS world with the specified systems and entities.
|
||||||
-- @impl ubiquitousse
|
-- @impl ubiquitousse
|
||||||
scene = (name, systems={}, entities={})
|
scene = (name, systems={}, entities={})
|
||||||
|
assert(scene, "ubiquitousse.scene unavailable")
|
||||||
let s = scene.new(name)
|
let s = scene.new(name)
|
||||||
let w
|
let w
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue