Streamline entity lifetime

This commit is contained in:
Tjakka5 2019-12-19 08:47:38 +01:00
parent bc47eaa651
commit 038111d558
7 changed files with 117 additions and 163 deletions

View file

@ -22,20 +22,49 @@ local test_comp_3 = Concord.component("test_comp_3", function(e, b)
end) end)
local test_system = Concord.system({Component.test_comp_1}) local test_system = Concord.system({Component.test_comp_1})
function test_system:update(dt)
print(#self.pool) function onEntityAdded(e)
print("Added")
end end
function onEntityRemoved(e)
print("Removed")
end
function test_system:init()
self.pool.onEntityAdded = onEntityAdded
self.pool.onEntityRemoved = onEntityRemoved
end
function test_system:update(dt)
--print(#self.pool)
end
local world = Concord.world() local world = Concord.world()
local entity = Concord.entity() local entity = Concord.entity()
entity:give(Component.test_comp_2, 100, 100) entity:give(Component.test_comp_1, 100, 100)
entity:apply()
world:addEntity(entity) world:addEntity(entity)
world:addSystem(test_system(), "update") world:addSystem(test_system(), "update")
function love.update(dt) function love.update(dt)
world:flush()
world:emit("update", dt) world:emit("update", dt)
end
function love.keypressed(key)
if key == "q" then
entity:remove(Component.test_comp_1)
end
if key == "w" then
entity:give(Component.test_comp_1)
end
if key == "e" then
world:removeEntity(entity)
end
end end

View file

@ -51,4 +51,4 @@ return setmetatable(Component, {
__call = function(_, ...) __call = function(_, ...)
return Component.new(...) return Component.new(...)
end, end,
}) })

View file

@ -3,7 +3,6 @@
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
local Type = require(PATH..".type") local Type = require(PATH..".type")
local List = require(PATH..".list")
local Entity = {} local Entity = {}
Entity.__index = Entity Entity.__index = Entity
@ -12,8 +11,11 @@ Entity.__index = Entity
-- @return A new Entity -- @return A new Entity
function Entity.new() function Entity.new()
local e = setmetatable({ local e = setmetatable({
removed = {}, world = nil,
worlds = List(),
__isDirty = true,
__wasAdded = false,
__wasRemoved = false,
__isEntity = true, __isEntity = true,
}, Entity) }, Entity)
@ -25,7 +27,13 @@ local function give(e, component, ...)
local comp = component:__initialize(...) local comp = component:__initialize(...)
e[component] = comp e[component] = comp
e:mark() e.__isDirty = true
end
local function remove(e, component)
e[component] = nil
e.__isDirty = true
end end
--- Gives an Entity a component with values. --- Gives an Entity a component with values.
@ -37,10 +45,6 @@ function Entity:give(component, ...)
error("bad argument #1 to 'Entity:give' (Component expected, got "..type(component)..")", 2) error("bad argument #1 to 'Entity:give' (Component expected, got "..type(component)..")", 2)
end end
if self[component] then
self:remove(component):apply()
end
give(self, component, ...) give(self, component, ...)
return self return self
@ -68,9 +72,7 @@ function Entity:remove(component)
error("bad argument #1 to 'Entity:remove' (Component expected, got "..type(component)..")") error("bad argument #1 to 'Entity:remove' (Component expected, got "..type(component)..")")
end end
self.removed[#self.removed + 1] = component remove(self, component)
self:mark()
return self return self
end end
@ -85,28 +87,11 @@ function Entity:assemble(assemblage, ...)
return self return self
end end
function Entity:mark()
for i = 1, self.worlds.size do
self.worlds:get(i):markEntity(self)
end
end
function Entity:apply()
for i = 1, #self.removed do
local component = self.removed[i]
self[component] = nil
self.removed[i] = nil
end
return self
end
--- Destroys the Entity. --- Destroys the Entity.
-- @return self -- @return self
function Entity:destroy() function Entity:destroy()
for i = 1, self.worlds.size do if self.world then
self.worlds:get(i):removeEntity(self) self.world:removeEntity(self)
end end
return self return self

View file

@ -73,7 +73,7 @@ end
-- @param obj The object to search for -- @param obj The object to search for
-- true if the list has the object, false otherwise -- true if the list has the object, false otherwise
function List:has(obj) function List:has(obj)
return self[obj] and true return self[obj] and true or false
end end
return setmetatable(List, { return setmetatable(List, {

View file

@ -14,9 +14,6 @@ Pool.__index = Pool
function Pool.new(name, filter) function Pool.new(name, filter)
local pool = setmetatable(List(), Pool) local pool = setmetatable(List(), Pool)
pool.added = {}
pool.removed = {}
pool.name = name pool.name = name
pool.filter = filter pool.filter = filter
@ -25,18 +22,12 @@ function Pool.new(name, filter)
return pool return pool
end end
function Pool:flush()
for i = 1, math.max(#self.added, #self.removed) do
self.added[i], self.removed[i] = nil, nil
end
end
--- Checks if an Entity is eligible for the Pool. --- Checks if an Entity is eligible for the Pool.
-- @param e The Entity to check -- @param e The Entity to check
-- @return True if the entity is eligible, false otherwise -- @return True if the entity is eligible, false otherwise
function Pool:eligible(e) function Pool:eligible(e)
for _, component in ipairs(self.filter) do for _, component in ipairs(self.filter) do
if not e[component] or e.removed[component] then if not e[component] then
return false return false
end end
end end
@ -44,6 +35,22 @@ function Pool:eligible(e)
return true return true
end end
function Pool:add(e)
List.add(self, e)
self:onEntityAdded(e)
end
function Pool:remove(e)
List.remove(self, e)
self:onEntityRemoved(e)
end
function Pool:onEntityAdded(e)
end
function Pool:onEntityRemoved(e)
end
return setmetatable(Pool, { return setmetatable(Pool, {
__index = List, __index = List,
__call = function(_, ...) __call = function(_, ...)

View file

@ -9,7 +9,6 @@ System.mt = {
__index = System, __index = System,
__call = function(systemProto, ...) __call = function(systemProto, ...)
local system = setmetatable({ local system = setmetatable({
__all = {},
__pools = {}, __pools = {},
__world = nil, __world = nil,
@ -63,22 +62,15 @@ end
--- Checks and applies an Entity to the System's pools. --- Checks and applies an Entity to the System's pools.
-- @param e The Entity to check -- @param e The Entity to check
-- @return True if the Entity was added, false if it was removed. Nil if nothing happend function System:__evaluate(e)
function System:__check(e)
for _, pool in ipairs(self.__pools) do for _, pool in ipairs(self.__pools) do
local poolHas = pool:has(e) local has = pool:has(e)
local eligible = pool:eligible(e) local eligible = pool:eligible(e)
if not poolHas and eligible then if not has and eligible then
pool:add(e) pool:add(e)
pool.added[#pool.added + 1] = e elseif has and not eligible then
self:__tryAdd(e)
elseif poolHas and not eligible then
pool:remove(e) pool:remove(e)
pool.removed[#pool.removed + 1] = e
self:__tryRemove(e)
end end
end end
end end
@ -86,46 +78,16 @@ end
--- Remove an Entity from the System. --- Remove an Entity from the System.
-- @param e The Entity to remove -- @param e The Entity to remove
function System:__remove(e) function System:__remove(e)
if self.__all[e] then for _, pool in ipairs(self.__pools) do
for _, pool in ipairs(self.__pools) do if pool:has(e) then
if pool:has(e) then pool:remove(e)
pool:remove(e)
pool.removed[#pool.removed + 1] = e
end
end
self.__all[e] = nil
end
end
--- Tries to add an Entity to the System.
-- @param e The Entity to add
function System:__tryAdd(e)
if not self.__all[e] then
self.__all[e] = 0
end
self.__all[e] = self.__all[e] + 1
end
--- Tries to remove an Entity from the System.
-- @param e The Entity to remove
function System:__tryRemove(e)
if self.__all[e] then
self.__all[e] = self.__all[e] - 1
if self.__all[e] == 0 then
self.__all[e] = nil
end end
end end
end end
function System:flush() -- luacheck: ignore
end
function System:clear() function System:clear()
for i = 1, #self.__pools do for i = 1, #self.__pools do
self.__pools[i]:flush() self.__pools[i]:clear()
end end
end end
@ -142,7 +104,7 @@ end
-- Default callback for when the System is added to an World. -- Default callback for when the System is added to an World.
-- @param world The World the System was added to -- @param world The World the System was added to
function System:addedTo(World) -- luacheck: ignore function System:addedTo(world) -- luacheck: ignore
end end
-- Default callback for when a System's callback is enabled. -- Default callback for when a System's callback is enabled.

View file

@ -16,8 +16,8 @@ function World.new()
systems = List(), systems = List(),
events = {}, events = {},
marked = {}, __added = {},
removed = {}, __removed = {},
__isWorld = true, __isWorld = true,
}, World) }, World)
@ -33,16 +33,19 @@ function World:addEntity(e)
error("bad argument #1 to 'World:addEntity' (Entity expected, got "..type(e)..")", 2) error("bad argument #1 to 'World:addEntity' (Entity expected, got "..type(e)..")", 2)
end end
self:onEntityAdded(e) if e.world then
error("bad argument #1 to 'World:addEntity' (Entity was already added to a world)", 2)
end
e.world = self
e.__wasAdded = true
e.worlds:add(self)
self.entities:add(e) self.entities:add(e)
self:checkEntity(e)
return self return self
end end
--- Marks an Entity as removed from the World. --- Removes an entity from the World.
-- @param e The Entity to mark -- @param e The Entity to mark
-- @return self -- @return self
function World:removeEntity(e) function World:removeEntity(e)
@ -50,73 +53,46 @@ function World:removeEntity(e)
error("bad argument #1 to 'World:removeEntity' (Entity expected, got "..type(e)..")", 2) error("bad argument #1 to 'World:removeEntity' (Entity expected, got "..type(e)..")", 2)
end end
self.removed[#self.removed + 1] = e e.__wasRemoved = true
return self return self
end end
function World:markEntity(e)
if not Type.isEntity(e) then
error("bad argument #1 to 'World:markEntity' (Entity expected, got "..type(e)..")", 2)
end
self.marked[#self.marked + 1] = e
return self
end
--- Checks an Entity against all the systems in the World.
-- @param e The Entity to check
-- @return self
function World:checkEntity(e)
if not Type.isEntity(e) then
error("bad argument #1 to 'World:checkEntity' (Entity expected, got "..type(e)..")", 2)
end
for i = 1, self.systems.size do
self.systems:get(i):__check(e)
end
return self
end
--- Completely removes all marked Entities in the World.
-- @return self -- @return self
function World:flush() function World:flush()
while #self.marked > 0 do local e
local marked = self.removed for i = 1, self.entities.size do
self.removed = {} e = self.entities:get(i)
for i = 1, #marked do if e.__wasAdded then
local e = marked[i] e.__wasAdded = false
e.__isDirty = false
e.Worlds:apply() for i = 1, self.systems.size do
e.Worlds:checkEntity(e) self.systems:get(i):__evaluate(e)
end
end
while #self.removed > 0 do
local removed = self.removed
self.removed = {}
for i = 1, #removed do
local e = removed[i]
e.worlds:remove(self)
self.entities:remove(e)
for j = 1, self.systems.size do
self.systems:get(j):__remove(e)
end end
self:onEntityRemoved(e) self:onEntityAdded(e)
end end
end
for i = 1, self.systems.size do if e.__wasRemoved then
local system = self.systems:get(i) e.world = nil
system:flush() self.entities:remove(e)
system:clear()
for i = 1, self.systems.size do
self.systems:get(i):__remove(e)
end
e.__wasRemoved = false
end
if e.__isDirty then
for i = 1, self.systems.size do
self.systems:get(i):__evaluate(e)
end
e.__isDirty = false
end
end end
return self return self
@ -142,6 +118,10 @@ function World:addSystem(system, eventName, callback, enabled)
system.__World = self system.__World = self
system:addedTo(self) system:addedTo(self)
for i = 1, self.entities.size do
system:__evaluate(self.entities:get(i))
end
end end
if eventName then if eventName then
@ -159,13 +139,6 @@ function World:addSystem(system, eventName, callback, enabled)
end end
end end
local e
for i = 1, self.entities.size do
e = self.entities:get(i)
self:checkEntity(e)
end
return self return self
end end
@ -242,8 +215,6 @@ function World:emit(eventName, ...)
error("bad argument #1 to 'World:emit' (String expected, got "..type(eventName)..")") error("bad argument #1 to 'World:emit' (String expected, got "..type(eventName)..")")
end end
self:flush()
local listeners = self.events[eventName] local listeners = self.events[eventName]
if listeners then if listeners then