Added assemblages

This commit is contained in:
Justin van der Leij 2018-11-11 19:42:44 +01:00
parent 11255fd722
commit f7a394f057
12 changed files with 204 additions and 93 deletions

1
.luacheckrc Normal file
View file

@ -0,0 +1 @@
std="love+luajit"

11
TODO
View file

@ -3,6 +3,13 @@
[x] Add Entity:ensure (maybe?) [x] Add Entity:ensure (maybe?)
[ ] Put pools in the Instance and invert dependency. [ ] Put pools in the Instance and invert dependency.
[ ] Share pools between systems [ ] Share pools between systems
[ ] Remove System callbacks [x] Remove System callbacks
[x] Put .added and .removed in pools so they can be iterated over [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

View file

@ -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))

View file

@ -78,7 +78,7 @@ end
function RandomRemover:update(dt) function RandomRemover:update(dt)
self.time = self.time + dt self.time = self.time + dt
if self.time >= 0.5 then if self.time >= 0.05 then
self.time = 0 self.time = 0
if self.pool.size > 0 then if self.pool.size > 0 then
@ -95,7 +95,7 @@ Game:addSystem(RandomRemover(), "update")
Game:addSystem(RectangleRenderer(), "draw") Game:addSystem(RectangleRenderer(), "draw")
Game:addSystem(CircleRenderer(), "draw") Game:addSystem(CircleRenderer(), "draw")
for i = 1, 100 do for _ = 1, 100 do
local e = Entity() local e = Entity()
e:give(Position, love.math.random(0, 700), love.math.random(0, 700)) 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)) 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) Game:addEntity(e)
end end
for i = 1, 100 do for _ = 1, 100 do
local e = Entity() local e = Entity()
e:give(Position, love.math.random(0, 700), love.math.random(0, 700)) e:give(Position, love.math.random(0, 700), love.math.random(0, 700))
e:give(Circle, love.math.random(5, 20)) e:give(Circle, love.math.random(5, 20))

26
lib/assemblage.lua Normal file
View file

@ -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,
})

View file

@ -12,9 +12,8 @@ Entity.__index = Entity
-- @return A new Entity -- @return A new Entity
function Entity.new() function Entity.new()
local e = setmetatable({ local e = setmetatable({
components = {}, removed = {},
removed = {}, instances = List(),
instances = List(),
__isEntity = true, __isEntity = true,
}, Entity) }, Entity)
@ -24,8 +23,9 @@ end
local function give(e, component, ...) local function give(e, component, ...)
local comp = component:__initialize(...) local comp = component:__initialize(...)
e.components[component] = comp
e[component] = comp e[component] = comp
e:mark()
end end
--- Gives an Entity a component with values. --- 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)..")") error("bad argument #1 to 'Entity:remove' (Component expected, got "..type(component)..")")
end end
self.removed[component] = true self.removed[#self.removed + 1] = component
self:mark()
return self return self
end end
--- Checks the Entity against the pools again. function Entity:assemble(assemblage, ...)
-- @return self if not Type.isAssemblage(assemblage) then
function Entity:apply() error("bad argument #1 to 'Entity:assemble' (Assemblage expected, got "..type(assemblage)..")")
for i = 1, self.instances.size do
self.instances:get(i):checkEntity(self)
end end
for component, _ in pairs(self.removed) do assemblage:assemble(self, ...)
self.components[component] = nil
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[component] = nil
self.removed[component] = nil
self.removed[i] = nil
end end
return self return self
@ -107,7 +121,7 @@ function Entity:get(component)
error("bad argument #1 to 'Entity:get' (Component expected, got "..type(component)..")") error("bad argument #1 to 'Entity:get' (Component expected, got "..type(component)..")")
end end
return self.components[component] return self[component]
end end
--- Returns true if the Entity has the Component. --- 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)..")") error("bad argument #1 to 'Entity:has' (Component expected, got "..type(component)..")")
end end
return self.components[component] ~= nil return self[component] ~= nil
end end
return setmetatable(Entity, { return setmetatable(Entity, {

View file

@ -22,7 +22,7 @@ local Concord = {
The above copyright notice and this permission notice shall be included The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software. in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
@ -39,10 +39,11 @@ local Concord = {
-- } -- }
-- @return Concord -- @return Concord
function Concord.init(settings) function Concord.init(settings)
Concord.entity = require(PATH..".entity") Concord.entity = require(PATH..".entity")
Concord.component = require(PATH..".component") Concord.component = require(PATH..".component")
Concord.system = require(PATH..".system") Concord.system = require(PATH..".system")
Concord.instance = require(PATH..".instance") Concord.instance = require(PATH..".instance")
Concord.assemblage = require(PATH..".assemblage")
if settings and settings.useEvents then if settings and settings.useEvents then
Concord.instances = {} Concord.instances = {}

View file

@ -2,8 +2,6 @@
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
local Entity = require(PATH..".entity")
local System = require(PATH..".system")
local Type = require(PATH..".type") local Type = require(PATH..".type")
local List = require(PATH..".list") local List = require(PATH..".list")
@ -17,8 +15,9 @@ function Instance.new()
entities = List(), entities = List(),
systems = List(), systems = List(),
events = {}, events = {},
marked = {},
removed = {}, removed = {},
toRemove = nil,
__isInstance = true, __isInstance = true,
}, Instance) }, Instance)
@ -43,6 +42,29 @@ function Instance:addEntity(e)
return self return self
end 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. --- Checks an Entity against all the systems in the Instance.
-- @param e The Entity to check -- @param e The Entity to check
-- @return self -- @return self
@ -58,34 +80,33 @@ function Instance:checkEntity(e)
return self return self
end 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. --- Completely removes all marked Entities in the Instance.
-- @return self -- @return self
function Instance:flush() function Instance:flush()
while #self.removed > 0 do while #self.marked > 0 do
self.toRemove = self.removed local marked = self.removed
self.removed = {} self.removed = {}
for i = 1, #self.toRemove do for i = 1, #marked do
local e = self.toRemove[i] 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) e.instances:remove(self)
self.entities:remove(e) self.entities:remove(e)
for i = 1, self.systems.size do for j = 1, self.systems.size do
self.systems:get(i):__remove(e) self.systems:get(j):__remove(e)
end end
self:onEntityRemoved(e) self:onEntityRemoved(e)
@ -93,7 +114,9 @@ function Instance:flush()
end end
for i = 1, self.systems.size do for i = 1, self.systems.size do
self.systems:get(i):flush() local system = self.systems:get(i)
system:flush()
system:clear()
end end
return self return self
@ -250,12 +273,12 @@ end
--- Default callback for adding an Entity. --- Default callback for adding an Entity.
-- @param e The Entity that was added -- @param e The Entity that was added
function Instance:onEntityAdded(e) function Instance:onEntityAdded(e) -- luacheck: ignore
end end
--- Default callback for removing an Entity. --- Default callback for removing an Entity.
-- @param e The Entity that was removed -- @param e The Entity that was removed
function Instance:onEntityRemoved(e) function Instance:onEntityRemoved(e) -- luacheck: ignore
end end
return setmetatable(Instance, { return setmetatable(Instance, {

View file

@ -36,7 +36,7 @@ end
-- @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.components[component] or e.removed[component] then if not e[component] or e.removed[component] then
return false return false
end end
end end

View file

@ -2,8 +2,7 @@
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
local Component = require(PATH..".component") local Pool = require(PATH..".pool")
local Pool = require(PATH..".pool")
local System = {} local System = {}
System.mt = { System.mt = {
@ -47,11 +46,11 @@ end
--- Builds a Pool for the System. --- Builds a Pool for the System.
-- @param baseFilter The 'raw' Filter -- @param baseFilter The 'raw' Filter
-- @return A new Pool -- @return A new Pool
function System:__buildPool(baseFilter) function System:__buildPool(baseFilter) -- luacheck: ignore
local name = "pool" local name = "pool"
local filter = {} local filter = {}
for i, v in ipairs(baseFilter) do for _, v in ipairs(baseFilter) do
if type(v) == "table" then if type(v) == "table" then
filter[#filter + 1] = v filter[#filter + 1] = v
elseif type(v) == "string" then elseif type(v) == "string" then
@ -66,8 +65,6 @@ end
-- @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 -- @return True if the Entity was added, false if it was removed. Nil if nothing happend
function System:__check(e) function System:__check(e)
local systemHas = self.__all[e]
for _, pool in ipairs(self.__pools) do for _, pool in ipairs(self.__pools) do
local poolHas = pool:has(e) local poolHas = pool:has(e)
local eligible = pool:eligible(e) local eligible = pool:eligible(e)
@ -76,13 +73,11 @@ function System:__check(e)
pool:add(e) pool:add(e)
pool.added[#pool.added + 1] = e pool.added[#pool.added + 1] = e
self:entityAddedTo(e, pool)
self:__tryAdd(e) self:__tryAdd(e)
elseif poolHas and not eligible then elseif poolHas and not eligible then
pool:remove(e) pool:remove(e)
pool.removed[#pool.removed + 1] = e pool.removed[#pool.removed + 1] = e
self:entityRemovedFrom(e, pool)
self:__tryRemove(e) self:__tryRemove(e)
end end
end end
@ -96,12 +91,10 @@ function System:__remove(e)
if pool:has(e) then if pool:has(e) then
pool:remove(e) pool:remove(e)
pool.removed[#pool.removed + 1] = e pool.removed[#pool.removed + 1] = e
self:entityRemovedFrom(e, pool)
end end
end end
self.__all[e] = nil self.__all[e] = nil
self:entityRemoved(e)
end end
end end
@ -110,7 +103,6 @@ end
function System:__tryAdd(e) function System:__tryAdd(e)
if not self.__all[e] then if not self.__all[e] then
self.__all[e] = 0 self.__all[e] = 0
self:entityAdded(e)
end end
self.__all[e] = self.__all[e] + 1 self.__all[e] = self.__all[e] + 1
@ -124,13 +116,11 @@ function System:__tryRemove(e)
if self.__all[e] == 0 then if self.__all[e] == 0 then
self.__all[e] = nil self.__all[e] = nil
self:entityRemoved(e)
end end
end end
end end
function System:flush() function System:flush() -- luacheck: ignore
self:clear()
end end
function System:clear() function System:clear()
@ -147,44 +137,22 @@ end
--- Default callback for system initialization. --- Default callback for system initialization.
-- @param ... Varags -- @param ... Varags
function System:init(...) function System:init(...) -- luacheck: ignore
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)
end end
-- Default callback for when the System is added to an Instance. -- Default callback for when the System is added to an Instance.
-- @param instance The Instance the System was added to -- @param instance The Instance the System was added to
function System:addedTo(instance) function System:addedTo(instance) -- luacheck: ignore
end end
-- Default callback for when a System's callback is enabled. -- Default callback for when a System's callback is enabled.
-- @param callbackName The name of the callback that was enabled -- @param callbackName The name of the callback that was enabled
function System:enabledCallback(callbackName) function System:enabledCallback(callbackName) -- luacheck: ignore
end end
-- Default callback for when a System's callback is disabled. -- Default callback for when a System's callback is disabled.
-- @param callbackName The name of the callback that was disabled -- @param callbackName The name of the callback that was disabled
function System:disabledCallback(callbackName) function System:disabledCallback(callbackName) -- luacheck: ignore
end end
return setmetatable(System, { return setmetatable(System, {

View file

@ -16,4 +16,8 @@ function Type.isInstance(t)
return type(t) == "table" and t.__isInstance return type(t) == "table" and t.__isInstance
end end
return Type function Type.isAssemblage(t)
return type(t) == "table" and t.__isAssemblage
end
return Type

View file

@ -1,4 +1,4 @@
local file = "examples.simpleDrawing" local file = "examples.simpleDrawing"
-- local file = "examples.baseLayout.main" -- local file = "examples.baseLayout.main"
require(file) require(file)