mirror of
https://github.com/Keyslam-Group/Concord.git
synced 2025-08-20 13:28:30 -04:00
Replaced Pools with Filters
Filters allow for a Pool constructor (defaults to Lists) that can be used to define Custom Pools. The constructor is a function that takes the Filter Definition and returns a Custom Pool with these functions: :add(e) - Add the Entity to the pool :remove(e) - Remove the Entity from the pool :has(e) boolean - Checks if the Entity exists in the pool :clear() - Clears the Pool from Entities Fixes #40
This commit is contained in:
parent
07bd5d0f28
commit
743d662ef9
6 changed files with 221 additions and 163 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -37,4 +37,7 @@ luac.out
|
|||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
*.hex
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
168
concord/filter.lua
Normal file
168
concord/filter.lua
Normal file
|
@ -0,0 +1,168 @@
|
|||
--- Used to filter Entities with specific Components
|
||||
-- A Filter has an associated Pool that can contain any amount of Entities.
|
||||
-- @classmod Filter
|
||||
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
local List = require(PATH..".list")
|
||||
local Type = require(PATH..".type")
|
||||
local Components = require(PATH..".components")
|
||||
|
||||
|
||||
local Filter = {}
|
||||
Filter.__mt = {
|
||||
__index = Filter,
|
||||
}
|
||||
|
||||
--- Validates a Filter Definition to make sure every component is valid.
|
||||
-- @string name Name for the Filter.
|
||||
-- @tparam table definition Table containing the Filter Definition
|
||||
-- @tparam onComponent Optional function, called when a component is valid.
|
||||
function Filter.validate (name, def, onComponent)
|
||||
if type(def) ~= 'table' then
|
||||
error("invalid component list for filter '"..name.."' (table expected, got "..type(def)..")", 3)
|
||||
end
|
||||
|
||||
if not onComponent and def.constructor and not Type.isCallable(def.constructor) then
|
||||
error("invalid pool constructor (callable expected, got "..type(def.constructor)..")", 3)
|
||||
end
|
||||
|
||||
for n, component in ipairs(def) do
|
||||
local ok, err, reject = Components.try(component, true)
|
||||
|
||||
if not ok then
|
||||
error("invalid component for filter '"..name.."' at position #"..n.." ("..err..")", 3)
|
||||
end
|
||||
|
||||
if onComponent then
|
||||
onComponent(component, reject)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Parses the Filter Defintion into two tables
|
||||
-- required: An array of all the required component names.
|
||||
-- rejected: An array of all the components that will be rejected.
|
||||
-- @string name Name for the Filter.
|
||||
-- @tparam table definition Table containing the Filter Definition
|
||||
-- @treturn table required
|
||||
-- @treturn table rejected
|
||||
function Filter.parse (name, def)
|
||||
local required, rejected = {}, {}
|
||||
|
||||
Filter.validate(name, def, function (component, reject)
|
||||
if reject then
|
||||
table.insert(rejected, reject)
|
||||
else
|
||||
table.insert(required, component)
|
||||
end
|
||||
end)
|
||||
|
||||
return required, rejected
|
||||
end
|
||||
|
||||
--- Creates a new Filter
|
||||
-- @string name Name for the Filter.
|
||||
-- @tparam table definition Table containing the Filter Definition
|
||||
-- @treturn Filter The new Filter
|
||||
-- @treturn Pool The associated Pool
|
||||
function Filter.new (name, def)
|
||||
local constructor = def.constructor or List
|
||||
local pool = constructor(def)
|
||||
|
||||
local required, rejected = Filter.parse(name, def)
|
||||
|
||||
local filter = setmetatable({
|
||||
pool = pool,
|
||||
|
||||
__required = required,
|
||||
__rejected = rejected,
|
||||
|
||||
__name = name,
|
||||
|
||||
__isFilter = true,
|
||||
}, Filter.__mt)
|
||||
|
||||
return filter, pool
|
||||
end
|
||||
|
||||
--- Checks if an Entity fulfills the Filter requirements.
|
||||
-- @tparam Entity e Entity to check
|
||||
-- @treturn boolean
|
||||
function Filter:eligible(e)
|
||||
for i=#self.__required, 1, -1 do
|
||||
local name = self.__required[i]
|
||||
if not e[name] then return false end
|
||||
end
|
||||
|
||||
for i=#self.__rejected, 1, -1 do
|
||||
local name = self.__rejected[i]
|
||||
if e[name] then return false end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function Filter:evaluate (e)
|
||||
local has = self.pool:has(e)
|
||||
local eligible = self:eligible(e)
|
||||
|
||||
if not has and eligible then
|
||||
self.pool:add(e)
|
||||
elseif has and not eligible then
|
||||
self.pool:remove(e)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-- Adds an Entity to the Pool, if it passes the Filter.
|
||||
-- @param e Entity to add
|
||||
-- @param bypass Whether to bypass the Filter or not.
|
||||
-- @treturn Filter self
|
||||
-- @treturn boolean Whether the entity was added or not.
|
||||
function Filter:add (e, bypass)
|
||||
if not bypass and not self:eligible(e) then
|
||||
return self, false
|
||||
end
|
||||
|
||||
self.pool:add(e)
|
||||
|
||||
return self, true
|
||||
end
|
||||
|
||||
-- Remove an Entity from the Pool associated to this Filter.
|
||||
-- @param e Entity to remove
|
||||
-- @treturn Filter self
|
||||
function Filter:remove (e)
|
||||
self.pool:remove(e)
|
||||
return self
|
||||
end
|
||||
|
||||
-- Clear the Pool associated to this Filter.
|
||||
-- @param e Entity to remove
|
||||
-- @treturn Filter self
|
||||
function Filter:clear (e)
|
||||
self.pool:clear(e)
|
||||
return self
|
||||
end
|
||||
|
||||
-- Check if the Pool bound to this System contains the passed Entity
|
||||
-- @param e Entity to check
|
||||
-- @treturn boolean Whether the Entity exists.
|
||||
function Filter:has (e)
|
||||
return self.pool:has(e)
|
||||
end
|
||||
|
||||
--- Gets the name of the Filter
|
||||
-- @treturn string
|
||||
function Filter:getName()
|
||||
return self.__name
|
||||
end
|
||||
|
||||
return setmetatable(Filter, {
|
||||
__call = function(_, ...)
|
||||
return Filter.new(...)
|
||||
end,
|
||||
})
|
|
@ -16,7 +16,7 @@ end
|
|||
|
||||
--- Adds an object to the List.
|
||||
-- Object must be of reference type
|
||||
-- Object may not be the string 'size'
|
||||
-- Object may not be the string 'size', 'onAdded' or 'onRemoved'
|
||||
-- @param obj Object to add
|
||||
-- @treturn List self
|
||||
function List:add(obj)
|
||||
|
@ -26,6 +26,7 @@ function List:add(obj)
|
|||
self[obj] = size
|
||||
self.size = size
|
||||
|
||||
if self.onAdded then self:onAdded(obj) end
|
||||
return self
|
||||
end
|
||||
|
||||
|
@ -51,6 +52,7 @@ function List:remove(obj)
|
|||
self[obj] = nil
|
||||
self.size = size - 1
|
||||
|
||||
if self.onRemoved then self:onRemoved(obj) end
|
||||
return self
|
||||
end
|
||||
|
||||
|
@ -108,6 +110,16 @@ function List:sort(order)
|
|||
return self
|
||||
end
|
||||
|
||||
--- Callback for when an item is added to the List.
|
||||
-- @param obj Object that was added
|
||||
function List:onAdded (obj) --luacheck: ignore
|
||||
end
|
||||
|
||||
--- Callback for when an item is removed to the List.
|
||||
-- @param obj Object that was removed
|
||||
function List:onRemoved (obj) --luacheck: ignore
|
||||
end
|
||||
|
||||
return setmetatable(List, {
|
||||
__call = function()
|
||||
return List.new()
|
||||
|
|
115
concord/pool.lua
115
concord/pool.lua
|
@ -1,115 +0,0 @@
|
|||
--- Used to iterate over Entities with a specific Components
|
||||
-- A Pool contain a any amount of Entities.
|
||||
-- @classmod Pool
|
||||
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
local List = require(PATH..".list")
|
||||
|
||||
local Pool = {}
|
||||
Pool.__mt = {
|
||||
__index = Pool,
|
||||
}
|
||||
|
||||
--- Creates a new Pool
|
||||
-- @string name Name for the Pool.
|
||||
-- @tparam table filter Table containing the required BaseComponents
|
||||
-- @treturn Pool The new Pool
|
||||
function Pool.new(name, filter)
|
||||
local pool = setmetatable(List(), Pool.__mt)
|
||||
|
||||
pool.__name = name
|
||||
pool.__filter = filter
|
||||
|
||||
pool.__isPool = true
|
||||
|
||||
return pool
|
||||
end
|
||||
|
||||
--- Checks if an Entity is eligible for the Pool.
|
||||
-- @tparam Entity e Entity to check
|
||||
-- @treturn boolean
|
||||
function Pool:eligible(e)
|
||||
for i=#self.__filter.require, 1, -1 do
|
||||
local name = self.__filter.require[i]
|
||||
if not e[name] then return false end
|
||||
end
|
||||
|
||||
for i=#self.__filter.reject, 1, -1 do
|
||||
local name = self.__filter.reject[i]
|
||||
if e[name] then return false end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Adds an Entity to the Pool, if it can be eligible.
|
||||
-- @param e Entity to add
|
||||
-- @treturn Pool self
|
||||
-- @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
|
||||
|
||||
--- Evaluate whether an Entity should be added or removed from the Pool.
|
||||
-- @param e Entity to add or remove
|
||||
-- @treturn Pool self
|
||||
function Pool:evaluate(e)
|
||||
local has = self:has(e)
|
||||
local eligible = self:eligible(e)
|
||||
|
||||
if not has and eligible then
|
||||
self:add(e, true) --Bypass the check cause we already checked
|
||||
elseif has and not eligible then
|
||||
self:remove(e)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the name of the Pool
|
||||
-- @treturn string
|
||||
function Pool:getName()
|
||||
return self.__name
|
||||
end
|
||||
|
||||
--- Gets the filter of the Pool.
|
||||
-- Warning: Do not modify this filter.
|
||||
-- @return Filter of the Pool.
|
||||
function Pool:getFilter()
|
||||
return self.__filter
|
||||
end
|
||||
|
||||
--- Callback for when an Entity is added to the Pool.
|
||||
-- @tparam Entity e Entity that was added.
|
||||
function Pool:onEntityAdded(e) -- luacheck: ignore
|
||||
end
|
||||
|
||||
-- Callback for when an Entity is removed from the Pool.
|
||||
-- @tparam Entity e Entity that was removed.
|
||||
function Pool:onEntityRemoved(e) -- luacheck: ignore
|
||||
end
|
||||
|
||||
return setmetatable(Pool, {
|
||||
__index = List,
|
||||
__call = function(_, ...)
|
||||
return Pool.new(...)
|
||||
end,
|
||||
})
|
|
@ -5,9 +5,8 @@
|
|||
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
local Pool = require(PATH..".pool")
|
||||
local Filter = require(PATH..".filter")
|
||||
local Utils = require(PATH..".utils")
|
||||
local Components = require(PATH..".components")
|
||||
|
||||
local System = {
|
||||
ENABLE_OPTIMIZATION = true,
|
||||
|
@ -19,7 +18,7 @@ System.mt = {
|
|||
local system = setmetatable({
|
||||
__enabled = true,
|
||||
|
||||
__pools = {},
|
||||
__filters = {},
|
||||
__world = world,
|
||||
|
||||
__isSystem = true,
|
||||
|
@ -33,11 +32,11 @@ System.mt = {
|
|||
Utils.shallowCopy(systemClass, system)
|
||||
end
|
||||
|
||||
for name, filter in pairs(systemClass.__filters) do
|
||||
local pool = Pool(name, filter)
|
||||
for name, def in pairs(systemClass.__definition) do
|
||||
local filter, pool = Filter(name, Utils.shallowCopy(def, {}))
|
||||
|
||||
system[name] = pool
|
||||
system.__pools[#system.__pools + 1] = pool
|
||||
table.insert(system.__filters, filter)
|
||||
end
|
||||
|
||||
system:init(world)
|
||||
|
@ -45,45 +44,20 @@ System.mt = {
|
|||
return system
|
||||
end,
|
||||
}
|
||||
|
||||
local validateFilters = function (definition)
|
||||
local filters = {}
|
||||
|
||||
for name, componentsList in pairs(definition) 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
|
||||
|
||||
filters[name] = { require = {}, reject = {} }
|
||||
|
||||
for n, component in ipairs(componentsList) do
|
||||
local ok, componentClass, rejected = Components.try(component, true)
|
||||
|
||||
if not ok then
|
||||
error("invalid component for filter '"..name.."' at position #"..n.." ("..componentClass..")", 3)
|
||||
elseif rejected then
|
||||
table.insert(filters[name].reject, rejected)
|
||||
else
|
||||
table.insert(filters[name].require, component)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return filters
|
||||
end
|
||||
|
||||
--- Creates a new SystemClass.
|
||||
-- @param table filters A table containing filters (name = {components...})
|
||||
-- @treturn System A new SystemClass
|
||||
function System.new(definition)
|
||||
local filters = validateFilters(definition)
|
||||
for name, def in pairs(definition) do
|
||||
if type(name) ~= 'string' then
|
||||
error("invalid name for filter (string key expected, got "..type(name)..")", 2)
|
||||
end
|
||||
|
||||
Filter.validate(name, def)
|
||||
end
|
||||
|
||||
local systemClass = setmetatable({
|
||||
__filters = filters,
|
||||
__definition = definition,
|
||||
|
||||
__isSystemClass = true,
|
||||
}, System.mt)
|
||||
|
@ -103,8 +77,8 @@ end
|
|||
-- @param e The Entity to check
|
||||
-- @treturn System self
|
||||
function System:__evaluate(e)
|
||||
for _, pool in ipairs(self.__pools) do
|
||||
pool:evaluate(e)
|
||||
for _, filter in ipairs(self.__filters) do
|
||||
filter:evaluate(e)
|
||||
end
|
||||
|
||||
return self
|
||||
|
@ -114,9 +88,9 @@ end
|
|||
-- @param e The Entity to remove
|
||||
-- @treturn System self
|
||||
function System:__remove(e)
|
||||
for _, pool in ipairs(self.__pools) do
|
||||
if pool:has(e) then
|
||||
pool:remove(e)
|
||||
for _, filter in ipairs(self.__filters) do
|
||||
if filter:has(e) then
|
||||
filter:remove(e)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -126,8 +100,8 @@ end
|
|||
-- Internal: Clears all Entities from the System.
|
||||
-- @treturn System self
|
||||
function System:__clear()
|
||||
for i = 1, #self.__pools do
|
||||
self.__pools[i]:clear()
|
||||
for _, filter in ipairs(self.__filters) do
|
||||
filter:clear()
|
||||
end
|
||||
|
||||
return self
|
||||
|
|
|
@ -3,6 +3,15 @@
|
|||
|
||||
local Type = {}
|
||||
|
||||
function Type.isCallable(t)
|
||||
if type(t) == "function" then return true end
|
||||
|
||||
local meta = getmetatable(t)
|
||||
if meta and type(meta.__call) == "function" then return true end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns if object is an Entity.
|
||||
-- @param t Object to check
|
||||
-- @treturn boolean
|
||||
|
@ -45,4 +54,11 @@ function Type.isWorld(t)
|
|||
return type(t) == "table" and t.__isWorld or false
|
||||
end
|
||||
|
||||
--- Returns if object is a Filter.
|
||||
-- @param t Object to check
|
||||
-- @treturn boolean
|
||||
function Type.isFilter(t)
|
||||
return type(t) == "table" and t.__isFilter or false
|
||||
end
|
||||
|
||||
return Type
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue