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:
bakpakin
2015-03-22 10:41:34 +08:00
parent f4c6ce1004
commit b98cd5a14d
2 changed files with 134 additions and 67 deletions
+127 -64
View File
@@ -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
View File
@@ -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)