mirror of
https://github.com/Keyslam-Group/Concord.git
synced 2025-09-10 16:17:48 -04:00
Compare commits
No commits in common. "main" and "v3.0" have entirely different histories.
17 changed files with 299 additions and 734 deletions
38
.github/workflows/doc.yml
vendored
38
.github/workflows/doc.yml
vendored
|
@ -1,38 +0,0 @@
|
|||
name: Build Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build docs
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Lua
|
||||
uses: leafo/gh-actions-lua@v8
|
||||
with:
|
||||
luaVersion: 5.4
|
||||
|
||||
- name: Install Luarocks
|
||||
uses: leafo/gh-actions-luarocks@v4
|
||||
|
||||
- name: Install LDoc
|
||||
run: luarocks install ldoc
|
||||
|
||||
- name: Show
|
||||
run: luarocks show ldoc
|
||||
|
||||
- name: Build docs
|
||||
run: ldoc .
|
||||
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.5
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: docs
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -37,7 +37,4 @@ luac.out
|
|||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
*.hex
|
|
@ -1,2 +1 @@
|
|||
---@diagnostic disable: lowercase-global
|
||||
std="love+luajit"
|
||||
|
|
31
README.md
31
README.md
|
@ -7,7 +7,7 @@ With Concord it is possibile to easily write fast and clean code.
|
|||
This readme will explain how to use Concord.
|
||||
|
||||
Additionally all of Concord is documented using the LDoc format.
|
||||
Auto generated docs for Concord can be found in `docs` folder, or on the [GitHub page](https://keyslam-group.github.io/Concord/).
|
||||
Auto generated docs for Concord can be found in `docs` folder, or on the [Github page](https://tjakka5.github.io/Concord/).
|
||||
|
||||
---
|
||||
|
||||
|
@ -133,21 +133,16 @@ Concord does a few things that might not be immediately clear. This segment shou
|
|||
Since you'll have lots of Components and Systems in your game Concord makes it a bit easier to load things in.
|
||||
|
||||
```lua
|
||||
-- Loads all files in the directory. Components automatically register into Concord.components, so loading them into a namespace isn't necessary.
|
||||
Concord.utils.loadNamespace("path/to/components")
|
||||
|
||||
print(Concord.components.componentName)
|
||||
|
||||
-- Loads all files in the directory, and puts the return value in the table Systems. The key is their filename without any extension
|
||||
local Systems = {}
|
||||
Concord.utils.loadNamespace("path/to/systems", Systems)
|
||||
|
||||
myWorld:addSystems(
|
||||
Systems.healthSystem
|
||||
Systems.damageSystem,
|
||||
Systems.moveSystem,
|
||||
-- etc
|
||||
)
|
||||
print(Systems.systemName)
|
||||
|
||||
-- Loads all files in the directory. Components automatically register into Concord.components, so loading them into a namespace isn't necessary.
|
||||
Concord.utils.loadNamespace("path/to/components")
|
||||
|
||||
print(Concord.components.componentName)
|
||||
```
|
||||
|
||||
#### Method chaining
|
||||
|
@ -175,7 +170,7 @@ When defining a ComponentClass you need to pass in a name and usually a `populat
|
|||
-- Create the position class with a populate function
|
||||
-- The component variable is the actual Component given to an Entity
|
||||
-- The x and y variables are values we pass in when we create the Component
|
||||
Concord.component("position", function(component, x, y)
|
||||
Concord.component("position" function(component, x, y)
|
||||
component.x = x or 0
|
||||
component.y = y or 0
|
||||
end)
|
||||
|
@ -271,10 +266,10 @@ Systems can have multiple pools.
|
|||
|
||||
```lua
|
||||
-- Create a System
|
||||
local mySystemClass = Concord.system({pool = {"position"}}) -- Pool named 'pool' will contain all Entities with a position Component
|
||||
local mySystemClass = Concord.system(pool = {"position"}) -- Pool named 'pool' will contain all Entities with a position Component
|
||||
|
||||
-- Create a System with multiple pools
|
||||
local mySystemClass = Concord.system({
|
||||
local mySystemClass = Concord.system(
|
||||
pool = { -- This pool will be named 'pool'
|
||||
"position",
|
||||
"velocity",
|
||||
|
@ -283,7 +278,7 @@ local mySystemClass = Concord.system({
|
|||
"health",
|
||||
"damageable",
|
||||
}
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
```lua
|
||||
|
@ -299,14 +294,14 @@ end
|
|||
-- Defining a function
|
||||
function mySystemClass:update(dt)
|
||||
-- Iterate over all entities in the Pool
|
||||
for _, e in ipairs(self.pool) do
|
||||
for _, e in ipairs(self.pool)
|
||||
-- Do something with the Components
|
||||
e.position.x = e.position.x + e.velocity.x * dt
|
||||
e.position.y = e.position.y + e.velocity.y * dt
|
||||
end
|
||||
|
||||
-- Iterate over all entities in the second Pool
|
||||
for _, e in ipairs(self.secondPool) do
|
||||
for _, e in ipairs(self.secondPool)
|
||||
-- Do something
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
local PATH = (...):gsub("(%.init)$", "")
|
||||
|
||||
return {
|
||||
serializable = require(PATH..".serializable"),
|
||||
key = require(PATH..".key"),
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
local PATH = (...):gsub('%.builtins%.[^%.]+$', '')
|
||||
|
||||
local Component = require(PATH..".component")
|
||||
|
||||
local getKey = function (self, key)
|
||||
local entity = self.__entity
|
||||
|
||||
if not entity:inWorld() then
|
||||
error("entity needs to belong to a world")
|
||||
end
|
||||
|
||||
local world = entity:getWorld()
|
||||
|
||||
return world:__assignKey(entity, key)
|
||||
end
|
||||
|
||||
local Key = Component("key", function (self, key)
|
||||
self.value = getKey(self, key)
|
||||
end)
|
||||
|
||||
function Key:deserialize (data)
|
||||
self.value = getKey(self, data)
|
||||
end
|
||||
|
||||
function Key.__mt:__call()
|
||||
return self.value
|
||||
end
|
||||
|
||||
function Key:removed (replaced)
|
||||
if not replaced then
|
||||
local entity = self.__entity
|
||||
|
||||
if entity:inWorld() then
|
||||
local world = entity:getWorld()
|
||||
|
||||
return world:__clearKey(entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Key
|
|
@ -1,12 +0,0 @@
|
|||
local PATH = (...):gsub('%.builtins%.[^%.]+$', '')
|
||||
|
||||
local Component = require(PATH..".component")
|
||||
|
||||
local Serializable = Component("serializable")
|
||||
|
||||
function Serializable:serialize ()
|
||||
-- Don't serialize this Component
|
||||
return nil
|
||||
end
|
||||
|
||||
return Serializable
|
|
@ -16,19 +16,15 @@ Component.__mt = {
|
|||
-- @treturn Component A new ComponentClass
|
||||
function Component.new(name, populate)
|
||||
if (type(name) ~= "string") then
|
||||
Utils.error(2, "bad argument #1 to 'Component.new' (string expected, got %s)", type(name))
|
||||
end
|
||||
|
||||
if (string.match(name, Components.__REJECT_MATCH) ~= "") then
|
||||
Utils.error(2, "bad argument #1 to 'Component.new' (Component names can't start with '%s', got %s)", Components.__REJECT_PREFIX, name)
|
||||
error("bad argument #1 to 'Component.new' (string expected, got "..type(name)..")", 2)
|
||||
end
|
||||
|
||||
if (rawget(Components, name)) then
|
||||
Utils.error(2, "bad argument #1 to 'Component.new' (ComponentClass with name '%s' was already registerd)", name) -- luacheck: ignore
|
||||
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
|
||||
Utils.error(2, "bad argument #1 to 'Component.new' (function/nil expected, got %s)", type(populate))
|
||||
error("bad argument #1 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2)
|
||||
end
|
||||
|
||||
local componentClass = setmetatable({
|
||||
|
@ -47,40 +43,31 @@ function Component.new(name, populate)
|
|||
return componentClass
|
||||
end
|
||||
|
||||
-- Internal: Populates a Component with values.
|
||||
-- Internal: Populates a Component with values
|
||||
function Component:__populate() -- luacheck: ignore
|
||||
end
|
||||
|
||||
-- Callback: When the Component gets removed or replaced in an Entity.
|
||||
function Component:removed() -- luacheck: ignore
|
||||
end
|
||||
|
||||
-- Callback: When the Component gets serialized as part of an Entity.
|
||||
function Component:serialize()
|
||||
local data = Utils.shallowCopy(self, {})
|
||||
|
||||
--This values shouldn't be copied over
|
||||
data.__componentClass = nil
|
||||
data.__entity = nil
|
||||
data.__isComponent = nil
|
||||
data.__componentClass = nil
|
||||
data.__isComponent = nil
|
||||
data.__isComponentClass = nil
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
-- Callback: When the Component gets deserialized from serialized data.
|
||||
function Component:deserialize(data)
|
||||
Utils.shallowCopy(data, self)
|
||||
end
|
||||
|
||||
-- Internal: Creates a new Component.
|
||||
-- @param entity The Entity that will receive this Component.
|
||||
-- @return A new Component
|
||||
function Component:__new(entity)
|
||||
function Component:__new()
|
||||
local component = setmetatable({
|
||||
__componentClass = self,
|
||||
|
||||
__entity = entity,
|
||||
__isComponent = true,
|
||||
__isComponentClass = false,
|
||||
}, self.__mt)
|
||||
|
@ -89,13 +76,11 @@ function Component:__new(entity)
|
|||
end
|
||||
|
||||
-- Internal: Creates and populates a new Component.
|
||||
-- @param entity The Entity that will receive this Component.
|
||||
-- @param ... Varargs passed to the populate function
|
||||
-- @return A new populated Component
|
||||
function Component:__initialize(entity, ...)
|
||||
local component = self:__new(entity)
|
||||
function Component:__initialize(...)
|
||||
local component = self:__new()
|
||||
|
||||
---@diagnostic disable-next-line: redundant-parameter
|
||||
self.__populate(component, ...)
|
||||
|
||||
return component
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
--- Container for registered ComponentClasses
|
||||
-- @module Components
|
||||
|
||||
local Components = {}
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
Components.__REJECT_PREFIX = "!"
|
||||
Components.__REJECT_MATCH = "^(%"..Components.__REJECT_PREFIX.."?)(.+)"
|
||||
local Type = require(PATH..".type")
|
||||
|
||||
local Components = {}
|
||||
|
||||
--- Returns true if the containter has the ComponentClass with the specified name
|
||||
-- @string name Name of the ComponentClass to check
|
||||
|
@ -13,43 +14,22 @@ function Components.has(name)
|
|||
return rawget(Components, name) and true or false
|
||||
end
|
||||
|
||||
--- Prefix a component's name with the currently set Reject Prefix
|
||||
-- @string name Name of the ComponentClass to reject
|
||||
-- @treturn string
|
||||
function Components.reject(name)
|
||||
local ok, err = Components.try(name)
|
||||
|
||||
if not ok then error(err, 2) end
|
||||
|
||||
return Components.__REJECT_PREFIX..name
|
||||
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
|
||||
-- @boolean acceptRejected Whether to accept names prefixed with the Reject Prefix.
|
||||
-- @treturn boolean
|
||||
-- @treturn Component or error string
|
||||
-- @treturn true if acceptRejected was true and the name had the Reject Prefix, false otherwise.
|
||||
function Components.try(name, acceptRejected)
|
||||
function Components.try(name)
|
||||
if type(name) ~= "string" then
|
||||
return false, "ComponentsClass name is expected to be a string, got "..type(name)..")"
|
||||
end
|
||||
|
||||
local rejected = false
|
||||
if acceptRejected then
|
||||
local prefix
|
||||
prefix, name = string.match(name, Components.__REJECT_MATCH)
|
||||
|
||||
rejected = prefix ~= "" and name
|
||||
end
|
||||
|
||||
local value = rawget(Components, name)
|
||||
if not value then
|
||||
return false, "ComponentClass '"..name.."' does not exist / was not registered"
|
||||
end
|
||||
|
||||
return true, value, rejected
|
||||
return true, value
|
||||
end
|
||||
|
||||
--- Returns the ComponentClass with the specified name
|
||||
|
|
|
@ -6,16 +6,8 @@ local PATH = (...):gsub('%.[^%.]+$', '')
|
|||
|
||||
local Components = require(PATH..".components")
|
||||
local Type = require(PATH..".type")
|
||||
local Utils = require(PATH..".utils")
|
||||
|
||||
-- Initialize built-in Components (as soon as possible)
|
||||
local Builtins = require(PATH..".builtins.init") --luacheck: ignore
|
||||
-- Builtins is unused but the require already registers the Components
|
||||
|
||||
local Entity = {
|
||||
SERIALIZE_BY_DEFAULT = true,
|
||||
}
|
||||
|
||||
local Entity = {}
|
||||
Entity.__mt = {
|
||||
__index = Entity,
|
||||
}
|
||||
|
@ -25,11 +17,12 @@ Entity.__mt = {
|
|||
-- @treturn Entity A new Entity
|
||||
function Entity.new(world)
|
||||
if (world ~= nil and not Type.isWorld(world)) then
|
||||
Utils.error(2, "bad argument #1 to 'Entity.new' (world/nil expected, got %s)", type(world))
|
||||
error("bad argument #1 to 'Entity.new' (world/nil expected, got "..type(world)..")", 2)
|
||||
end
|
||||
|
||||
local e = setmetatable({
|
||||
__world = nil,
|
||||
__components = {},
|
||||
|
||||
__isEntity = true,
|
||||
}, Entity.__mt)
|
||||
|
@ -38,86 +31,23 @@ function Entity.new(world)
|
|||
world:addEntity(e)
|
||||
end
|
||||
|
||||
if Entity.SERIALIZE_BY_DEFAULT then
|
||||
e:give("serializable")
|
||||
end
|
||||
|
||||
return e
|
||||
end
|
||||
|
||||
local function createComponent(e, name, componentClass, ...)
|
||||
local component = componentClass:__initialize(e, ...)
|
||||
local hadComponent = not not e[name]
|
||||
|
||||
if hadComponent then
|
||||
e[name]:removed(true)
|
||||
end
|
||||
local function give(e, name, componentClass, ...)
|
||||
local component = componentClass:__initialize(...)
|
||||
|
||||
e[name] = component
|
||||
e.__components[name] = component
|
||||
|
||||
if not hadComponent then
|
||||
e:__dirty()
|
||||
end
|
||||
e:__dirty()
|
||||
end
|
||||
|
||||
local function deserializeComponent(e, name, componentData)
|
||||
local componentClass = Components[name]
|
||||
local hadComponent = not not e[name]
|
||||
local function remove(e, name, componentClass)
|
||||
e[name] = nil
|
||||
e.__components[name] = nil
|
||||
|
||||
if hadComponent then
|
||||
e[name]:removed(true)
|
||||
end
|
||||
|
||||
local component = componentClass:__new(e)
|
||||
component:deserialize(componentData)
|
||||
|
||||
e[name] = component
|
||||
|
||||
if not hadComponent then
|
||||
e:__dirty()
|
||||
end
|
||||
end
|
||||
|
||||
local function giveComponent(e, ensure, name, ...)
|
||||
local component
|
||||
if Type.isComponent(name) then
|
||||
component = name
|
||||
name = component:getName()
|
||||
end
|
||||
|
||||
if ensure and e[name] then
|
||||
return e
|
||||
end
|
||||
|
||||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
Utils.error(3, "bad argument #1 to 'Entity:%s' (%s)", ensure and 'ensure' or 'give', componentClass)
|
||||
end
|
||||
|
||||
if component then
|
||||
local data = component:deserialize()
|
||||
if data == nil then
|
||||
Utils.error(3, "bad argument #1 to 'Entity:$s' (Component '%s' couldn't be deserialized)", ensure and 'ensure' or 'give', name)
|
||||
end
|
||||
|
||||
deserializeComponent(e, name, data)
|
||||
else
|
||||
createComponent(e, name, componentClass, ...)
|
||||
end
|
||||
|
||||
return e
|
||||
end
|
||||
|
||||
|
||||
local function removeComponent(e, name)
|
||||
if e[name] then
|
||||
e[name]:removed(false)
|
||||
|
||||
e[name] = nil
|
||||
|
||||
e:__dirty()
|
||||
end
|
||||
e:__dirty()
|
||||
end
|
||||
|
||||
--- Gives an Entity a Component.
|
||||
|
@ -126,7 +56,15 @@ end
|
|||
-- @param ... additional arguments to pass to the Component's populate function
|
||||
-- @treturn Entity self
|
||||
function Entity:give(name, ...)
|
||||
return giveComponent(self, false, name, ...)
|
||||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
give(self, name, componentClass, ...)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Ensures an Entity to have a Component.
|
||||
|
@ -135,7 +73,19 @@ end
|
|||
-- @param ... additional arguments to pass to the Component's populate function
|
||||
-- @treturn Entity self
|
||||
function Entity:ensure(name, ...)
|
||||
return giveComponent(self, true, name, ...)
|
||||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
if self[name] then
|
||||
return self
|
||||
end
|
||||
|
||||
give(self, name, componentClass, ...)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a Component from an Entity.
|
||||
|
@ -145,10 +95,10 @@ function Entity:remove(name)
|
|||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
Utils.error(2, "bad argument #1 to 'Entity:remove' (%s)", componentClass)
|
||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
removeComponent(self, name)
|
||||
remove(self, name, componentClass)
|
||||
|
||||
return self
|
||||
end
|
||||
|
@ -159,7 +109,7 @@ end
|
|||
-- @treturn Entity self
|
||||
function Entity:assemble(assemblage, ...)
|
||||
if type(assemblage) ~= "function" then
|
||||
Utils.error(2, "bad argument #1 to 'Entity:assemble' (function expected, got %s)", type(assemblage))
|
||||
error("bad argument #1 to 'Entity:assemble' (function expected, got "..type(assemblage)..")")
|
||||
end
|
||||
|
||||
assemblage(self, ...)
|
||||
|
@ -195,7 +145,7 @@ function Entity:has(name)
|
|||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
Utils.error(2, "bad argument #1 to 'Entity:has' (%s)", componentClass)
|
||||
error("bad argument #1 to 'Entity:has' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
return self[name] and true or false
|
||||
|
@ -208,7 +158,7 @@ function Entity:get(name)
|
|||
local ok, componentClass = Components.try(name)
|
||||
|
||||
if not ok then
|
||||
Utils.error(2, "bad argument #1 to 'Entity:get' (%s)", componentClass)
|
||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
||||
end
|
||||
|
||||
return self[name]
|
||||
|
@ -218,13 +168,8 @@ end
|
|||
-- Warning: Do not modify this table.
|
||||
-- Use Entity:give/ensure/remove instead
|
||||
-- @treturn table Table of all Components the Entity has
|
||||
function Entity:getComponents(output)
|
||||
output = output or {}
|
||||
local components = Utils.shallowCopy(self, output)
|
||||
components.__world = nil
|
||||
components.__isEntity = nil
|
||||
|
||||
return components
|
||||
function Entity:getComponents()
|
||||
return self.__components
|
||||
end
|
||||
|
||||
--- Returns true if the Entity is in a World.
|
||||
|
@ -239,17 +184,11 @@ function Entity:getWorld()
|
|||
return self.__world
|
||||
end
|
||||
|
||||
function Entity:serialize(ignoreKey)
|
||||
function Entity:serialize()
|
||||
local data = {}
|
||||
|
||||
for name, component in pairs(self) do
|
||||
-- The key component needs to be treated separately.
|
||||
if name == "key" and component.__name == "key" then
|
||||
if not ignoreKey then
|
||||
data.key = component.value
|
||||
end
|
||||
--We only care about components that were properly given to the entity
|
||||
elseif Type.isComponent(component) and (component.__name == name) then
|
||||
for _, component in pairs(self.__components) do
|
||||
if component.__name then
|
||||
local componentData = component:serialize()
|
||||
|
||||
if componentData ~= nil then
|
||||
|
@ -267,10 +206,18 @@ function Entity:deserialize(data)
|
|||
local componentData = data[i]
|
||||
|
||||
if (not Components.has(componentData.__name)) then
|
||||
Utils.error(2, "bad argument #1 to 'Entity:deserialize' (ComponentClass '%s' wasn't yet loaded)", tostring(componentData.__name)) -- luacheck: ignore
|
||||
error("bad argument #1 to 'Entity:deserialize' (ComponentClass '"..tostring(componentData.__name).."' wasn't yet loaded)") -- luacheck: ignore
|
||||
end
|
||||
|
||||
deserializeComponent(self, componentData.__name, componentData)
|
||||
local componentClass = Components[componentData.__name]
|
||||
|
||||
local component = componentClass:__new()
|
||||
component:deserialize(componentData)
|
||||
|
||||
self[componentData.__name] = component
|
||||
self.__components[componentData.__name] = component
|
||||
|
||||
self:__dirty()
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,199 +0,0 @@
|
|||
--- 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 Utils = require(PATH..".utils")
|
||||
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 (errorLevel, name, def, onComponent)
|
||||
local filter = "World:query filter"
|
||||
if name then
|
||||
filter = ("filter '%s'"):format(name)
|
||||
end
|
||||
|
||||
if type(def) ~= 'table' then
|
||||
Utils.error(3 + errorLevel, "invalid component list for %s (table expected, got %s)", filter, type(def))
|
||||
end
|
||||
|
||||
if not onComponent and def.constructor and not Type.isCallable(def.constructor) then
|
||||
Utils.error(3 + errorLevel, "invalid pool constructor for %s (callable expected, got %s)", filter, type(def.constructor))
|
||||
end
|
||||
|
||||
for n, component in ipairs(def) do
|
||||
local ok, err, reject = Components.try(component, true)
|
||||
|
||||
if not ok then
|
||||
Utils.error(3 + errorLevel, "invalid component for %s at position #%d (%s)", filter, n, err)
|
||||
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 filter = {}
|
||||
|
||||
Filter.validate(1, name, def, function (component, reject)
|
||||
if reject then
|
||||
table.insert(filter, reject)
|
||||
table.insert(filter, false)
|
||||
else
|
||||
table.insert(filter, component)
|
||||
table.insert(filter, true)
|
||||
end
|
||||
end)
|
||||
|
||||
return filter
|
||||
end
|
||||
|
||||
function Filter.match (e, filter)
|
||||
for i=#filter, 2, -2 do
|
||||
local match = filter[i - 0]
|
||||
local name = filter[i - 1]
|
||||
|
||||
if (not e[name]) == match then return false end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local REQUIRED_METHODS = {"add", "remove", "has", "clear"}
|
||||
local VALID_POOL_TYPES = {table=true, userdata=true, lightuserdata=true, cdata=true}
|
||||
|
||||
function Filter.isValidPool (name, pool)
|
||||
local poolType = type(pool)
|
||||
--Check that pool is not nil
|
||||
if not VALID_POOL_TYPES[poolType] then
|
||||
Utils.error(3, "invalid value returned by pool '%s' constructor (table expected, got %s).", name, type(pool))
|
||||
end
|
||||
|
||||
--Check if methods are callables
|
||||
for _, method in ipairs(REQUIRED_METHODS) do
|
||||
if not Type.isCallable(pool[method]) then
|
||||
Utils.error(3, "invalid :%s method on pool '%s' (callable expected, got %s).", method, name, type(pool[method]))
|
||||
end
|
||||
end
|
||||
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 pool
|
||||
|
||||
if def.constructor then
|
||||
pool = def.constructor(def)
|
||||
Filter.isValidPool(name, pool)
|
||||
else
|
||||
pool = List()
|
||||
end
|
||||
|
||||
local filter = Filter.parse(name, def)
|
||||
|
||||
local filter = setmetatable({
|
||||
pool = pool,
|
||||
|
||||
__filter = filter,
|
||||
__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)
|
||||
return Filter.match(e, self.__filter)
|
||||
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', 'onAdded' or 'onRemoved'
|
||||
-- Object may not be the string 'size'
|
||||
-- @param obj Object to add
|
||||
-- @treturn List self
|
||||
function List:add(obj)
|
||||
|
@ -26,7 +26,6 @@ function List:add(obj)
|
|||
self[obj] = size
|
||||
self.size = size
|
||||
|
||||
if self.onAdded then self:onAdded(obj) end
|
||||
return self
|
||||
end
|
||||
|
||||
|
@ -52,7 +51,6 @@ function List:remove(obj)
|
|||
self[obj] = nil
|
||||
self.size = size - 1
|
||||
|
||||
if self.onRemoved then self:onRemoved(obj) end
|
||||
return self
|
||||
end
|
||||
|
||||
|
@ -96,30 +94,6 @@ function List:indexOf(obj)
|
|||
return self[obj]
|
||||
end
|
||||
|
||||
--- Sorts the List in place, using the order function.
|
||||
-- The order function is passed to table.sort internally so documentation on table.sort can be used as reference.
|
||||
-- @param order Function that takes two Entities (a and b) and returns true if a should go before than b.
|
||||
-- @treturn List self
|
||||
function List:sort(order)
|
||||
table.sort(self, order)
|
||||
|
||||
for key, obj in ipairs(self) do
|
||||
self[obj] = key
|
||||
end
|
||||
|
||||
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()
|
||||
|
|
111
concord/pool.lua
Normal file
111
concord/pool.lua
Normal file
|
@ -0,0 +1,111 @@
|
|||
--- 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, 1, -1 do
|
||||
local component = self.__filter[i].__name
|
||||
|
||||
if not e[component] 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,8 +5,9 @@
|
|||
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
local Filter = require(PATH..".filter")
|
||||
local Pool = require(PATH..".pool")
|
||||
local Utils = require(PATH..".utils")
|
||||
local Components = require(PATH..".components")
|
||||
|
||||
local System = {
|
||||
ENABLE_OPTIMIZATION = true,
|
||||
|
@ -18,7 +19,7 @@ System.mt = {
|
|||
local system = setmetatable({
|
||||
__enabled = true,
|
||||
|
||||
__filters = {},
|
||||
__pools = {},
|
||||
__world = world,
|
||||
|
||||
__isSystem = true,
|
||||
|
@ -32,11 +33,11 @@ System.mt = {
|
|||
Utils.shallowCopy(systemClass, system)
|
||||
end
|
||||
|
||||
for name, def in pairs(systemClass.__definition) do
|
||||
local filter, pool = Filter(name, Utils.shallowCopy(def, {}))
|
||||
for name, filter in pairs(systemClass.__filter) do
|
||||
local pool = Pool(name, filter)
|
||||
|
||||
system[name] = pool
|
||||
table.insert(system.__filters, filter)
|
||||
system.__pools[#system.__pools + 1] = pool
|
||||
end
|
||||
|
||||
system:init(world)
|
||||
|
@ -44,23 +45,44 @@ System.mt = {
|
|||
return system
|
||||
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 table filters A table containing filters (name = {components...})
|
||||
-- @treturn System A new SystemClass
|
||||
function System.new(definition)
|
||||
definition = definition or {}
|
||||
|
||||
for name, def in pairs(definition) do
|
||||
if type(name) ~= 'string' then
|
||||
Utils.error(2, "invalid name for filter (string key expected, got %s)", type(name))
|
||||
end
|
||||
|
||||
Filter.validate(0, name, def)
|
||||
end
|
||||
|
||||
function System.new(filters)
|
||||
local systemClass = setmetatable({
|
||||
__definition = definition,
|
||||
__filter = validateFilters(filters),
|
||||
|
||||
__name = nil,
|
||||
__isSystemClass = true,
|
||||
}, System.mt)
|
||||
systemClass.__index = systemClass
|
||||
|
@ -79,8 +101,8 @@ end
|
|||
-- @param e The Entity to check
|
||||
-- @treturn System self
|
||||
function System:__evaluate(e)
|
||||
for _, filter in ipairs(self.__filters) do
|
||||
filter:evaluate(e)
|
||||
for _, pool in ipairs(self.__pools) do
|
||||
pool:evaluate(e)
|
||||
end
|
||||
|
||||
return self
|
||||
|
@ -90,9 +112,9 @@ end
|
|||
-- @param e The Entity to remove
|
||||
-- @treturn System self
|
||||
function System:__remove(e)
|
||||
for _, filter in ipairs(self.__filters) do
|
||||
if filter:has(e) then
|
||||
filter:remove(e)
|
||||
for _, pool in ipairs(self.__pools) do
|
||||
if pool:has(e) then
|
||||
pool:remove(e)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -102,8 +124,8 @@ end
|
|||
-- Internal: Clears all Entities from the System.
|
||||
-- @treturn System self
|
||||
function System:__clear()
|
||||
for _, filter in ipairs(self.__filters) do
|
||||
filter:clear()
|
||||
for i = 1, #self.__pools do
|
||||
self.__pools[i]:clear()
|
||||
end
|
||||
|
||||
return self
|
||||
|
@ -136,6 +158,18 @@ function System:getWorld()
|
|||
return self.__world
|
||||
end
|
||||
|
||||
--- Returns true if the System has a name.
|
||||
-- @treturn boolean
|
||||
function System:hasName()
|
||||
return self.__name and true or false
|
||||
end
|
||||
|
||||
--- Returns the name of the System.
|
||||
-- @treturn string
|
||||
function System:getName()
|
||||
return self.__name
|
||||
end
|
||||
|
||||
--- Callbacks
|
||||
-- @section Callbacks
|
||||
|
||||
|
|
|
@ -3,15 +3,6 @@
|
|||
|
||||
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
|
||||
|
@ -54,11 +45,4 @@ 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
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
|
||||
local Utils = {}
|
||||
|
||||
function Utils.error(level, str, ...)
|
||||
error(string.format(str, ...), level + 1)
|
||||
end
|
||||
|
||||
--- Does a shallow copy of a table and appends it to a target table.
|
||||
-- @param orig Table to copy
|
||||
-- @param target Table to append to
|
||||
|
@ -25,43 +21,39 @@ end
|
|||
-- @param namespace A table that will hold the required files
|
||||
-- @treturn table The namespace table
|
||||
function Utils.loadNamespace(pathOrFiles, namespace)
|
||||
if type(pathOrFiles) ~= "string" and type(pathOrFiles) ~= "table" then
|
||||
Utils.error(2, "bad argument #1 to 'loadNamespace' (string/table of strings expected, got %s)", type(pathOrFiles))
|
||||
if (type(pathOrFiles) ~= "string" and type(pathOrFiles) ~= "table") then
|
||||
error("bad argument #1 to 'loadNamespace' (string/table of strings expected, got "..type(pathOrFiles)..")", 2)
|
||||
end
|
||||
|
||||
if type(pathOrFiles) == "string" then
|
||||
if (type(pathOrFiles) == "string") then
|
||||
local info = love.filesystem.getInfo(pathOrFiles) -- luacheck: ignore
|
||||
if info == nil or info.type ~= "directory" then
|
||||
Utils.error(2, "bad argument #1 to 'loadNamespace' (path '%s' not found)", pathOrFiles)
|
||||
if (info == nil or info.type ~= "directory") then
|
||||
error("bad argument #1 to 'loadNamespace' (path '"..pathOrFiles.."' not found)", 2)
|
||||
end
|
||||
|
||||
local files = love.filesystem.getDirectoryItems(pathOrFiles)
|
||||
|
||||
for _, file in ipairs(files) do
|
||||
local isFile = love.filesystem.getInfo(pathOrFiles .. "/" .. file).type == "file"
|
||||
local name = file:sub(1, #file - 4)
|
||||
local path = pathOrFiles.."."..name
|
||||
|
||||
if isFile and string.match(file, '%.lua$') ~= nil then
|
||||
local name = file:sub(1, #file - 4)
|
||||
local path = pathOrFiles.."."..name
|
||||
|
||||
local value = require(path:gsub("%/", "."))
|
||||
if namespace then namespace[name] = value end
|
||||
end
|
||||
local value = require(path)
|
||||
if namespace then namespace[name] = value end
|
||||
end
|
||||
elseif type(pathOrFiles) == "table" then
|
||||
elseif (type(pathOrFiles == "table")) then
|
||||
for _, path in ipairs(pathOrFiles) do
|
||||
if type(path) ~= "string" then
|
||||
Utils.error(2, "bad argument #2 to 'loadNamespace' (string/table of strings expected, got table containing %s)", type(path)) -- luacheck: ignore
|
||||
if (type(path) ~= "string") then
|
||||
error("bad argument #2 to 'loadNamespace' (string/table of strings expected, got table containing "..type(path)..")", 2) -- luacheck: ignore
|
||||
end
|
||||
|
||||
local name = path
|
||||
|
||||
local dotIndex, slashIndex = path:match("^.*()%."), path:match("^.*()%/")
|
||||
if dotIndex or slashIndex then
|
||||
if (dotIndex or slashIndex) then
|
||||
name = path:sub((dotIndex or slashIndex) + 1)
|
||||
end
|
||||
|
||||
local value = require(path:gsub("%/", "."))
|
||||
local value = require(path)
|
||||
if namespace then namespace[name] = value end
|
||||
end
|
||||
end
|
||||
|
@ -69,4 +61,4 @@ function Utils.loadNamespace(pathOrFiles, namespace)
|
|||
return namespace
|
||||
end
|
||||
|
||||
return Utils
|
||||
return Utils
|
|
@ -6,12 +6,10 @@
|
|||
|
||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||
|
||||
local Filter = require(PATH..".filter")
|
||||
local Entity = require(PATH..".entity")
|
||||
local Components = require(PATH..".components")
|
||||
local Type = require(PATH..".type")
|
||||
local List = require(PATH..".list")
|
||||
local Utils = require(PATH..".utils")
|
||||
local Entity = require(PATH..".entity")
|
||||
local Type = require(PATH..".type")
|
||||
local List = require(PATH..".list")
|
||||
local Utils = require(PATH..".utils")
|
||||
|
||||
local World = {
|
||||
ENABLE_OPTIMIZATION = true,
|
||||
|
@ -20,12 +18,6 @@ World.__mt = {
|
|||
__index = World,
|
||||
}
|
||||
|
||||
local defaultGenerator = function (state)
|
||||
local current = state
|
||||
state = state +1
|
||||
return string.format("%d", current), state
|
||||
end
|
||||
|
||||
--- Creates a new World.
|
||||
-- @treturn World The new World
|
||||
function World.new()
|
||||
|
@ -36,24 +28,14 @@ function World.new()
|
|||
__events = {},
|
||||
__emitSDepth = 0,
|
||||
|
||||
__resources = {},
|
||||
|
||||
__hash = {
|
||||
state = -2^53,
|
||||
generator = defaultGenerator,
|
||||
keys = {},
|
||||
entities = {}
|
||||
},
|
||||
|
||||
__added = List(), __backAdded = List(),
|
||||
__removed = List(), __backRemoved = List(),
|
||||
__dirty = List(), __backDirty = List(),
|
||||
|
||||
__systemLookup = {},
|
||||
|
||||
__name = nil,
|
||||
__isWorld = true,
|
||||
|
||||
__ignoreEmits = false
|
||||
}, World.__mt)
|
||||
|
||||
-- Optimization: We deep copy the World class into our instance of a world.
|
||||
|
@ -71,7 +53,7 @@ end
|
|||
-- @treturn World self
|
||||
function World:addEntity(e)
|
||||
if not Type.isEntity(e) then
|
||||
Utils.error(2, "bad argument #1 to 'World:addEntity' (Entity expected, got %s)", type(e))
|
||||
error("bad argument #1 to 'World:addEntity' (Entity expected, got "..type(e)..")", 2)
|
||||
end
|
||||
|
||||
if e.__world then
|
||||
|
@ -84,47 +66,12 @@ function World:addEntity(e)
|
|||
return self
|
||||
end
|
||||
|
||||
--- Creates a new Entity and adds it to the World.
|
||||
-- @treturn Entity e the new Entity
|
||||
function World:newEntity()
|
||||
return Entity(self)
|
||||
end
|
||||
|
||||
function World:query(def, onMatch)
|
||||
local filter = Filter.parse(nil, def)
|
||||
|
||||
local list = nil
|
||||
if not Type.isCallable(onMatch) then
|
||||
list = type(onMatch) == "table" and onMatch or {}
|
||||
end
|
||||
|
||||
for _, e in ipairs(self.__entities) do
|
||||
if Filter.match(e, filter) then
|
||||
if list then
|
||||
table.insert(list, e)
|
||||
else
|
||||
onMatch(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
--- Removes an Entity from the World.
|
||||
-- @tparam Entity e Entity to remove
|
||||
-- @treturn World self
|
||||
function World:removeEntity(e)
|
||||
if not Type.isEntity(e) then
|
||||
Utils.error(2, "bad argument #1 to 'World:removeEntity' (Entity expected, got %s)", type(e))
|
||||
end
|
||||
|
||||
if e.__world ~= self then
|
||||
error("trying to remove an Entity from a World it doesn't belong to", 2)
|
||||
end
|
||||
|
||||
if e:has("key") then
|
||||
e:remove("key")
|
||||
error("bad argument #1 to 'World:removeEntity' (Entity expected, got "..type(e)..")", 2)
|
||||
end
|
||||
|
||||
self.__removed:add(e)
|
||||
|
@ -261,7 +208,7 @@ function World:addSystem(systemClass)
|
|||
local ok, err = tryAddSystem(self, systemClass)
|
||||
|
||||
if not ok then
|
||||
Utils.error(2, "bad argument #1 to 'World:addSystem' (%s)", err)
|
||||
error("bad argument #1 to 'World:addSystem' ("..err..")", 2)
|
||||
end
|
||||
|
||||
return self
|
||||
|
@ -279,7 +226,7 @@ function World:addSystems(...)
|
|||
|
||||
local ok, err = tryAddSystem(self, systemClass)
|
||||
if not ok then
|
||||
Utils.error(2, "bad argument #%d to 'World:addSystems' (%s)", i, err)
|
||||
error("bad argument #"..i.." to 'World:addSystems' ("..err..")", 2)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -291,7 +238,7 @@ end
|
|||
-- @treturn boolean
|
||||
function World:hasSystem(systemClass)
|
||||
if not Type.isSystemClass(systemClass) then
|
||||
Utils.error(2, "bad argument #1 to 'World:hasSystem' (SystemClass expected, got %s)", type(systemClass))
|
||||
error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2)
|
||||
end
|
||||
|
||||
return self.__systemLookup[systemClass] and true or false
|
||||
|
@ -302,7 +249,7 @@ end
|
|||
-- @treturn System System to get
|
||||
function World:getSystem(systemClass)
|
||||
if not Type.isSystemClass(systemClass) then
|
||||
Utils.error(2, "bad argument #1 to 'World:getSystem' (SystemClass expected, got %s)", type(systemClass))
|
||||
error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2)
|
||||
end
|
||||
|
||||
return self.__systemLookup[systemClass]
|
||||
|
@ -315,21 +262,14 @@ end
|
|||
-- @treturn World self
|
||||
function World:emit(functionName, ...)
|
||||
if not functionName or type(functionName) ~= "string" then
|
||||
Utils.error(2, "bad argument #1 to 'World:emit' (String expected, got %s)", type(functionName))
|
||||
error("bad argument #1 to 'World:emit' (String expected, got "..type(functionName)..")")
|
||||
end
|
||||
|
||||
local shouldFlush = self.__emitSDepth == 0
|
||||
|
||||
self.__emitSDepth = self.__emitSDepth + 1
|
||||
|
||||
local listeners = self.__events[functionName]
|
||||
|
||||
if not self.__ignoreEmits and Type.isCallable(self.beforeEmit) then
|
||||
self.__ignoreEmits = true
|
||||
local preventDefaults = self:beforeEmit(functionName, listeners, ...)
|
||||
self.__ignoreEmits = false
|
||||
if preventDefaults then return end
|
||||
end
|
||||
local listeners = self.__events[functionName]
|
||||
|
||||
if listeners then
|
||||
for i = 1, #listeners do
|
||||
|
@ -345,12 +285,6 @@ function World:emit(functionName, ...)
|
|||
end
|
||||
end
|
||||
|
||||
if not self.__ignoreEmits and Type.isCallable(self.afterEmit) then
|
||||
self.__ignoreEmits = true
|
||||
self:afterEmit(functionName, listeners, ...)
|
||||
self.__ignoreEmits = false
|
||||
end
|
||||
|
||||
self.__emitSDepth = self.__emitSDepth - 1
|
||||
|
||||
return self
|
||||
|
@ -382,103 +316,49 @@ function World:getSystems()
|
|||
return self.__systems
|
||||
end
|
||||
|
||||
function World:serialize(ignoreKeys)
|
||||
function World:serialize()
|
||||
self:__flush()
|
||||
|
||||
local data = { generator = self.__hash.state }
|
||||
local data = {}
|
||||
|
||||
for i = 1, self.__entities.size do
|
||||
local entity = self.__entities[i]
|
||||
|
||||
if entity.serializable then
|
||||
local entityData = entity:serialize(ignoreKeys)
|
||||
table.insert(data, entityData)
|
||||
end
|
||||
local entityData = entity:serialize()
|
||||
|
||||
data[i] = entityData
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
function World:deserialize(data, startClean, ignoreGenerator)
|
||||
if startClean then
|
||||
function World:deserialize(data, append)
|
||||
if (not append) then
|
||||
self:clear()
|
||||
end
|
||||
|
||||
if (not ignoreGenerator) then
|
||||
self.__hash.state = data.generator
|
||||
end
|
||||
|
||||
local entities = {}
|
||||
|
||||
for i = 1, #data do
|
||||
local entity = Entity(self)
|
||||
local entityData = data[i]
|
||||
|
||||
if data[i].key then
|
||||
local component = Components.key:__new(entity)
|
||||
component:deserialize(data[i].key)
|
||||
entity.key = component
|
||||
local entity = Entity()
|
||||
entity:deserialize(entityData)
|
||||
|
||||
entity:__dirty()
|
||||
end
|
||||
|
||||
entities[i] = entity
|
||||
end
|
||||
|
||||
for i = 1, #data do
|
||||
entities[i]:deserialize(data[i])
|
||||
self:addEntity(entity)
|
||||
end
|
||||
|
||||
self:__flush()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function World:setKeyGenerator(generator, initialState)
|
||||
if not Type.isCallable(generator) then
|
||||
Utils.error(2, "bad argument #1 to 'World:setKeyGenerator' (function expected, got %s)", type(generator))
|
||||
end
|
||||
|
||||
self.__hash.generator = generator
|
||||
self.__hash.state = initialState
|
||||
|
||||
return self
|
||||
--- Returns true if the World has a name.
|
||||
-- @treturn boolean
|
||||
function World:hasName()
|
||||
return self.__name and true or false
|
||||
end
|
||||
|
||||
function World:__clearKey(e)
|
||||
local key = self.__hash.keys[e]
|
||||
|
||||
if key then
|
||||
self.__hash.keys[e] = nil
|
||||
self.__hash.entities[key] = nil
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function World:__assignKey(e, key)
|
||||
local hash = self.__hash
|
||||
|
||||
if not key then
|
||||
key = hash.keys[e]
|
||||
if key then return key end
|
||||
|
||||
key, hash.state = hash.generator(hash.state)
|
||||
end
|
||||
|
||||
if hash.entities[key] and hash.entities[key] ~= e then
|
||||
Utils.error(4, "Trying to assign a key that is already taken (key: '%s').", key)
|
||||
elseif hash.keys[e] and hash.keys[e] ~= key then
|
||||
Utils.error(4, "Trying to assign more than one key to an Entity. (old: '%s', new: '%s')", hash.keys[e], key)
|
||||
end
|
||||
|
||||
hash.keys[e] = key
|
||||
hash.entities[key] = e
|
||||
|
||||
return key
|
||||
end
|
||||
|
||||
function World:getEntityByKey(key)
|
||||
return self.__hash.entities[key]
|
||||
--- Returns the name of the World.
|
||||
-- @treturn string
|
||||
function World:getName()
|
||||
return self.__name
|
||||
end
|
||||
|
||||
--- Callback for when an Entity is added to the World.
|
||||
|
@ -491,25 +371,8 @@ end
|
|||
function World:onEntityRemoved(e) -- luacheck: ignore
|
||||
end
|
||||
|
||||
--- Sets a named resource in the world
|
||||
-- @string name Name of the resource
|
||||
-- @tparam Any resource Resource to set
|
||||
-- @treturn World self
|
||||
function World:setResource(name, resource)
|
||||
self.__resources[name] = resource
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets a named resource from the world
|
||||
-- @string name Name of the resource
|
||||
-- @treturn Any resource
|
||||
function World:getResource(name)
|
||||
return self.__resources[name]
|
||||
end
|
||||
|
||||
return setmetatable(World, {
|
||||
__call = function(_, ...)
|
||||
---@diagnostic disable-next-line: redundant-parameter
|
||||
return World.new(...)
|
||||
end,
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue