mirror of
https://github.com/bakpakin/tiny-ecs.git
synced 2026-07-22 07:56:52 -06:00
Rename World:free to World:remove.
Fix World:free bug. Add __tostring method for Aspect and System. Add a few more tests. Add more comments. Give systems ids. Change injected "_id" field in Entities to "id" field.
This commit is contained in:
@@ -6,19 +6,19 @@ local jojo = {
|
|||||||
-- Simplified class implementation with no inheritance or polymorphism.
|
-- Simplified class implementation with no inheritance or polymorphism.
|
||||||
local setmetatable = setmetatable
|
local setmetatable = setmetatable
|
||||||
local function class()
|
local function class()
|
||||||
local c = {}
|
local c = {}
|
||||||
local mt = {}
|
local mt = {}
|
||||||
setmetatable(c, mt)
|
setmetatable(c, mt)
|
||||||
c.__index = c
|
c.__index = c
|
||||||
function mt.__call(_, ...)
|
function mt.__call(_, ...)
|
||||||
local newobj = {}
|
local newobj = {}
|
||||||
setmetatable(newobj, c)
|
setmetatable(newobj, c)
|
||||||
if c.init then
|
if c.init then
|
||||||
c.init(newobj, ...)
|
c.init(newobj, ...)
|
||||||
end
|
end
|
||||||
return newobj
|
return newobj
|
||||||
end
|
end
|
||||||
return c
|
return c
|
||||||
end
|
end
|
||||||
|
|
||||||
local World = class()
|
local World = class()
|
||||||
@@ -30,7 +30,6 @@ jojo.Aspect = Aspect
|
|||||||
jojo.System = System
|
jojo.System = System
|
||||||
|
|
||||||
local tinsert = table.insert
|
local tinsert = table.insert
|
||||||
local tremove = table.remove
|
|
||||||
local tconcat = table.concat
|
local tconcat = table.concat
|
||||||
local pairs = pairs
|
local pairs = pairs
|
||||||
local ipairs = ipairs
|
local ipairs = ipairs
|
||||||
@@ -82,7 +81,7 @@ function Aspect:init(required, excluded, oneRequired)
|
|||||||
-- Iterate through one-required Components
|
-- Iterate through one-required Components
|
||||||
for _, v in ipairs(oneRequired or {}) do
|
for _, v in ipairs(oneRequired or {}) do
|
||||||
if requiredSet[v] then -- If one-required Comp. is also required,
|
if requiredSet[v] then -- If one-required Comp. is also required,
|
||||||
-- don't need one required Components
|
-- don't need one required Components
|
||||||
self[3] = {}
|
self[3] = {}
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -97,17 +96,23 @@ end
|
|||||||
-- Composes multiple Aspects into one Aspect. The resulting Aspect will match
|
-- Composes multiple Aspects into one Aspect. The resulting Aspect will match
|
||||||
-- any Entity that matches all sub Aspects.
|
-- any Entity that matches all sub Aspects.
|
||||||
function Aspect.compose(...)
|
function Aspect.compose(...)
|
||||||
|
|
||||||
local newa = {{}, {}, {}}
|
local newa = {{}, {}, {}}
|
||||||
|
|
||||||
for _, a in ipairs{...} do
|
for _, a in ipairs{...} do
|
||||||
if a[4] then
|
|
||||||
|
if a[4] then -- Aspect must be empty Aspect
|
||||||
return Aspect()
|
return Aspect()
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, 3 do
|
for i = 1, 3 do
|
||||||
for _, c in ipairs(a[i]) do
|
for _, c in ipairs(a[i]) do
|
||||||
tinsert(newa[i], c)
|
tinsert(newa[i], c)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return Aspect(newa[1], newa[2], newa[3])
|
return Aspect(newa[1], newa[2], newa[3])
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -115,16 +120,26 @@ end
|
|||||||
|
|
||||||
-- Returns boolean indicating if an Entity matches the Aspect.
|
-- Returns boolean indicating if an Entity matches the Aspect.
|
||||||
function Aspect:matches(entity)
|
function Aspect:matches(entity)
|
||||||
|
|
||||||
|
-- Aspect is the empty Aspect
|
||||||
if self[4] then return false end
|
if self[4] then return false end
|
||||||
|
|
||||||
local rs, es, os = self[1], self[2], self[3]
|
local rs, es, os = self[1], self[2], self[3]
|
||||||
|
|
||||||
|
-- Assert Entity has all required Components
|
||||||
for i = 1, #rs do
|
for i = 1, #rs do
|
||||||
local r = rs[i]
|
local r = rs[i]
|
||||||
if entity[r] == nil then return false end
|
if entity[r] == nil then return false end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Assert Entity has no excluded Components
|
||||||
for i = 1, #es do
|
for i = 1, #es do
|
||||||
local e = es[i]
|
local e = es[i]
|
||||||
if entity[e] ~= nil then return false end
|
if entity[e] ~= nil then return false end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- if Aspect has at least one Component in the one-required
|
||||||
|
-- field, assert that the Entity has at least one of these.
|
||||||
if #os >= 1 then
|
if #os >= 1 then
|
||||||
for i = 1, #os do
|
for i = 1, #os do
|
||||||
local o = os[i]
|
local o = os[i]
|
||||||
@@ -132,24 +147,28 @@ function Aspect:matches(entity)
|
|||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Aspect:trace()
|
function Aspect:__tostring()
|
||||||
|
|
||||||
-- Prints out a description of this Aspect.
|
|
||||||
function Aspect:trace()
|
|
||||||
if self[4] then
|
if self[4] then
|
||||||
print("Empty Aspect.")
|
return "JojoAspect<>"
|
||||||
else
|
else
|
||||||
print("Required Components:", tconcat(self[1], ", "))
|
return "JojoAspect<Required: {" ..
|
||||||
print("Excluded Components:", tconcat(self[2], ", "))
|
tconcat(self[1], ", ") ..
|
||||||
print("One Req. Components:", tconcat(self[3], ", "))
|
"}, Excluded: {" ..
|
||||||
|
tconcat(self[2], ", ") ..
|
||||||
|
"}, One Req.: {" ..
|
||||||
|
tconcat(self[3], ", ") ..
|
||||||
|
"}>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
----- System -----
|
----- System -----
|
||||||
|
|
||||||
|
System.nextID = 0
|
||||||
|
|
||||||
-- System(preupdate, update, [aspect])
|
-- System(preupdate, update, [aspect])
|
||||||
|
|
||||||
-- Creates a new System with the given aspect and update callback. The update
|
-- Creates a new System with the given aspect and update callback. The update
|
||||||
@@ -162,6 +181,23 @@ function System:init(preupdate, update, aspect)
|
|||||||
self.update = update
|
self.update = update
|
||||||
self.aspect = aspect or Aspect()
|
self.aspect = aspect or Aspect()
|
||||||
self.active = true
|
self.active = true
|
||||||
|
local id = System.nextID
|
||||||
|
self.id = id
|
||||||
|
System.nextID = id + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function System:__tostring()
|
||||||
|
return "JojoSystem<id: "..
|
||||||
|
self.id ..
|
||||||
|
"preupdate: " ..
|
||||||
|
self.preupdate ..
|
||||||
|
", update: " ..
|
||||||
|
self.update ..
|
||||||
|
", aspect: " ..
|
||||||
|
self.aspect ..
|
||||||
|
", active: " ..
|
||||||
|
self.active ..
|
||||||
|
">"
|
||||||
end
|
end
|
||||||
|
|
||||||
----- World -----
|
----- World -----
|
||||||
@@ -172,35 +208,39 @@ end
|
|||||||
-- Systems after creation.
|
-- Systems after creation.
|
||||||
function World:init(...)
|
function World:init(...)
|
||||||
|
|
||||||
local args = {...}
|
local args = {...}
|
||||||
|
|
||||||
-- Table of Entity IDs to status
|
-- Table of Entity IDs to status
|
||||||
self.status = {}
|
self.status = {}
|
||||||
|
|
||||||
-- Table of Entity IDs to Entities
|
-- Table of Entity IDs to Entities
|
||||||
self.entities = {}
|
self.entities = {}
|
||||||
|
|
||||||
-- List of Systems
|
-- List of Systems
|
||||||
self.systems = args
|
self.systems = args
|
||||||
|
|
||||||
-- Accumulated time for each System.
|
-- Table of System IDs to System Indices
|
||||||
self.times = {}
|
local systemIndices = {}
|
||||||
|
self.systemIndices = systemIndices
|
||||||
|
|
||||||
-- Next available entity ID
|
-- Next available entity ID
|
||||||
self.nextID = 0
|
self.nextID = 0
|
||||||
|
|
||||||
-- Table of System indices to Sets of matching Entity IDs
|
-- Table of System indices to Sets of matching Entity IDs
|
||||||
local aspectEntities = {}
|
local systemEntities = {}
|
||||||
self.aspectEntities = aspectEntities
|
self.systemEntities = systemEntities
|
||||||
for i, sys in ipairs(args) do
|
|
||||||
aspectEntities[i] = {}
|
for i, sys in ipairs(args) do
|
||||||
end
|
systemEntities[i] = {}
|
||||||
|
systemIndices[sys.id] = i
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- World:add(...)
|
-- World:add(...)
|
||||||
|
|
||||||
-- Adds Entities to the World. An Entity is just a table of Components.
|
-- Adds Entities to the World. Entities will enter the World the next time
|
||||||
|
-- World:update(dt) is called.
|
||||||
function World:add(...)
|
function World:add(...)
|
||||||
local args = {...}
|
local args = {...}
|
||||||
local status = self.status
|
local status = self.status
|
||||||
@@ -208,7 +248,7 @@ function World:add(...)
|
|||||||
for _, e in ipairs(args) do
|
for _, e in ipairs(args) do
|
||||||
local id = self.nextID
|
local id = self.nextID
|
||||||
self.nextID = id + 1
|
self.nextID = id + 1
|
||||||
e._id = id
|
e.id = id
|
||||||
entities[id] = e
|
entities[id] = e
|
||||||
status[id] = "add"
|
status[id] = "add"
|
||||||
end
|
end
|
||||||
@@ -217,65 +257,88 @@ end
|
|||||||
-- World:changed(...)
|
-- World:changed(...)
|
||||||
|
|
||||||
-- Call this function on any Entities that have changed such that they would
|
-- Call this function on any Entities that have changed such that they would
|
||||||
-- now match different systems.
|
-- now match different systems. Entities will be updated in the world the next
|
||||||
function World:changed(...)
|
-- time World:update(dt) is called.
|
||||||
|
function World:change(...)
|
||||||
local args = {...}
|
local args = {...}
|
||||||
local status = self.status
|
local status = self.status
|
||||||
for _, e in ipairs(args) do
|
for _, e in ipairs(args) do
|
||||||
status[e._id] = "add"
|
status[e.id] = "add"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- World:free(...)
|
-- World:free(...)
|
||||||
|
|
||||||
-- Frees Entities from the World.
|
-- Removes Entities from the World. Entities will exit the World the next time
|
||||||
function World:free(...)
|
-- World:update(dt) is called.
|
||||||
|
function World:remove(...)
|
||||||
local args = {...}
|
local args = {...}
|
||||||
local status = self.status
|
local status = self.status
|
||||||
for _, e in ipairs(args) do
|
for _, e in ipairs(args) do
|
||||||
status[e._id] = "free"
|
status[e.id] = "remove"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- World:update()
|
-- World:update()
|
||||||
|
|
||||||
-- Updates the World.
|
-- Updates the World, frees Entities that have been marked for freeing, adds
|
||||||
|
-- entities that have been marked for adding, etc.
|
||||||
function World:update(dt)
|
function World:update(dt)
|
||||||
|
|
||||||
local statuses = self.status
|
local statuses = self.status
|
||||||
local aspectEntities = self.aspectEntities
|
local systemEntities = self.systemEntities
|
||||||
local entities = self.entities
|
local entities = self.entities
|
||||||
local systems = self.systems
|
local systems = self.systems
|
||||||
|
|
||||||
for eid, s in pairs(statuses) do
|
for eid, s in pairs(statuses) do
|
||||||
if s == "add" then
|
if s == "add" then
|
||||||
local e = entities[eid]
|
local e = entities[eid]
|
||||||
for sysi, set in pairs(aspectEntities) do
|
for sysid, eids in pairs(systemEntities) do
|
||||||
local a = systems[sysi].aspect
|
local a = systems[sysid].aspect
|
||||||
set[eid] = a:matches(e) and true or nil
|
eids[eid] = a:matches(e) and true or nil
|
||||||
end
|
end
|
||||||
statuses[eid] = nil
|
statuses[eid] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for sysi, s in ipairs(self.systems) do
|
for sysid, s in ipairs(self.systems) do
|
||||||
|
|
||||||
local preupdate = s.preupdate
|
local preupdate = s.preupdate
|
||||||
if s.active and preupdate then
|
local eids = systemEntities[sysid]
|
||||||
|
local active = s.active
|
||||||
|
|
||||||
|
-- Preupdate
|
||||||
|
if active and preupdate then
|
||||||
preupdate(dt)
|
preupdate(dt)
|
||||||
end
|
end
|
||||||
local eids = aspectEntities[sysi]
|
|
||||||
local u = s.update
|
if active then -- Free freed entities and update the others.
|
||||||
for eid in pairs(eids) do
|
|
||||||
local status = statuses[eid]
|
local u = s.update
|
||||||
if status == "free" then
|
|
||||||
eids[eid] = nil
|
for eid in pairs(eids) do
|
||||||
|
local status = statuses[eid]
|
||||||
|
if status == "remove" then
|
||||||
|
eids[eid] = nil
|
||||||
|
else
|
||||||
|
u(entities[eid], dt)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
u(entities[eid], dt)
|
|
||||||
|
else -- Just free freed Entities
|
||||||
|
|
||||||
|
for eid in pairs(eids) do
|
||||||
|
if statuses[eid] == "remove" then
|
||||||
|
eids[eid] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Reset all statuses for next update
|
||||||
for eid, s in pairs(statuses) do
|
for eid, s in pairs(statuses) do
|
||||||
if s == "free" then
|
if s == "remove" then
|
||||||
entities[eid].id = nil
|
entities[eid].id = nil
|
||||||
entities[eid] = nil
|
entities[eid] = nil
|
||||||
end
|
end
|
||||||
|
|||||||
+7
-3
@@ -80,16 +80,20 @@ local world = World(moves)
|
|||||||
world:add(e1, e2, e3)
|
world:add(e1, e2, e3)
|
||||||
world:update(21)
|
world:update(21)
|
||||||
assert(e1.xform.x == 21, "e1.xform.x should be 21, but is " .. e1.xform.x)
|
assert(e1.xform.x == 21, "e1.xform.x should be 21, but is " .. e1.xform.x)
|
||||||
|
assert(e2.xform.x == -19, "e2.xform.x should be -19, but is " .. e2.xform.x)
|
||||||
|
assert(e3.xform.y == 68, "e3.xform.y should be 68, but is " .. e3.xform.y)
|
||||||
|
|
||||||
world:free(e3, e2)
|
world:remove(e3, e2)
|
||||||
world:update(20)
|
world:update(20)
|
||||||
assert(e1.xform.x == 41, "e1.xform.x should be 41, but is " .. e1.xform.x)
|
assert(e1.xform.x == 41, "e1.xform.x should be 41, but is " .. e1.xform.x)
|
||||||
|
assert(e2.xform.x == -19, "e2.xform.x should be -19, but is " .. e2.xform.x)
|
||||||
|
assert(e3.xform.y == 68, "e3.xform.y should be 68, but is " .. e3.xform.y)
|
||||||
|
|
||||||
world:add(e3, e2)
|
world:add(e3, e2)
|
||||||
world:update(19)
|
world:update(19)
|
||||||
world:free(e3, e2)
|
world:remove(e3, e2)
|
||||||
e1.vel = nil
|
e1.vel = nil
|
||||||
world:changed(e1)
|
world:change(e1)
|
||||||
world:update(11)
|
world:update(11)
|
||||||
|
|
||||||
assert(e1.xform.x == 60, "e1.xform.x should be 60, but is " .. e1.xform.x)
|
assert(e1.xform.x == 60, "e1.xform.x should be 60, but is " .. e1.xform.x)
|
||||||
|
|||||||
Reference in New Issue
Block a user