diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..b557738 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1 @@ +std="love+luajit" diff --git a/TODO b/TODO index 4ef844e..33fc816 100644 --- a/TODO +++ b/TODO @@ -3,6 +3,13 @@ [x] Add Entity:ensure (maybe?) [ ] Put pools in the Instance and invert dependency. [ ] Share pools between systems -[ ] Remove System callbacks +[x] Remove System callbacks [x] Put .added and .removed in pools so they can be iterated over -[ ] Implement assemblages +[x] Implement assemblages +[x] Do :apply automatically with marked entities +[x] Remove Entity.components. Use Entity[Component] instead + +[ ] Add missing documentation +[ ] Fix current documentation +[ ] Write unit tests +[ ] Write examples diff --git a/examples/assemblageTest/init.lua b/examples/assemblageTest/init.lua new file mode 100644 index 0000000..730129b --- /dev/null +++ b/examples/assemblageTest/init.lua @@ -0,0 +1,67 @@ +local Concord = require("lib").init({ + useEvents = true +}) +local Entity = Concord.entity +local Component = Concord.component +local System = Concord.system +local Assemblage = Concord.assemblage + +local Game = Concord.instance() +Concord.addInstance(Game) + +local Legs = Component(function(e, legCount) + e.legCount = legCount or 0 +end) + +local Instinct = Component(function(e) -- luacheck: ignore +end) + +local Cool = Component(function(e, coolness) + e.coolness = coolness +end) + +local Wings = Component(function(e) + e.wingCount = 2 +end) + + +local Animal = Assemblage(function(e, legCount) + e + :give(Legs, legCount) + :give(Instinct) + + print("Animal") +end) + +local Lion = Assemblage(function(e, coolness) + e + :assemble(Animal, 4) + :give(Cool, coolness) + + print("Lion") +end) + +local Eagle = Assemblage(function(e) + e + :assemble(Animal, 2) + :give(Wings) + + print("Eagle") +end) + +local Griffin = Assemblage(function(e, coolness) + e + :assemble(Animal, 4) + :assemble(Lion, coolness * 2) + :assemble(Eagle) +end) + + +local myAnimal = Entity() +:assemble(Griffin, 5) +--:apply() + +print(myAnimal:has(Legs)) +print(myAnimal:has(Instinct)) +print(myAnimal:has(Cool)) +print(myAnimal:has(Wings)) diff --git a/examples/simpleDrawing/init.lua b/examples/simpleDrawing/init.lua index f9a2d9f..8cc0426 100644 --- a/examples/simpleDrawing/init.lua +++ b/examples/simpleDrawing/init.lua @@ -78,7 +78,7 @@ end function RandomRemover:update(dt) self.time = self.time + dt - if self.time >= 0.5 then + if self.time >= 0.05 then self.time = 0 if self.pool.size > 0 then @@ -95,7 +95,7 @@ Game:addSystem(RandomRemover(), "update") Game:addSystem(RectangleRenderer(), "draw") Game:addSystem(CircleRenderer(), "draw") -for i = 1, 100 do +for _ = 1, 100 do local e = Entity() e:give(Position, love.math.random(0, 700), love.math.random(0, 700)) e:give(Rectangle, love.math.random(5, 20), love.math.random(5, 20)) @@ -107,7 +107,7 @@ for i = 1, 100 do Game:addEntity(e) end -for i = 1, 100 do +for _ = 1, 100 do local e = Entity() e:give(Position, love.math.random(0, 700), love.math.random(0, 700)) e:give(Circle, love.math.random(5, 20)) diff --git a/lib/assemblage.lua b/lib/assemblage.lua new file mode 100644 index 0000000..67a1428 --- /dev/null +++ b/lib/assemblage.lua @@ -0,0 +1,26 @@ +--- Assemblage + +local Assemblage = {} +Assemblage.__index = Assemblage + +function Assemblage.new(assemble) + local assemblage = setmetatable({ + __assemble = assemble, + + __isAssemblage = true, + }, Assemblage) + + Assemblage.__mt = {__index = assemblage} + + return assemblage +end + +function Assemblage:assemble(e, ...) + self.__assemble(e, ...) + + return self +end + +return setmetatable(Assemblage, { + __call = function(_, ...) return Assemblage.new(...) end, +}) diff --git a/lib/entity.lua b/lib/entity.lua index bfb9a07..441b9df 100644 --- a/lib/entity.lua +++ b/lib/entity.lua @@ -12,9 +12,8 @@ Entity.__index = Entity -- @return A new Entity function Entity.new() local e = setmetatable({ - components = {}, - removed = {}, - instances = List(), + removed = {}, + instances = List(), __isEntity = true, }, Entity) @@ -24,8 +23,9 @@ end local function give(e, component, ...) local comp = component:__initialize(...) - e.components[component] = comp e[component] = comp + + e:mark() end --- Gives an Entity a component with values. @@ -68,22 +68,36 @@ function Entity:remove(component) error("bad argument #1 to 'Entity:remove' (Component expected, got "..type(component)..")") end - self.removed[component] = true + self.removed[#self.removed + 1] = component + + self:mark() return self end ---- Checks the Entity against the pools again. --- @return self -function Entity:apply() - for i = 1, self.instances.size do - self.instances:get(i):checkEntity(self) +function Entity:assemble(assemblage, ...) + if not Type.isAssemblage(assemblage) then + error("bad argument #1 to 'Entity:assemble' (Assemblage expected, got "..type(assemblage)..")") end - for component, _ in pairs(self.removed) do - self.components[component] = nil + assemblage:assemble(self, ...) + + return self +end + +function Entity:mark() + for i = 1, self.instances.size do + self.instances: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[component] = nil + + self.removed[i] = nil end return self @@ -107,7 +121,7 @@ function Entity:get(component) error("bad argument #1 to 'Entity:get' (Component expected, got "..type(component)..")") end - return self.components[component] + return self[component] end --- Returns true if the Entity has the Component. @@ -118,7 +132,7 @@ function Entity:has(component) error("bad argument #1 to 'Entity:has' (Component expected, got "..type(component)..")") end - return self.components[component] ~= nil + return self[component] ~= nil end return setmetatable(Entity, { diff --git a/lib/init.lua b/lib/init.lua index 76f2588..81887fb 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -22,7 +22,7 @@ local Concord = { The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. @@ -39,10 +39,11 @@ local Concord = { -- } -- @return Concord function Concord.init(settings) - Concord.entity = require(PATH..".entity") - Concord.component = require(PATH..".component") - Concord.system = require(PATH..".system") - Concord.instance = require(PATH..".instance") + Concord.entity = require(PATH..".entity") + Concord.component = require(PATH..".component") + Concord.system = require(PATH..".system") + Concord.instance = require(PATH..".instance") + Concord.assemblage = require(PATH..".assemblage") if settings and settings.useEvents then Concord.instances = {} diff --git a/lib/instance.lua b/lib/instance.lua index f4d63a0..fa59c35 100644 --- a/lib/instance.lua +++ b/lib/instance.lua @@ -2,8 +2,6 @@ local PATH = (...):gsub('%.[^%.]+$', '') -local Entity = require(PATH..".entity") -local System = require(PATH..".system") local Type = require(PATH..".type") local List = require(PATH..".list") @@ -17,8 +15,9 @@ function Instance.new() entities = List(), systems = List(), events = {}, + + marked = {}, removed = {}, - toRemove = nil, __isInstance = true, }, Instance) @@ -43,6 +42,29 @@ function Instance:addEntity(e) return self end +--- Marks an Entity as removed from the Instance. +-- @param e The Entity to mark +-- @return self +function Instance:removeEntity(e) + if not Type.isEntity(e) then + error("bad argument #1 to 'Instance:removeEntity' (Entity expected, got "..type(e)..")", 2) + end + + self.removed[#self.removed + 1] = e + + return self +end + +function Instance:markEntity(e) + if not Type.isEntity(e) then + error("bad argument #1 to 'Instance: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 Instance. -- @param e The Entity to check -- @return self @@ -58,34 +80,33 @@ function Instance:checkEntity(e) return self end ---- Marks an Entity as removed from the Instance. --- @param e The Entity to mark --- @return self -function Instance:removeEntity(e) - if not Type.isEntity(e) then - error("bad argument #1 to 'Instance:removeEntity' (Entity expected, got "..type(e)..")", 2) - end - - self.removed[#self.removed + 1] = e - - return self -end - --- Completely removes all marked Entities in the Instance. -- @return self function Instance:flush() - while #self.removed > 0 do - self.toRemove = self.removed + while #self.marked > 0 do + local marked = self.removed self.removed = {} - for i = 1, #self.toRemove do - local e = self.toRemove[i] + for i = 1, #marked do + local e = marked[i] + + e.instances:apply() + e.instances:checkEntity(e) + end + end + + while #self.removed > 0 do + local removed = self.removed + self.removed = {} + + for i = 1, #removed do + local e = removed[i] e.instances:remove(self) self.entities:remove(e) - for i = 1, self.systems.size do - self.systems:get(i):__remove(e) + for j = 1, self.systems.size do + self.systems:get(j):__remove(e) end self:onEntityRemoved(e) @@ -93,7 +114,9 @@ function Instance:flush() end for i = 1, self.systems.size do - self.systems:get(i):flush() + local system = self.systems:get(i) + system:flush() + system:clear() end return self @@ -250,12 +273,12 @@ end --- Default callback for adding an Entity. -- @param e The Entity that was added -function Instance:onEntityAdded(e) +function Instance:onEntityAdded(e) -- luacheck: ignore end --- Default callback for removing an Entity. -- @param e The Entity that was removed -function Instance:onEntityRemoved(e) +function Instance:onEntityRemoved(e) -- luacheck: ignore end return setmetatable(Instance, { diff --git a/lib/pool.lua b/lib/pool.lua index 5a7f44b..e7e949c 100644 --- a/lib/pool.lua +++ b/lib/pool.lua @@ -36,7 +36,7 @@ end -- @return True if the entity is eligible, false otherwise function Pool:eligible(e) for _, component in ipairs(self.filter) do - if not e.components[component] or e.removed[component] then + if not e[component] or e.removed[component] then return false end end diff --git a/lib/system.lua b/lib/system.lua index 7c7b22a..a42f79b 100644 --- a/lib/system.lua +++ b/lib/system.lua @@ -2,8 +2,7 @@ local PATH = (...):gsub('%.[^%.]+$', '') -local Component = require(PATH..".component") -local Pool = require(PATH..".pool") +local Pool = require(PATH..".pool") local System = {} System.mt = { @@ -47,11 +46,11 @@ end --- Builds a Pool for the System. -- @param baseFilter The 'raw' Filter -- @return A new Pool -function System:__buildPool(baseFilter) +function System:__buildPool(baseFilter) -- luacheck: ignore local name = "pool" local filter = {} - for i, v in ipairs(baseFilter) do + for _, v in ipairs(baseFilter) do if type(v) == "table" then filter[#filter + 1] = v elseif type(v) == "string" then @@ -66,8 +65,6 @@ end -- @param e The Entity to check -- @return True if the Entity was added, false if it was removed. Nil if nothing happend function System:__check(e) - local systemHas = self.__all[e] - for _, pool in ipairs(self.__pools) do local poolHas = pool:has(e) local eligible = pool:eligible(e) @@ -76,13 +73,11 @@ function System:__check(e) pool:add(e) pool.added[#pool.added + 1] = e - self:entityAddedTo(e, pool) self:__tryAdd(e) elseif poolHas and not eligible then pool:remove(e) pool.removed[#pool.removed + 1] = e - self:entityRemovedFrom(e, pool) self:__tryRemove(e) end end @@ -96,12 +91,10 @@ function System:__remove(e) if pool:has(e) then pool:remove(e) pool.removed[#pool.removed + 1] = e - self:entityRemovedFrom(e, pool) end end self.__all[e] = nil - self:entityRemoved(e) end end @@ -110,7 +103,6 @@ end function System:__tryAdd(e) if not self.__all[e] then self.__all[e] = 0 - self:entityAdded(e) end self.__all[e] = self.__all[e] + 1 @@ -124,13 +116,11 @@ function System:__tryRemove(e) if self.__all[e] == 0 then self.__all[e] = nil - self:entityRemoved(e) end end end -function System:flush() - self:clear() +function System:flush() -- luacheck: ignore end function System:clear() @@ -147,44 +137,22 @@ end --- Default callback for system initialization. -- @param ... Varags -function System:init(...) -end - ---- Default callback for adding an Entity. --- @param e The Entity that was added -function System:entityAdded(e) -end - ---- Default callback for adding an Entity to a pool. --- @param e The Entity that was added --- @param pool The pool the Entity was added to -function System:entityAddedTo(e, pool) -end - ---- Default callback for removing an Entity. --- @param e The Entity that was removed -function System:entityRemoved(e) -end - ---- Default callback for removing an Entity from a pool. --- @param e The Entity that was removed --- @param pool The pool the Entity was removed from -function System:entityRemovedFrom(e, pool) +function System:init(...) -- luacheck: ignore end -- Default callback for when the System is added to an Instance. -- @param instance The Instance the System was added to -function System:addedTo(instance) +function System:addedTo(instance) -- luacheck: ignore end -- Default callback for when a System's callback is enabled. -- @param callbackName The name of the callback that was enabled -function System:enabledCallback(callbackName) +function System:enabledCallback(callbackName) -- luacheck: ignore end -- Default callback for when a System's callback is disabled. -- @param callbackName The name of the callback that was disabled -function System:disabledCallback(callbackName) +function System:disabledCallback(callbackName) -- luacheck: ignore end return setmetatable(System, { diff --git a/lib/type.lua b/lib/type.lua index 204aa79..80d14b0 100644 --- a/lib/type.lua +++ b/lib/type.lua @@ -16,4 +16,8 @@ function Type.isInstance(t) return type(t) == "table" and t.__isInstance end -return Type \ No newline at end of file +function Type.isAssemblage(t) + return type(t) == "table" and t.__isAssemblage +end + +return Type diff --git a/main.lua b/main.lua index c809a1f..ce78707 100644 --- a/main.lua +++ b/main.lua @@ -1,4 +1,4 @@ local file = "examples.simpleDrawing" -- local file = "examples.baseLayout.main" -require(file) \ No newline at end of file +require(file)