mirror of
https://github.com/Keyslam-Group/Concord.git
synced 2025-08-30 17:08:29 -04:00
CONCORD IS DEAD
Long live Concord!!
This commit is contained in:
parent
5dffe04b72
commit
c640641b09
16 changed files with 263 additions and 424 deletions
|
@ -1,49 +0,0 @@
|
|||
--- Gives an entity a set of components.
|
||||
-- @classmod Assemblage
|
||||
|
||||
local Assemblage = {}
|
||||
Assemblage.__mt = {
|
||||
__index = Assemblage,
|
||||
}
|
||||
|
||||
--- Creates a new Assemblage.
|
||||
-- @tparam function assemble Function that assembles an Entity
|
||||
-- @treturn Assemblage A new assemblage
|
||||
function Assemblage.new(assemble)
|
||||
local assemblage = setmetatable({
|
||||
__assemble = assemble,
|
||||
|
||||
__name = nil,
|
||||
__isAssemblage = true,
|
||||
}, Assemblage.__mt)
|
||||
|
||||
return assemblage
|
||||
end
|
||||
|
||||
--- Assembles an Entity.
|
||||
-- @tparam Entity e Entity to assemble
|
||||
-- @param ... additional arguments to pass to the assemble function
|
||||
-- @treturn Assemblage self
|
||||
function Assemblage:assemble(e, ...)
|
||||
self.__assemble(e, ...)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns true if the Assemblage has a name.
|
||||
-- @treturn boolean
|
||||
function Assemblage:hasName()
|
||||
return self.__name and true or false
|
||||
end
|
||||
|
||||
--- Returns the name of the Assemblage.
|
||||
-- @treturn string
|
||||
function Assemblage:getName()
|
||||
return self.__name
|
||||
end
|
||||
|
||||
return setmetatable(Assemblage, {
|
||||
__call = function(_, ...)
|
||||
return Assemblage.new(...)
|
||||
end,
|
||||
})
|
|
@ -1,49 +0,0 @@
|
|||
--- A container for registered @{Assemblage}s
|
||||
-- @module Assemblages
|
||||
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
local Type = require(PATH..".type")
|
||||
|
||||
local Assemblages = {}
|
||||
|
||||
--- Registers an Assemblage.
|
||||
-- @string name Name to register under
|
||||
-- @tparam Assemblage assemblage Assemblage to register
|
||||
function Assemblages.register(name, assemblage)
|
||||
if (type(name) ~= "string") then
|
||||
error("bad argument #1 to 'Assemblages.register' (string expected, got "..type(name)..")", 3)
|
||||
end
|
||||
|
||||
if (not Type.isAssemblage(assemblage)) then
|
||||
error("bad argument #2 to 'Assemblages.register' (assemblage expected, got "..type(assemblage)..")", 3)
|
||||
end
|
||||
|
||||
if (rawget(Assemblages, name)) then
|
||||
error("bad argument #2 to 'Assemblages.register' (Assemblage with name '"..name.."' was already registerd)", 3)
|
||||
end
|
||||
|
||||
Assemblages[name] = assemblage
|
||||
assemblage.__name = name
|
||||
end
|
||||
|
||||
--- Returns true if the containter has an Assemblage with the specified name
|
||||
-- @string name Name of the Assemblage to check
|
||||
-- @treturn boolean
|
||||
function Assemblages.has(name)
|
||||
return Assemblages[name] and true or false
|
||||
end
|
||||
|
||||
--- Returns the Assemblage with the specified name
|
||||
-- @string name Name of the Assemblage to get
|
||||
-- @treturn Assemblage
|
||||
function Assemblages.get(name)
|
||||
return Assemblages[name]
|
||||
end
|
||||
|
||||
|
||||
return setmetatable(Assemblages, {
|
||||
__index = function(_, name)
|
||||
error("Attempt to index assemblage '"..tostring(name).."' that does not exist / was not registered", 2)
|
||||
end
|
||||
})
|
|
@ -1,6 +1,10 @@
|
|||
--- A pure data container that is contained by a single entity.
|
||||
-- @classmod Component
|
||||
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
local Components = require(PATH..".components")
|
||||
|
||||
local Component = {}
|
||||
Component.__mt = {
|
||||
__index = Component,
|
||||
|
@ -9,7 +13,15 @@ Component.__mt = {
|
|||
--- Creates a new ComponentClass.
|
||||
-- @tparam function populate Function that populates a Component with values
|
||||
-- @treturn Component A new ComponentClass
|
||||
function Component.new(populate)
|
||||
function Component.new(name, populate)
|
||||
if (type(name) ~= "string") then
|
||||
error("bad argument #1 to 'Component.new' (string expected, got "..type(name)..")", 2)
|
||||
end
|
||||
|
||||
if (rawget(Components, name)) then
|
||||
error("bad argument #1 to 'Component.new' (ComponentClass with name '"..name.."' was already registerd)", 2) -- luacheck: ignore
|
||||
end
|
||||
|
||||
if (type(populate) ~= "function" and type(populate) ~= "nil") then
|
||||
error("bad argument #1 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2)
|
||||
end
|
||||
|
@ -17,7 +29,7 @@ function Component.new(populate)
|
|||
local componentClass = setmetatable({
|
||||
__populate = populate,
|
||||
|
||||
__name = nil,
|
||||
__name = name,
|
||||
__isComponentClass = true,
|
||||
}, Component.__mt)
|
||||
|
||||
|
@ -25,6 +37,8 @@ function Component.new(populate)
|
|||
__index = componentClass
|
||||
}
|
||||
|
||||
Components[name] = componentClass
|
||||
|
||||
return componentClass
|
||||
end
|
||||
|
||||
|
|
|
@ -7,42 +7,51 @@ local Type = require(PATH..".type")
|
|||
|
||||
local Components = {}
|
||||
|
||||
--- Registers a ComponentClass.
|
||||
-- @string name Name to register under
|
||||
-- @tparam Component componentClass ComponentClass to register
|
||||
function Components.register(name, componentClass)
|
||||
if (type(name) ~= "string") then
|
||||
error("bad argument #1 to 'Components.register' (string expected, got "..type(name)..")", 3)
|
||||
local try = function (name)
|
||||
if type(name) ~= "string" then
|
||||
return false, "ComponentsClass name is expected to be a string, got "..type(name)..")"
|
||||
end
|
||||
|
||||
if (not Type.isComponentClass(componentClass)) then
|
||||
error("bad argument #2 to 'Components.register' (ComponentClass expected, got "..type(componentClass)..")", 3)
|
||||
local value = rawget(Components, name)
|
||||
if not value then
|
||||
return false, "ComponentClass '"..name.."' does not exist / was not registered"
|
||||
end
|
||||
|
||||
if (rawget(Components, name)) then
|
||||
error("bad argument #2 to 'Components.register' (ComponentClass with name '"..name.."' was already registerd)", 3) -- luacheck: ignore
|
||||
end
|
||||
|
||||
Components[name] = componentClass
|
||||
componentClass.__name = name
|
||||
return true, value
|
||||
end
|
||||
|
||||
--- Returns true if the containter has the ComponentClass with the specified name
|
||||
-- @string name Name of the ComponentClass to check
|
||||
-- @treturn boolean
|
||||
function Components.has(name)
|
||||
return Components[name] and true or false
|
||||
return rawget(Components, name) and true or false
|
||||
end
|
||||
|
||||
--- Returns true and the ComponentClass if one was registered with the specified name
|
||||
-- or false and an error otherwise
|
||||
-- @string name Name of the ComponentClass to check
|
||||
-- @treturn boolean
|
||||
-- @treturn Component or error string
|
||||
function Components.try(name)
|
||||
return try(name)
|
||||
end
|
||||
|
||||
--- Returns the ComponentClass with the specified name
|
||||
-- @string name Name of the ComponentClass to get
|
||||
-- @treturn Component
|
||||
function Components.get(name)
|
||||
return Components[name]
|
||||
local ok, value = try(name)
|
||||
|
||||
if not ok then error(value, 2) end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
return setmetatable(Components, {
|
||||
__index = function(_, name)
|
||||
error("Attempt to index ComponentClass '"..tostring(name).."' that does not exist / was not registered", 2)
|
||||
end
|
||||
local ok, value = try(name)
|
||||
|
||||
if not ok then error(value, 2) end
|
||||
|
||||
return value end
|
||||
})
|
||||
|
|
|
@ -34,18 +34,18 @@ function Entity.new(world)
|
|||
return e
|
||||
end
|
||||
|
||||
local function give(e, componentClass, ...)
|
||||
local function give(e, name, componentClass, ...)
|
||||
local component = componentClass:__initialize(...)
|
||||
|
||||
e[componentClass] = component
|
||||
e.__components[componentClass] = component
|
||||
e[name] = component
|
||||
e.__components[name] = component
|
||||
|
||||
e:__dirty()
|
||||
end
|
||||
|
||||
local function remove(e, componentClass)
|
||||
e[componentClass] = nil
|
||||
e.__components[componentClass] = nil
|
||||
local function remove(e, name, componentClass)
|
||||
e[name] = nil
|
||||
e.__components[name] = nil
|
||||
|
||||
e:__dirty()
|
||||
end
|
||||
|
@ -55,12 +55,14 @@ end
|
|||
-- @tparam Component componentClass ComponentClass to add an instance of
|
||||
-- @param ... additional arguments to pass to the Component's populate function
|
||||
-- @treturn Entity self
|
||||
function Entity:give(componentClass, ...)
|
||||
if not Type.isComponentClass(componentClass) then
|
||||
error("bad argument #1 to 'Entity:give' (ComponentClass expected, got "..type(componentClass)..")", 2)
|
||||
function Entity:give(name, ...)
|
||||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
give(self, componentClass, ...)
|
||||
give(self, name, componentClass, ...)
|
||||
|
||||
return self
|
||||
end
|
||||
|
@ -70,16 +72,18 @@ end
|
|||
-- @tparam Component componentClass ComponentClass to add an instance of
|
||||
-- @param ... additional arguments to pass to the Component's populate function
|
||||
-- @treturn Entity self
|
||||
function Entity:ensure(componentClass, ...)
|
||||
if not Type.isComponentClass(componentClass) then
|
||||
error("bad argument #1 to 'Entity:ensure' (ComponentClass expected, got "..type(componentClass)..")", 2)
|
||||
function Entity:ensure(name, ...)
|
||||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
if self[componentClass] then
|
||||
if self[name] then
|
||||
return self
|
||||
end
|
||||
|
||||
give(self, componentClass, ...)
|
||||
give(self, name, componentClass, ...)
|
||||
|
||||
return self
|
||||
end
|
||||
|
@ -87,26 +91,28 @@ end
|
|||
--- Removes a Component from an Entity.
|
||||
-- @tparam Component componentClass ComponentClass of the Component to remove
|
||||
-- @treturn Entity self
|
||||
function Entity:remove(componentClass)
|
||||
if not Type.isComponentClass(componentClass) then
|
||||
error("bad argument #1 to 'Entity:remove' (ComponentClass expected, got "..type(componentClass)..")")
|
||||
function Entity:remove(name)
|
||||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
remove(self, componentClass)
|
||||
remove(self, name, componentClass)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Assembles an Entity.
|
||||
-- @tparam Assemblage assemblage Assemblage to assemble with
|
||||
-- @param ... additional arguments to pass to the Assemblage's assemble function.
|
||||
-- @tparam function assemblage Function that will assemble an entity
|
||||
-- @param ... additional arguments to pass to the assemblage function.
|
||||
-- @treturn Entity self
|
||||
function Entity:assemble(assemblage, ...)
|
||||
if not Type.isAssemblage(assemblage) then
|
||||
error("bad argument #1 to 'Entity:assemble' (Assemblage expected, got "..type(assemblage)..")")
|
||||
if type(assemblage) ~= "function" then
|
||||
error("bad argument #1 to 'Entity:assemble' (function expected, got "..type(assemblage)..")")
|
||||
end
|
||||
|
||||
assemblage:assemble(self, ...)
|
||||
assemblage(self, ...)
|
||||
|
||||
return self
|
||||
end
|
||||
|
@ -135,23 +141,27 @@ end
|
|||
--- Returns true if the Entity has a Component.
|
||||
-- @tparam Component componentClass ComponentClass of the Component to check
|
||||
-- @treturn boolean
|
||||
function Entity:has(componentClass)
|
||||
if not Type.isComponentClass(componentClass) then
|
||||
error("bad argument #1 to 'Entity:has' (ComponentClass expected, got "..type(componentClass)..")")
|
||||
function Entity:has(name)
|
||||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
error("bad argument #1 to 'Entity:has' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
return self[componentClass] ~= nil
|
||||
return self[name] and true or false
|
||||
end
|
||||
|
||||
--- Gets a Component from the Entity.
|
||||
-- @tparam Component componentClass ComponentClass of the Component to get
|
||||
-- @treturn table
|
||||
function Entity:get(componentClass)
|
||||
if not Type.isComponentClass(componentClass) then
|
||||
error("bad argument #1 to 'Entity:get' (ComponentClass expected, got "..type(componentClass)..")")
|
||||
function Entity:get(name)
|
||||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
return self[componentClass]
|
||||
return self[name]
|
||||
end
|
||||
|
||||
--- Returns a table of all Components the Entity has.
|
||||
|
@ -193,7 +203,7 @@ function Entity:deserialize(data)
|
|||
for i = 1, #data do
|
||||
local componentData = data[i]
|
||||
|
||||
if (not Components[componentData.__name]) then
|
||||
if (not Components.has(componentData.__name)) then
|
||||
error("bad argument #1 to 'Entity:deserialize' (ComponentClass "..type(componentData.__name).." wasn't yet loaded)") -- luacheck: ignore
|
||||
end
|
||||
|
||||
|
@ -202,8 +212,8 @@ function Entity:deserialize(data)
|
|||
local component = componentClass:__new()
|
||||
component:deserialize(componentData)
|
||||
|
||||
self[componentClass] = component
|
||||
self.__components[componentClass] = component
|
||||
self[componentData.__name] = component
|
||||
self.__components[componentData.__name] = component
|
||||
|
||||
self:__dirty()
|
||||
end
|
||||
|
|
|
@ -38,13 +38,8 @@ Concord.component = require(PATH..".component")
|
|||
Concord.components = require(PATH..".components")
|
||||
|
||||
Concord.system = require(PATH..".system")
|
||||
Concord.systems = require(PATH..".systems")
|
||||
|
||||
Concord.world = require(PATH..".world")
|
||||
Concord.worlds = require(PATH..".worlds")
|
||||
|
||||
Concord.assemblage = require(PATH..".assemblage")
|
||||
Concord.assemblages = require(PATH..".assemblages")
|
||||
|
||||
local function load(pathOrFiles, namespace)
|
||||
if (type(pathOrFiles) ~= "string" and type(pathOrFiles) ~= "table") then
|
||||
|
@ -63,7 +58,8 @@ local function load(pathOrFiles, namespace)
|
|||
local name = file:sub(1, #file - 4)
|
||||
local path = pathOrFiles.."."..name
|
||||
|
||||
namespace.register(name, require(path))
|
||||
local value = require(path)
|
||||
if namespace then namespace[name] = value end
|
||||
end
|
||||
elseif (type(pathOrFiles == "table")) then
|
||||
for _, path in ipairs(pathOrFiles) do
|
||||
|
@ -78,37 +74,43 @@ local function load(pathOrFiles, namespace)
|
|||
name = path:sub((dotIndex or slashIndex) + 1)
|
||||
end
|
||||
|
||||
namespace.register(name, require(path))
|
||||
local value = require(path)
|
||||
if namespace then namespace[name] = value end
|
||||
end
|
||||
end
|
||||
|
||||
return namespace
|
||||
end
|
||||
|
||||
--- Loads ComponentClasses and puts them in the Components container.
|
||||
-- Accepts a table of paths to files: {"component_1", "component_2", "etc"}
|
||||
-- Accepts a path to a directory with ComponentClasses: "components"
|
||||
function Concord.loadComponents(pathOrFiles)
|
||||
load(pathOrFiles, Concord.components)
|
||||
load(pathOrFiles, nil)
|
||||
return Concord.components
|
||||
end
|
||||
|
||||
--- Loads SystemClasses and puts them in the Systems container.
|
||||
-- Accepts a table of paths to files: {"system_1", "system_2", "etc"}
|
||||
-- Accepts a path to a directory with SystemClasses: "systems"
|
||||
function Concord.loadSystems(pathOrFiles)
|
||||
load(pathOrFiles, Concord.systems)
|
||||
function Concord.loadSystems(pathOrFiles, world)
|
||||
local systems = load(pathOrFiles, {})
|
||||
|
||||
if world then
|
||||
for _, system in pairs(systems) do
|
||||
world:addSystem(system)
|
||||
end
|
||||
end
|
||||
|
||||
return systems
|
||||
end
|
||||
|
||||
--- Loads Worlds and puts them in the Worlds container.
|
||||
-- Accepts a table of paths to files: {"world_1", "world_2", "etc"}
|
||||
-- Accepts a path to a directory with Worlds: "worlds"
|
||||
function Concord.loadWorlds(pathOrFiles)
|
||||
load(pathOrFiles, Concord.worlds)
|
||||
return load(pathOrFiles, {})
|
||||
end
|
||||
|
||||
--- Loads Assemblages and puts them in the Assemblages container.
|
||||
-- Accepts a table of paths to files: {"assemblage_1", "assemblage_2", "etc"}
|
||||
-- Accepts a path to a directory with Assemblages: "assemblages"
|
||||
function Concord.loadAssemblages(pathOrFiles)
|
||||
load(pathOrFiles, Concord.assemblages)
|
||||
end
|
||||
|
||||
return Concord
|
||||
|
|
|
@ -19,7 +19,7 @@ end
|
|||
-- Object may not be the string 'size'
|
||||
-- @param obj Object to add
|
||||
-- @treturn List self
|
||||
function List:__add(obj)
|
||||
function List:add(obj)
|
||||
local size = self.size + 1
|
||||
|
||||
self[size] = obj
|
||||
|
@ -32,7 +32,7 @@ end
|
|||
--- Removes an object from the List.
|
||||
-- @param obj Object to remove
|
||||
-- @treturn List self
|
||||
function List:__remove(obj)
|
||||
function List:remove(obj)
|
||||
local index = self[obj]
|
||||
if not index then return end
|
||||
local size = self.size
|
||||
|
@ -56,7 +56,7 @@ end
|
|||
|
||||
--- Clears the List completely.
|
||||
-- @treturn List self
|
||||
function List:__clear()
|
||||
function List:clear()
|
||||
for i = 1, self.size do
|
||||
local o = self[i]
|
||||
|
||||
|
|
|
@ -29,32 +29,53 @@ end
|
|||
--- Checks if an Entity is eligible for the Pool.
|
||||
-- @tparam Entity e Entity to check
|
||||
-- @treturn boolean
|
||||
function Pool:__eligible(e)
|
||||
for _, component in ipairs(self.__filter) do
|
||||
if not e[component] then
|
||||
return false
|
||||
end
|
||||
function Pool:eligible(e)
|
||||
for i=#self.__filter, 1, -1 do
|
||||
local component = self.__filter[i].__name
|
||||
|
||||
if not e[component] then return false end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Internal: Adds an Entity to the Pool.
|
||||
-- Adds an Entity to the Pool, if it can be eligible.
|
||||
-- @param e Entity to add
|
||||
-- @treturn Pool self
|
||||
function Pool:__add(e)
|
||||
List.__add(self, e)
|
||||
-- @treturn boolean Whether the entity was added or not
|
||||
function Pool:add(e, bypass)
|
||||
if not bypass and not self:eligible(e) then
|
||||
return self, false
|
||||
end
|
||||
|
||||
List.add(self, e)
|
||||
self:onEntityAdded(e)
|
||||
|
||||
return self, true
|
||||
end
|
||||
|
||||
-- Remove an Entity from the Pool.
|
||||
-- @param e Entity to remove
|
||||
-- @treturn Pool self
|
||||
function Pool:remove(e)
|
||||
List.remove(self, e)
|
||||
self:onEntityRemoved(e)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Internal: Removed an Entity from the Pool.
|
||||
-- @param e Entity to remove
|
||||
--- Evaluate whether an Entity should be added or removed from the Pool.
|
||||
-- @param e Entity to add or remove
|
||||
-- @treturn Pool self
|
||||
function Pool:__remove(e)
|
||||
List.__remove(self, e)
|
||||
self:onEntityRemoved(e)
|
||||
function Pool:evaluate(e)
|
||||
local has = self:has(e)
|
||||
local eligible = self:eligible(e)
|
||||
|
||||
if not has and eligible then
|
||||
self:add(e)
|
||||
elseif has and not eligible then
|
||||
self:remove(e)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ local PATH = (...):gsub('%.[^%.]+$', '')
|
|||
|
||||
local Pool = require(PATH..".pool")
|
||||
local Utils = require(PATH..".utils")
|
||||
local Components = require(PATH..".components")
|
||||
|
||||
local System = {
|
||||
ENABLE_OPTIMIZATION = true,
|
||||
|
@ -25,21 +26,18 @@ System.mt = {
|
|||
__isSystemClass = false, -- Overwrite value from systemClass
|
||||
}, systemClass)
|
||||
|
||||
-- Optimization: We deep copy the World class into our instance of a world.
|
||||
-- Optimization: We deep copy the System class into our instance of a system.
|
||||
-- This grants slightly faster access times at the cost of memory.
|
||||
-- Since there (generally) won't be many instances of worlds this is a worthwhile tradeoff
|
||||
if (System.ENABLE_OPTIMIZATION) then
|
||||
Utils.shallowCopy(systemClass, system)
|
||||
end
|
||||
|
||||
for _, filter in pairs(systemClass.__filter) do
|
||||
local pool = system.__buildPool(filter)
|
||||
if not system[pool.__name] then
|
||||
system[pool.__name] = pool
|
||||
for name, filter in pairs(systemClass.__filter) do
|
||||
local pool = Pool(name, filter)
|
||||
|
||||
system[name] = pool
|
||||
system.__pools[#system.__pools + 1] = pool
|
||||
else
|
||||
error("Pool with name '"..pool.name.."' already exists.")
|
||||
end
|
||||
end
|
||||
|
||||
system:init(world)
|
||||
|
@ -48,12 +46,41 @@ System.mt = {
|
|||
end,
|
||||
}
|
||||
|
||||
local validateFilters = function (baseFilters)
|
||||
local filters = {}
|
||||
|
||||
for name, componentsList in pairs(baseFilters) do
|
||||
if type(name) ~= 'string' then
|
||||
error("Invalid name for filter (string key expected, got "..type(name)..")", 3)
|
||||
end
|
||||
|
||||
if type(componentsList) ~= 'table' then
|
||||
error("Invalid component list for filter '"..name.."' (table expected, got "..type(componentsList)..")", 3)
|
||||
end
|
||||
|
||||
local filter = {}
|
||||
for n, component in ipairs(componentsList) do
|
||||
local ok, componentClass = Components.try(component)
|
||||
|
||||
if not ok then
|
||||
error("Invalid component for filter '"..name.."' at position #"..n.." ("..componentClass..")", 3)
|
||||
end
|
||||
|
||||
filter[#filter + 1] = componentClass
|
||||
end
|
||||
|
||||
filters[name] = filter
|
||||
end
|
||||
|
||||
return filters
|
||||
end
|
||||
|
||||
--- Creates a new SystemClass.
|
||||
-- @param ... Variable amounts of filters
|
||||
-- @param table filters A table containing filters (name = {components...})
|
||||
-- @treturn System A new SystemClass
|
||||
function System.new(...)
|
||||
function System.new(filters)
|
||||
local systemClass = setmetatable({
|
||||
__filter = {...},
|
||||
__filter = validateFilters(filters),
|
||||
|
||||
__name = nil,
|
||||
__isSystemClass = true,
|
||||
|
@ -70,37 +97,12 @@ function System.new(...)
|
|||
return systemClass
|
||||
end
|
||||
|
||||
-- Internal: Builds a Pool for the System.
|
||||
-- @param baseFilter The 'raw' Filter
|
||||
-- @return A new Pool
|
||||
function System.__buildPool(baseFilter)
|
||||
local name = "pool"
|
||||
local filter = {}
|
||||
|
||||
for _, value in ipairs(baseFilter) do
|
||||
if type(value) == "table" then
|
||||
filter[#filter + 1] = value
|
||||
elseif type(value) == "string" then
|
||||
name = value
|
||||
end
|
||||
end
|
||||
|
||||
return Pool(name, filter)
|
||||
end
|
||||
|
||||
-- Internal: Evaluates an Entity for all the System's Pools.
|
||||
-- @param e The Entity to check
|
||||
-- @treturn System self
|
||||
function System:__evaluate(e)
|
||||
for _, pool in ipairs(self.__pools) do
|
||||
local has = pool:has(e)
|
||||
local eligible = pool:__eligible(e)
|
||||
|
||||
if not has and eligible then
|
||||
pool:__add(e)
|
||||
elseif has and not eligible then
|
||||
pool:__remove(e)
|
||||
end
|
||||
pool:evaluate(e)
|
||||
end
|
||||
|
||||
return self
|
||||
|
@ -112,7 +114,7 @@ end
|
|||
function System:__remove(e)
|
||||
for _, pool in ipairs(self.__pools) do
|
||||
if pool:has(e) then
|
||||
pool:__remove(e)
|
||||
pool:remove(e)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -123,36 +125,12 @@ end
|
|||
-- @treturn System self
|
||||
function System:__clear()
|
||||
for i = 1, #self.__pools do
|
||||
self.__pools[i]:__clear()
|
||||
self.__pools[i]:clear()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Enables the System.
|
||||
-- @treturn System self
|
||||
function System:enable()
|
||||
self:setEnabled(true)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Disables the System.
|
||||
-- @treturn System self
|
||||
function System:disable()
|
||||
self:setEnabled(false)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Toggles if the System is enabled.
|
||||
-- @treturn System self
|
||||
function System:toggleEnabled()
|
||||
self:setEnabled(not self.__enabled)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets if the System is enabled
|
||||
-- @tparam boolean enable
|
||||
-- @treturn System self
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
--- Container for registered SystemClasses
|
||||
-- @module Systems
|
||||
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
local Type = require(PATH..".type")
|
||||
|
||||
local Systems = {}
|
||||
|
||||
--- Registers a SystemClass.
|
||||
-- @tparam string name Name to register under
|
||||
-- @tparam System systemClass SystemClass to register
|
||||
function Systems.register(name, systemClass)
|
||||
if (type(name) ~= "string") then
|
||||
error("bad argument #1 to 'Systems.register' (string expected, got "..type(name)..")", 3)
|
||||
end
|
||||
|
||||
if (not Type.isSystemClass(systemClass)) then
|
||||
error("bad argument #2 to 'Systems.register' (systemClass expected, got "..type(systemClass)..")", 3)
|
||||
end
|
||||
|
||||
if (rawget(Systems, name)) then
|
||||
error("bad argument #2 to 'Systems.register' (System with name '"..name.."' is already registerd)", 3)
|
||||
end
|
||||
|
||||
Systems[name] = systemClass
|
||||
systemClass.__name = name
|
||||
end
|
||||
|
||||
--- Returns true if the containter has the SystemClass with the name
|
||||
-- @tparam string name Name of the SystemClass to check
|
||||
-- @treturn boolean
|
||||
function Systems.has(name)
|
||||
return Systems[name] and true or false
|
||||
end
|
||||
|
||||
--- Returns the SystemClass with the name
|
||||
-- @tparam string name Name of the SystemClass to get
|
||||
-- @return SystemClass with the name
|
||||
function Systems.get(name)
|
||||
return Systems[name]
|
||||
end
|
||||
|
||||
return setmetatable(Systems, {
|
||||
__index = function(_, name)
|
||||
error("Attempt to index system '"..tostring(name).."' that does not exist / was not registered", 2)
|
||||
end
|
||||
})
|
|
@ -45,11 +45,4 @@ function Type.isWorld(t)
|
|||
return type(t) == "table" and t.__isWorld or false
|
||||
end
|
||||
|
||||
--- Returns if object is an Assemblage.
|
||||
-- @param t Object to check
|
||||
-- @treturn boolean
|
||||
function Type.isAssemblage(t)
|
||||
return type(t) == "table" and t.__isAssemblage or false
|
||||
end
|
||||
|
||||
return Type
|
||||
|
|
|
@ -61,7 +61,7 @@ function World:addEntity(e)
|
|||
end
|
||||
|
||||
e.__world = self
|
||||
self.__added:__add(e)
|
||||
self.__added:add(e)
|
||||
|
||||
return self
|
||||
end
|
||||
|
@ -74,7 +74,7 @@ function World:removeEntity(e)
|
|||
error("bad argument #1 to 'World:removeEntity' (Entity expected, got "..type(e)..")", 2)
|
||||
end
|
||||
|
||||
self.__removed:__add(e)
|
||||
self.__removed:add(e)
|
||||
|
||||
return self
|
||||
end
|
||||
|
@ -83,7 +83,7 @@ end
|
|||
-- @param e Entity to mark as dirty
|
||||
function World:__dirtyEntity(e)
|
||||
if not self.__dirty:has(e) then
|
||||
self.__dirty:__add(e)
|
||||
self.__dirty:add(e)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -107,7 +107,8 @@ function World:__flush()
|
|||
for i = 1, self.__backAdded.size do
|
||||
e = self.__backAdded[i]
|
||||
|
||||
self.__entities:__add(e)
|
||||
if e.__world == self then
|
||||
self.__entities:add(e)
|
||||
|
||||
for j = 1, self.__systems.size do
|
||||
self.__systems[j]:__evaluate(e)
|
||||
|
@ -115,14 +116,16 @@ function World:__flush()
|
|||
|
||||
self:onEntityAdded(e)
|
||||
end
|
||||
self.__backAdded:__clear()
|
||||
end
|
||||
self.__backAdded:clear()
|
||||
|
||||
-- Process removed entities
|
||||
for i = 1, self.__backRemoved.size do
|
||||
e = self.__backRemoved[i]
|
||||
|
||||
if e.__world == self then
|
||||
e.__world = nil
|
||||
self.__entities:__remove(e)
|
||||
self.__entities:remove(e)
|
||||
|
||||
for j = 1, self.__systems.size do
|
||||
self.__systems[j]:__remove(e)
|
||||
|
@ -130,17 +133,20 @@ function World:__flush()
|
|||
|
||||
self:onEntityRemoved(e)
|
||||
end
|
||||
self.__backRemoved:__clear()
|
||||
end
|
||||
self.__backRemoved:clear()
|
||||
|
||||
-- Process dirty entities
|
||||
for i = 1, self.__backDirty.size do
|
||||
e = self.__backDirty[i]
|
||||
|
||||
if e.__world == self then
|
||||
for j = 1, self.__systems.size do
|
||||
self.__systems[j]:__evaluate(e)
|
||||
end
|
||||
end
|
||||
self.__backDirty:__clear()
|
||||
end
|
||||
self.__backDirty:clear()
|
||||
|
||||
return self
|
||||
end
|
||||
|
@ -171,7 +177,7 @@ function World:addSystem(systemClass)
|
|||
local system = systemClass(self)
|
||||
|
||||
self.__systemLookup[systemClass] = system
|
||||
self.__systems:__add(system)
|
||||
self.__systems:add(system)
|
||||
|
||||
for callbackName, callback in pairs(systemClass) do
|
||||
-- Skip callback if its blacklisted
|
||||
|
@ -278,9 +284,13 @@ function World:clear()
|
|||
self:removeEntity(self.__entities[i])
|
||||
end
|
||||
|
||||
for i = 1, self.__systems.size do
|
||||
self.__systems[i]:__clear()
|
||||
for i = 1, self.__added.size do
|
||||
local e = self.__added[i]
|
||||
e.__world = nil
|
||||
end
|
||||
self.__added:clear()
|
||||
|
||||
self:__flush()
|
||||
|
||||
return self
|
||||
end
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
--- Worlds
|
||||
-- Container for registered Worlds
|
||||
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
local Type = require(PATH..".type")
|
||||
|
||||
local Worlds = {}
|
||||
|
||||
--- Registers a World.
|
||||
-- @tparam string name Name to register under
|
||||
-- @param world World to register
|
||||
function Worlds.register(name, world)
|
||||
if (type(name) ~= "string") then
|
||||
error("bad argument #1 to 'Worlds.register' (string expected, got "..type(name)..")", 3)
|
||||
end
|
||||
|
||||
if (not Type.isWorld(world)) then
|
||||
error("bad argument #2 to 'Worlds.register' (world expected, got "..type(world)..")", 3)
|
||||
end
|
||||
|
||||
if (rawget(Worlds, name)) then
|
||||
error("bad argument #2 to 'Worlds.register' (World with name '"..name.."' is already registerd)", 3)
|
||||
end
|
||||
|
||||
Worlds[name] = world
|
||||
world.__name = name
|
||||
end
|
||||
|
||||
--- Returns true if the containter has the World with the name
|
||||
-- @tparam string name Name of the World to check
|
||||
-- @treturn boolean
|
||||
function Worlds.has(name)
|
||||
return Worlds[name] and true or false
|
||||
end
|
||||
|
||||
--- Returns the World with the name
|
||||
-- @tparam string name Name of the World to get
|
||||
-- @return World with the name
|
||||
function Worlds.get(name)
|
||||
return Worlds[name]
|
||||
end
|
||||
|
||||
return setmetatable(Worlds, {
|
||||
__index = function(_, name)
|
||||
error("Attempt to index world '"..tostring(name).."' that does not exist / was not registered", 2)
|
||||
end
|
||||
})
|
|
@ -5,61 +5,59 @@ local Component = Concord.component
|
|||
local System = Concord.system
|
||||
local Assemblage = Concord.assemblage
|
||||
|
||||
local Game = Concord.context()
|
||||
|
||||
local Legs = Component(function(e, legCount)
|
||||
local Legs = Component("Legs", function(e, legCount)
|
||||
e.legCount = legCount or 0
|
||||
end)
|
||||
|
||||
local Instinct = Component(function(e) -- luacheck: ignore
|
||||
local Instinct = Component("Instinct", function(e) -- luacheck: ignore
|
||||
end)
|
||||
|
||||
local Cool = Component(function(e, coolness)
|
||||
local Cool = Component("Cool", function(e, coolness)
|
||||
e.coolness = coolness
|
||||
end)
|
||||
|
||||
local Wings = Component(function(e)
|
||||
local Wings = Component("Wings", function(e)
|
||||
e.wingCount = 2
|
||||
end)
|
||||
|
||||
|
||||
local Animal = Assemblage(function(e, legCount)
|
||||
local Animal = function(e, legCount)
|
||||
e
|
||||
:give(Legs, legCount)
|
||||
:give(Instinct)
|
||||
:give("Legs", legCount)
|
||||
:give("Instinct")
|
||||
|
||||
print("Animal")
|
||||
end)
|
||||
end
|
||||
|
||||
local Lion = Assemblage(function(e, coolness)
|
||||
local Lion = function(e, coolness)
|
||||
e
|
||||
:assemble(Animal, 4)
|
||||
:give(Cool, coolness)
|
||||
|
||||
print("Lion")
|
||||
end)
|
||||
end
|
||||
|
||||
local Eagle = Assemblage(function(e)
|
||||
local Eagle = function(e)
|
||||
e
|
||||
:assemble(Animal, 2)
|
||||
:give(Wings)
|
||||
|
||||
print("Eagle")
|
||||
end)
|
||||
end
|
||||
|
||||
local Griffin = Assemblage(function(e, coolness)
|
||||
local Griffin = function(e, coolness)
|
||||
e
|
||||
:assemble(Animal, 4)
|
||||
:assemble(Lion, coolness * 2)
|
||||
:assemble(Eagle)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local myAnimal = Entity()
|
||||
:assemble(Griffin, 5)
|
||||
--:apply()
|
||||
|
||||
print(myAnimal:has(Legs))
|
||||
print(myAnimal:has(Instinct))
|
||||
print(myAnimal:has(Cool))
|
||||
print(myAnimal:has(Wings))
|
||||
print(myAnimal:has("Legs"))
|
||||
print(myAnimal:has("Instinct"))
|
||||
print(myAnimal:has("Cool"))
|
||||
print(myAnimal:has("Wings"))
|
||||
|
|
|
@ -11,11 +11,10 @@ local function display(t)
|
|||
end
|
||||
end
|
||||
|
||||
local test_component_1 = Concord.component(function(e, x, y)
|
||||
local test_component_1 = Concord.component("test_component_1", function(e, x, y)
|
||||
e.x = x or 0
|
||||
e.y = y or 0
|
||||
end)
|
||||
Concord.components.register("test_component_1", test_component_1)
|
||||
|
||||
function test_component_1:serialize()
|
||||
return {
|
||||
|
@ -29,10 +28,9 @@ function test_component_1:deserialize(data)
|
|||
self.y = data.y or 0
|
||||
end
|
||||
|
||||
local test_component_2 = Concord.component(function(e, foo)
|
||||
local test_component_2 = Concord.component("test_component_2", function(e, foo)
|
||||
e.foo = foo
|
||||
end)
|
||||
Concord.components.register("test_component_2", test_component_2)
|
||||
|
||||
function test_component_2:serialize()
|
||||
return {
|
||||
|
@ -50,8 +48,8 @@ local world_2 = Concord.world()
|
|||
|
||||
-- Test Entity
|
||||
Concord.entity(world_1)
|
||||
:give(test_component_1, 100, 50)
|
||||
:give(test_component_2, "Hello World!")
|
||||
:give("test_component_1", 100, 50)
|
||||
:give("test_component_2", "Hello World!")
|
||||
|
||||
-- Serialize world
|
||||
local data = world_1:serialize()
|
||||
|
@ -62,8 +60,8 @@ world_2:deserialize(data)
|
|||
-- Check result
|
||||
local test_entity_copy = world_2:getEntities()[1]
|
||||
|
||||
local test_comp_1 = test_entity_copy[test_component_1]
|
||||
local test_comp_2 = test_entity_copy[test_component_2]
|
||||
local test_comp_1 = test_entity_copy["test_component_1"]
|
||||
local test_comp_2 = test_entity_copy["test_component_2"]
|
||||
|
||||
print(test_comp_1.x, test_comp_1.y)
|
||||
print(test_comp_2.foo)
|
|
@ -6,33 +6,33 @@ local System = Concord.system
|
|||
|
||||
local Game = Concord.world()
|
||||
|
||||
local Position = Component(function(e, x, y)
|
||||
local Position = Component('Position', function(e, x, y)
|
||||
e.x = x
|
||||
e.y = y
|
||||
end)
|
||||
|
||||
local Rectangle = Component(function(e, w, h)
|
||||
local Rectangle = Component('Rectangle', function(e, w, h)
|
||||
e.w = w
|
||||
e.h = h
|
||||
end)
|
||||
|
||||
local Circle = Component(function(e, r)
|
||||
local Circle = Component('Circle', function(e, r)
|
||||
e.r = r
|
||||
end)
|
||||
|
||||
local Color = Component(function(e, r, g, b, a)
|
||||
local Color = Component('Color', function(e, r, g, b, a)
|
||||
e.r = r
|
||||
e.g = g
|
||||
e.b = b
|
||||
e.a = a
|
||||
end)
|
||||
|
||||
local RectangleRenderer = System({Position, Rectangle})
|
||||
local RectangleRenderer = System{pool = {'Position', 'Rectangle'}}
|
||||
function RectangleRenderer:draw()
|
||||
for _, e in ipairs(self.pool) do
|
||||
local position = e:get(Position)
|
||||
local rectangle = e:get(Rectangle)
|
||||
local color = e:get(Color)
|
||||
local position = e:get('Position')
|
||||
local rectangle = e:get('Rectangle')
|
||||
local color = e:get('Color')
|
||||
|
||||
love.graphics.setColor(255, 255, 255)
|
||||
if color then
|
||||
|
@ -43,7 +43,7 @@ function RectangleRenderer:draw()
|
|||
end
|
||||
end
|
||||
|
||||
local CircleRenderer = System({Position, Circle})
|
||||
local CircleRenderer = System{pool = {'Position', 'Circle'}}
|
||||
function CircleRenderer:flush()
|
||||
for _, e in ipairs(self.pool.removed) do
|
||||
print(tostring(e).. " was removed from my pool D:")
|
||||
|
@ -52,9 +52,9 @@ end
|
|||
|
||||
function CircleRenderer:draw()
|
||||
for _, e in ipairs(self.pool) do
|
||||
local position = e:get(Position)
|
||||
local circle = e:get(Circle)
|
||||
local color = e:get(Color)
|
||||
local position = e:get('Position')
|
||||
local circle = e:get('Circle')
|
||||
local color = e:get('Color')
|
||||
|
||||
love.graphics.setColor(255, 255, 255)
|
||||
if color then
|
||||
|
@ -65,7 +65,7 @@ function CircleRenderer:draw()
|
|||
end
|
||||
end
|
||||
|
||||
local RandomRemover = System({})
|
||||
local RandomRemover = System{pool = {}}
|
||||
|
||||
function RandomRemover:init()
|
||||
self.time = 0
|
||||
|
@ -93,11 +93,11 @@ Game:addSystem(CircleRenderer(), "draw")
|
|||
|
||||
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))
|
||||
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))
|
||||
|
||||
if love.math.random(0, 1) == 0 then
|
||||
e:give(Color, love.math.random(), love.math.random(), love.math.random(), 1)
|
||||
e:give('Color', love.math.random(), love.math.random(), love.math.random(), 1)
|
||||
end
|
||||
|
||||
Game:addEntity(e)
|
||||
|
@ -105,11 +105,11 @@ end
|
|||
|
||||
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))
|
||||
e:give('Position', love.math.random(0, 700), love.math.random(0, 700))
|
||||
e:give('Circle', love.math.random(5, 20))
|
||||
|
||||
if love.math.random(0, 1) == 0 then
|
||||
e:give(Color, love.math.random(), love.math.random(), love.math.random(), 1)
|
||||
e:give('Color', love.math.random(), love.math.random(), love.math.random(), 1)
|
||||
end
|
||||
|
||||
Game:addEntity(e)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue