mirror of
https://github.com/Keyslam-Group/Concord.git
synced 2025-09-10 16:17:48 -04:00
Compare commits
49 commits
Author | SHA1 | Date | |
---|---|---|---|
|
848652f688 | ||
|
30b21c4c25 | ||
|
9f187f12e5 | ||
|
6575686b3b | ||
|
1aaf501401 | ||
|
f9da8dbe92 | ||
|
7b8f7b2f0a | ||
|
5f4b3b97da | ||
|
2386547caa | ||
|
1e4132be21 | ||
|
429a448ab6 | ||
|
9bccd05019 | ||
|
cf05cfc972 | ||
|
16c77c6a66 | ||
|
61720312cb | ||
|
8e1b14d53b | ||
|
bdfe2523b0 | ||
|
41fcfac6af | ||
|
a55efd042a | ||
|
3d195c790f | ||
|
a4ae392341 | ||
|
cc0fd1614c | ||
|
892f4d4700 | ||
|
743d662ef9 | ||
|
07bd5d0f28 | ||
|
c4594da19d | ||
|
50249d5ad3 | ||
|
695cc2dfe3 | ||
|
89eab3fb72 | ||
|
a45d89457b | ||
|
940870318d | ||
|
821b36c903 | ||
|
7a4bfaf33c | ||
|
2acb1458f2 | ||
|
edc1d2fdbc | ||
|
4313dc7856 | ||
|
9c22986501 | ||
|
73177f4048 | ||
|
6ce714ce14 | ||
|
e141a6183b | ||
|
6329e09138 | ||
|
cdf425d301 | ||
|
c7625ad376 | ||
|
65aae3c2ba | ||
|
297b30aa50 | ||
|
9aaff0fbcc | ||
|
b701493a27 | ||
|
b53f950e3e | ||
|
fd558dd3fe |
17 changed files with 735 additions and 300 deletions
38
.github/workflows/doc.yml
vendored
Normal file
38
.github/workflows/doc.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
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
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -38,3 +38,6 @@ luac.out
|
||||||
*.i*86
|
*.i*86
|
||||||
*.x86_64
|
*.x86_64
|
||||||
*.hex
|
*.hex
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
|
@ -1 +1,2 @@
|
||||||
|
---@diagnostic disable: lowercase-global
|
||||||
std="love+luajit"
|
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.
|
This readme will explain how to use Concord.
|
||||||
|
|
||||||
Additionally all of Concord is documented using the LDoc format.
|
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://tjakka5.github.io/Concord/).
|
Auto generated docs for Concord can be found in `docs` folder, or on the [GitHub page](https://keyslam-group.github.io/Concord/).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -133,16 +133,21 @@ 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.
|
Since you'll have lots of Components and Systems in your game Concord makes it a bit easier to load things in.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
-- 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)
|
|
||||||
|
|
||||||
print(Systems.systemName)
|
|
||||||
|
|
||||||
-- Loads all files in the directory. Components automatically register into Concord.components, so loading them into a namespace isn't necessary.
|
-- 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")
|
Concord.utils.loadNamespace("path/to/components")
|
||||||
|
|
||||||
print(Concord.components.componentName)
|
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
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Method chaining
|
#### Method chaining
|
||||||
|
@ -170,7 +175,7 @@ When defining a ComponentClass you need to pass in a name and usually a `populat
|
||||||
-- Create the position class with a populate function
|
-- Create the position class with a populate function
|
||||||
-- The component variable is the actual Component given to an Entity
|
-- 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
|
-- 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.x = x or 0
|
||||||
component.y = y or 0
|
component.y = y or 0
|
||||||
end)
|
end)
|
||||||
|
@ -266,10 +271,10 @@ Systems can have multiple pools.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
-- Create a System
|
-- 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
|
-- Create a System with multiple pools
|
||||||
local mySystemClass = Concord.system(
|
local mySystemClass = Concord.system({
|
||||||
pool = { -- This pool will be named 'pool'
|
pool = { -- This pool will be named 'pool'
|
||||||
"position",
|
"position",
|
||||||
"velocity",
|
"velocity",
|
||||||
|
@ -278,7 +283,7 @@ local mySystemClass = Concord.system(
|
||||||
"health",
|
"health",
|
||||||
"damageable",
|
"damageable",
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
|
@ -294,14 +299,14 @@ end
|
||||||
-- Defining a function
|
-- Defining a function
|
||||||
function mySystemClass:update(dt)
|
function mySystemClass:update(dt)
|
||||||
-- Iterate over all entities in the Pool
|
-- Iterate over all entities in the Pool
|
||||||
for _, e in ipairs(self.pool)
|
for _, e in ipairs(self.pool) do
|
||||||
-- Do something with the Components
|
-- Do something with the Components
|
||||||
e.position.x = e.position.x + e.velocity.x * dt
|
e.position.x = e.position.x + e.velocity.x * dt
|
||||||
e.position.y = e.position.y + e.velocity.y * dt
|
e.position.y = e.position.y + e.velocity.y * dt
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Iterate over all entities in the second Pool
|
-- Iterate over all entities in the second Pool
|
||||||
for _, e in ipairs(self.secondPool)
|
for _, e in ipairs(self.secondPool) do
|
||||||
-- Do something
|
-- Do something
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
6
concord/builtins/init.lua
Normal file
6
concord/builtins/init.lua
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
local PATH = (...):gsub("(%.init)$", "")
|
||||||
|
|
||||||
|
return {
|
||||||
|
serializable = require(PATH..".serializable"),
|
||||||
|
key = require(PATH..".key"),
|
||||||
|
}
|
41
concord/builtins/key.lua
Normal file
41
concord/builtins/key.lua
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
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
|
12
concord/builtins/serializable.lua
Normal file
12
concord/builtins/serializable.lua
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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,15 +16,19 @@ Component.__mt = {
|
||||||
-- @treturn Component A new ComponentClass
|
-- @treturn Component A new ComponentClass
|
||||||
function Component.new(name, populate)
|
function Component.new(name, populate)
|
||||||
if (type(name) ~= "string") then
|
if (type(name) ~= "string") then
|
||||||
error("bad argument #1 to 'Component.new' (string expected, got "..type(name)..")", 2)
|
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)
|
||||||
end
|
end
|
||||||
|
|
||||||
if (rawget(Components, name)) then
|
if (rawget(Components, name)) then
|
||||||
error("bad argument #1 to 'Component.new' (ComponentClass with name '"..name.."' was already registerd)", 2) -- luacheck: ignore
|
Utils.error(2, "bad argument #1 to 'Component.new' (ComponentClass with name '%s' was already registerd)", name) -- luacheck: ignore
|
||||||
end
|
end
|
||||||
|
|
||||||
if (type(populate) ~= "function" and type(populate) ~= "nil") then
|
if (type(populate) ~= "function" and type(populate) ~= "nil") then
|
||||||
error("bad argument #1 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2)
|
Utils.error(2, "bad argument #1 to 'Component.new' (function/nil expected, got %s)", type(populate))
|
||||||
end
|
end
|
||||||
|
|
||||||
local componentClass = setmetatable({
|
local componentClass = setmetatable({
|
||||||
|
@ -43,31 +47,40 @@ function Component.new(name, populate)
|
||||||
return componentClass
|
return componentClass
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Internal: Populates a Component with values
|
-- Internal: Populates a Component with values.
|
||||||
function Component:__populate() -- luacheck: ignore
|
function Component:__populate() -- luacheck: ignore
|
||||||
end
|
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()
|
function Component:serialize()
|
||||||
local data = Utils.shallowCopy(self, {})
|
local data = Utils.shallowCopy(self, {})
|
||||||
|
|
||||||
--This values shouldn't be copied over
|
--This values shouldn't be copied over
|
||||||
data.__componentClass = nil
|
data.__componentClass = nil
|
||||||
|
data.__entity = nil
|
||||||
data.__isComponent = nil
|
data.__isComponent = nil
|
||||||
data.__isComponentClass = nil
|
data.__isComponentClass = nil
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Callback: When the Component gets deserialized from serialized data.
|
||||||
function Component:deserialize(data)
|
function Component:deserialize(data)
|
||||||
Utils.shallowCopy(data, self)
|
Utils.shallowCopy(data, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Internal: Creates a new Component.
|
-- Internal: Creates a new Component.
|
||||||
|
-- @param entity The Entity that will receive this Component.
|
||||||
-- @return A new Component
|
-- @return A new Component
|
||||||
function Component:__new()
|
function Component:__new(entity)
|
||||||
local component = setmetatable({
|
local component = setmetatable({
|
||||||
__componentClass = self,
|
__componentClass = self,
|
||||||
|
|
||||||
|
__entity = entity,
|
||||||
__isComponent = true,
|
__isComponent = true,
|
||||||
__isComponentClass = false,
|
__isComponentClass = false,
|
||||||
}, self.__mt)
|
}, self.__mt)
|
||||||
|
@ -76,11 +89,13 @@ function Component:__new()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Internal: Creates and populates a new Component.
|
-- Internal: Creates and populates a new Component.
|
||||||
|
-- @param entity The Entity that will receive this Component.
|
||||||
-- @param ... Varargs passed to the populate function
|
-- @param ... Varargs passed to the populate function
|
||||||
-- @return A new populated Component
|
-- @return A new populated Component
|
||||||
function Component:__initialize(...)
|
function Component:__initialize(entity, ...)
|
||||||
local component = self:__new()
|
local component = self:__new(entity)
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: redundant-parameter
|
||||||
self.__populate(component, ...)
|
self.__populate(component, ...)
|
||||||
|
|
||||||
return component
|
return component
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
--- Container for registered ComponentClasses
|
--- Container for registered ComponentClasses
|
||||||
-- @module Components
|
-- @module Components
|
||||||
|
|
||||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
|
||||||
|
|
||||||
local Type = require(PATH..".type")
|
|
||||||
|
|
||||||
local Components = {}
|
local Components = {}
|
||||||
|
|
||||||
|
Components.__REJECT_PREFIX = "!"
|
||||||
|
Components.__REJECT_MATCH = "^(%"..Components.__REJECT_PREFIX.."?)(.+)"
|
||||||
|
|
||||||
--- Returns true if the containter has the ComponentClass with the specified name
|
--- Returns true if the containter has the ComponentClass with the specified name
|
||||||
-- @string name Name of the ComponentClass to check
|
-- @string name Name of the ComponentClass to check
|
||||||
-- @treturn boolean
|
-- @treturn boolean
|
||||||
|
@ -14,22 +13,43 @@ function Components.has(name)
|
||||||
return rawget(Components, name) and true or false
|
return rawget(Components, name) and true or false
|
||||||
end
|
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
|
--- Returns true and the ComponentClass if one was registered with the specified name
|
||||||
-- or false and an error otherwise
|
-- or false and an error otherwise
|
||||||
-- @string name Name of the ComponentClass to check
|
-- @string name Name of the ComponentClass to check
|
||||||
|
-- @boolean acceptRejected Whether to accept names prefixed with the Reject Prefix.
|
||||||
-- @treturn boolean
|
-- @treturn boolean
|
||||||
-- @treturn Component or error string
|
-- @treturn Component or error string
|
||||||
function Components.try(name)
|
-- @treturn true if acceptRejected was true and the name had the Reject Prefix, false otherwise.
|
||||||
|
function Components.try(name, acceptRejected)
|
||||||
if type(name) ~= "string" then
|
if type(name) ~= "string" then
|
||||||
return false, "ComponentsClass name is expected to be a string, got "..type(name)..")"
|
return false, "ComponentsClass name is expected to be a string, got "..type(name)..")"
|
||||||
end
|
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)
|
local value = rawget(Components, name)
|
||||||
if not value then
|
if not value then
|
||||||
return false, "ComponentClass '"..name.."' does not exist / was not registered"
|
return false, "ComponentClass '"..name.."' does not exist / was not registered"
|
||||||
end
|
end
|
||||||
|
|
||||||
return true, value
|
return true, value, rejected
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the ComponentClass with the specified name
|
--- Returns the ComponentClass with the specified name
|
||||||
|
|
|
@ -6,8 +6,16 @@ local PATH = (...):gsub('%.[^%.]+$', '')
|
||||||
|
|
||||||
local Components = require(PATH..".components")
|
local Components = require(PATH..".components")
|
||||||
local Type = require(PATH..".type")
|
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 = {
|
Entity.__mt = {
|
||||||
__index = Entity,
|
__index = Entity,
|
||||||
}
|
}
|
||||||
|
@ -17,12 +25,11 @@ Entity.__mt = {
|
||||||
-- @treturn Entity A new Entity
|
-- @treturn Entity A new Entity
|
||||||
function Entity.new(world)
|
function Entity.new(world)
|
||||||
if (world ~= nil and not Type.isWorld(world)) then
|
if (world ~= nil and not Type.isWorld(world)) then
|
||||||
error("bad argument #1 to 'Entity.new' (world/nil expected, got "..type(world)..")", 2)
|
Utils.error(2, "bad argument #1 to 'Entity.new' (world/nil expected, got %s)", type(world))
|
||||||
end
|
end
|
||||||
|
|
||||||
local e = setmetatable({
|
local e = setmetatable({
|
||||||
__world = nil,
|
__world = nil,
|
||||||
__components = {},
|
|
||||||
|
|
||||||
__isEntity = true,
|
__isEntity = true,
|
||||||
}, Entity.__mt)
|
}, Entity.__mt)
|
||||||
|
@ -31,23 +38,86 @@ function Entity.new(world)
|
||||||
world:addEntity(e)
|
world:addEntity(e)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if Entity.SERIALIZE_BY_DEFAULT then
|
||||||
|
e:give("serializable")
|
||||||
|
end
|
||||||
|
|
||||||
return e
|
return e
|
||||||
end
|
end
|
||||||
|
|
||||||
local function give(e, name, componentClass, ...)
|
local function createComponent(e, name, componentClass, ...)
|
||||||
local component = componentClass:__initialize(...)
|
local component = componentClass:__initialize(e, ...)
|
||||||
|
local hadComponent = not not e[name]
|
||||||
|
|
||||||
|
if hadComponent then
|
||||||
|
e[name]:removed(true)
|
||||||
|
end
|
||||||
|
|
||||||
e[name] = component
|
e[name] = component
|
||||||
e.__components[name] = component
|
|
||||||
|
if not hadComponent then
|
||||||
|
e:__dirty()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function deserializeComponent(e, name, componentData)
|
||||||
|
local componentClass = Components[name]
|
||||||
|
local hadComponent = not not e[name]
|
||||||
|
|
||||||
|
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()
|
e:__dirty()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function remove(e, name, componentClass)
|
|
||||||
e[name] = nil
|
|
||||||
e.__components[name] = nil
|
|
||||||
|
|
||||||
e:__dirty()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Gives an Entity a Component.
|
--- Gives an Entity a Component.
|
||||||
|
@ -56,15 +126,7 @@ end
|
||||||
-- @param ... additional arguments to pass to the Component's populate function
|
-- @param ... additional arguments to pass to the Component's populate function
|
||||||
-- @treturn Entity self
|
-- @treturn Entity self
|
||||||
function Entity:give(name, ...)
|
function Entity:give(name, ...)
|
||||||
local ok, componentClass = Components.try(name)
|
return giveComponent(self, false, name, ...)
|
||||||
|
|
||||||
if not ok then
|
|
||||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
give(self, name, componentClass, ...)
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Ensures an Entity to have a Component.
|
--- Ensures an Entity to have a Component.
|
||||||
|
@ -73,19 +135,7 @@ end
|
||||||
-- @param ... additional arguments to pass to the Component's populate function
|
-- @param ... additional arguments to pass to the Component's populate function
|
||||||
-- @treturn Entity self
|
-- @treturn Entity self
|
||||||
function Entity:ensure(name, ...)
|
function Entity:ensure(name, ...)
|
||||||
local ok, componentClass = Components.try(name)
|
return giveComponent(self, true, 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
|
end
|
||||||
|
|
||||||
--- Removes a Component from an Entity.
|
--- Removes a Component from an Entity.
|
||||||
|
@ -95,10 +145,10 @@ function Entity:remove(name)
|
||||||
local ok, componentClass = Components.try(name)
|
local ok, componentClass = Components.try(name)
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
Utils.error(2, "bad argument #1 to 'Entity:remove' (%s)", componentClass)
|
||||||
end
|
end
|
||||||
|
|
||||||
remove(self, name, componentClass)
|
removeComponent(self, name)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
@ -109,7 +159,7 @@ end
|
||||||
-- @treturn Entity self
|
-- @treturn Entity self
|
||||||
function Entity:assemble(assemblage, ...)
|
function Entity:assemble(assemblage, ...)
|
||||||
if type(assemblage) ~= "function" then
|
if type(assemblage) ~= "function" then
|
||||||
error("bad argument #1 to 'Entity:assemble' (function expected, got "..type(assemblage)..")")
|
Utils.error(2, "bad argument #1 to 'Entity:assemble' (function expected, got %s)", type(assemblage))
|
||||||
end
|
end
|
||||||
|
|
||||||
assemblage(self, ...)
|
assemblage(self, ...)
|
||||||
|
@ -145,7 +195,7 @@ function Entity:has(name)
|
||||||
local ok, componentClass = Components.try(name)
|
local ok, componentClass = Components.try(name)
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
error("bad argument #1 to 'Entity:has' ("..componentClass..")", 2)
|
Utils.error(2, "bad argument #1 to 'Entity:has' (%s)", componentClass)
|
||||||
end
|
end
|
||||||
|
|
||||||
return self[name] and true or false
|
return self[name] and true or false
|
||||||
|
@ -158,7 +208,7 @@ function Entity:get(name)
|
||||||
local ok, componentClass = Components.try(name)
|
local ok, componentClass = Components.try(name)
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2)
|
Utils.error(2, "bad argument #1 to 'Entity:get' (%s)", componentClass)
|
||||||
end
|
end
|
||||||
|
|
||||||
return self[name]
|
return self[name]
|
||||||
|
@ -168,8 +218,13 @@ end
|
||||||
-- Warning: Do not modify this table.
|
-- Warning: Do not modify this table.
|
||||||
-- Use Entity:give/ensure/remove instead
|
-- Use Entity:give/ensure/remove instead
|
||||||
-- @treturn table Table of all Components the Entity has
|
-- @treturn table Table of all Components the Entity has
|
||||||
function Entity:getComponents()
|
function Entity:getComponents(output)
|
||||||
return self.__components
|
output = output or {}
|
||||||
|
local components = Utils.shallowCopy(self, output)
|
||||||
|
components.__world = nil
|
||||||
|
components.__isEntity = nil
|
||||||
|
|
||||||
|
return components
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns true if the Entity is in a World.
|
--- Returns true if the Entity is in a World.
|
||||||
|
@ -184,11 +239,17 @@ function Entity:getWorld()
|
||||||
return self.__world
|
return self.__world
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entity:serialize()
|
function Entity:serialize(ignoreKey)
|
||||||
local data = {}
|
local data = {}
|
||||||
|
|
||||||
for _, component in pairs(self.__components) do
|
for name, component in pairs(self) do
|
||||||
if component.__name then
|
-- 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
|
||||||
local componentData = component:serialize()
|
local componentData = component:serialize()
|
||||||
|
|
||||||
if componentData ~= nil then
|
if componentData ~= nil then
|
||||||
|
@ -206,18 +267,10 @@ function Entity:deserialize(data)
|
||||||
local componentData = data[i]
|
local componentData = data[i]
|
||||||
|
|
||||||
if (not Components.has(componentData.__name)) then
|
if (not Components.has(componentData.__name)) then
|
||||||
error("bad argument #1 to 'Entity:deserialize' (ComponentClass '"..tostring(componentData.__name).."' wasn't yet loaded)") -- luacheck: ignore
|
Utils.error(2, "bad argument #1 to 'Entity:deserialize' (ComponentClass '%s' wasn't yet loaded)", tostring(componentData.__name)) -- luacheck: ignore
|
||||||
end
|
end
|
||||||
|
|
||||||
local componentClass = Components[componentData.__name]
|
deserializeComponent(self, componentData.__name, componentData)
|
||||||
|
|
||||||
local component = componentClass:__new()
|
|
||||||
component:deserialize(componentData)
|
|
||||||
|
|
||||||
self[componentData.__name] = component
|
|
||||||
self.__components[componentData.__name] = component
|
|
||||||
|
|
||||||
self:__dirty()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
199
concord/filter.lua
Normal file
199
concord/filter.lua
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
--- 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.
|
--- Adds an object to the List.
|
||||||
-- Object must be of reference type
|
-- 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
|
-- @param obj Object to add
|
||||||
-- @treturn List self
|
-- @treturn List self
|
||||||
function List:add(obj)
|
function List:add(obj)
|
||||||
|
@ -26,6 +26,7 @@ function List:add(obj)
|
||||||
self[obj] = size
|
self[obj] = size
|
||||||
self.size = size
|
self.size = size
|
||||||
|
|
||||||
|
if self.onAdded then self:onAdded(obj) end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ function List:remove(obj)
|
||||||
self[obj] = nil
|
self[obj] = nil
|
||||||
self.size = size - 1
|
self.size = size - 1
|
||||||
|
|
||||||
|
if self.onRemoved then self:onRemoved(obj) end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -94,6 +96,30 @@ function List:indexOf(obj)
|
||||||
return self[obj]
|
return self[obj]
|
||||||
end
|
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, {
|
return setmetatable(List, {
|
||||||
__call = function()
|
__call = function()
|
||||||
return List.new()
|
return List.new()
|
||||||
|
|
111
concord/pool.lua
111
concord/pool.lua
|
@ -1,111 +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, 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,9 +5,8 @@
|
||||||
|
|
||||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||||
|
|
||||||
local Pool = require(PATH..".pool")
|
local Filter = require(PATH..".filter")
|
||||||
local Utils = require(PATH..".utils")
|
local Utils = require(PATH..".utils")
|
||||||
local Components = require(PATH..".components")
|
|
||||||
|
|
||||||
local System = {
|
local System = {
|
||||||
ENABLE_OPTIMIZATION = true,
|
ENABLE_OPTIMIZATION = true,
|
||||||
|
@ -19,7 +18,7 @@ System.mt = {
|
||||||
local system = setmetatable({
|
local system = setmetatable({
|
||||||
__enabled = true,
|
__enabled = true,
|
||||||
|
|
||||||
__pools = {},
|
__filters = {},
|
||||||
__world = world,
|
__world = world,
|
||||||
|
|
||||||
__isSystem = true,
|
__isSystem = true,
|
||||||
|
@ -33,11 +32,11 @@ System.mt = {
|
||||||
Utils.shallowCopy(systemClass, system)
|
Utils.shallowCopy(systemClass, system)
|
||||||
end
|
end
|
||||||
|
|
||||||
for name, filter in pairs(systemClass.__filter) do
|
for name, def in pairs(systemClass.__definition) do
|
||||||
local pool = Pool(name, filter)
|
local filter, pool = Filter(name, Utils.shallowCopy(def, {}))
|
||||||
|
|
||||||
system[name] = pool
|
system[name] = pool
|
||||||
system.__pools[#system.__pools + 1] = pool
|
table.insert(system.__filters, filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
system:init(world)
|
system:init(world)
|
||||||
|
@ -45,44 +44,23 @@ System.mt = {
|
||||||
return system
|
return system
|
||||||
end,
|
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.
|
--- Creates a new SystemClass.
|
||||||
-- @param table filters A table containing filters (name = {components...})
|
-- @param table filters A table containing filters (name = {components...})
|
||||||
-- @treturn System A new SystemClass
|
-- @treturn System A new SystemClass
|
||||||
function System.new(filters)
|
function System.new(definition)
|
||||||
local systemClass = setmetatable({
|
definition = definition or {}
|
||||||
__filter = validateFilters(filters),
|
|
||||||
|
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
|
||||||
|
|
||||||
|
local systemClass = setmetatable({
|
||||||
|
__definition = definition,
|
||||||
|
|
||||||
__name = nil,
|
|
||||||
__isSystemClass = true,
|
__isSystemClass = true,
|
||||||
}, System.mt)
|
}, System.mt)
|
||||||
systemClass.__index = systemClass
|
systemClass.__index = systemClass
|
||||||
|
@ -101,8 +79,8 @@ end
|
||||||
-- @param e The Entity to check
|
-- @param e The Entity to check
|
||||||
-- @treturn System self
|
-- @treturn System self
|
||||||
function System:__evaluate(e)
|
function System:__evaluate(e)
|
||||||
for _, pool in ipairs(self.__pools) do
|
for _, filter in ipairs(self.__filters) do
|
||||||
pool:evaluate(e)
|
filter:evaluate(e)
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
@ -112,9 +90,9 @@ end
|
||||||
-- @param e The Entity to remove
|
-- @param e The Entity to remove
|
||||||
-- @treturn System self
|
-- @treturn System self
|
||||||
function System:__remove(e)
|
function System:__remove(e)
|
||||||
for _, pool in ipairs(self.__pools) do
|
for _, filter in ipairs(self.__filters) do
|
||||||
if pool:has(e) then
|
if filter:has(e) then
|
||||||
pool:remove(e)
|
filter:remove(e)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -124,8 +102,8 @@ end
|
||||||
-- Internal: Clears all Entities from the System.
|
-- Internal: Clears all Entities from the System.
|
||||||
-- @treturn System self
|
-- @treturn System self
|
||||||
function System:__clear()
|
function System:__clear()
|
||||||
for i = 1, #self.__pools do
|
for _, filter in ipairs(self.__filters) do
|
||||||
self.__pools[i]:clear()
|
filter:clear()
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
@ -158,18 +136,6 @@ function System:getWorld()
|
||||||
return self.__world
|
return self.__world
|
||||||
end
|
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
|
--- Callbacks
|
||||||
-- @section Callbacks
|
-- @section Callbacks
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,15 @@
|
||||||
|
|
||||||
local Type = {}
|
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.
|
--- Returns if object is an Entity.
|
||||||
-- @param t Object to check
|
-- @param t Object to check
|
||||||
-- @treturn boolean
|
-- @treturn boolean
|
||||||
|
@ -45,4 +54,11 @@ function Type.isWorld(t)
|
||||||
return type(t) == "table" and t.__isWorld or false
|
return type(t) == "table" and t.__isWorld or false
|
||||||
end
|
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
|
return Type
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
|
|
||||||
local Utils = {}
|
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.
|
--- Does a shallow copy of a table and appends it to a target table.
|
||||||
-- @param orig Table to copy
|
-- @param orig Table to copy
|
||||||
-- @param target Table to append to
|
-- @param target Table to append to
|
||||||
|
@ -21,39 +25,43 @@ end
|
||||||
-- @param namespace A table that will hold the required files
|
-- @param namespace A table that will hold the required files
|
||||||
-- @treturn table The namespace table
|
-- @treturn table The namespace table
|
||||||
function Utils.loadNamespace(pathOrFiles, namespace)
|
function Utils.loadNamespace(pathOrFiles, namespace)
|
||||||
if (type(pathOrFiles) ~= "string" and type(pathOrFiles) ~= "table") then
|
if type(pathOrFiles) ~= "string" and type(pathOrFiles) ~= "table" then
|
||||||
error("bad argument #1 to 'loadNamespace' (string/table of strings expected, got "..type(pathOrFiles)..")", 2)
|
Utils.error(2, "bad argument #1 to 'loadNamespace' (string/table of strings expected, got %s)", type(pathOrFiles))
|
||||||
end
|
end
|
||||||
|
|
||||||
if (type(pathOrFiles) == "string") then
|
if type(pathOrFiles) == "string" then
|
||||||
local info = love.filesystem.getInfo(pathOrFiles) -- luacheck: ignore
|
local info = love.filesystem.getInfo(pathOrFiles) -- luacheck: ignore
|
||||||
if (info == nil or info.type ~= "directory") then
|
if info == nil or info.type ~= "directory" then
|
||||||
error("bad argument #1 to 'loadNamespace' (path '"..pathOrFiles.."' not found)", 2)
|
Utils.error(2, "bad argument #1 to 'loadNamespace' (path '%s' not found)", pathOrFiles)
|
||||||
end
|
end
|
||||||
|
|
||||||
local files = love.filesystem.getDirectoryItems(pathOrFiles)
|
local files = love.filesystem.getDirectoryItems(pathOrFiles)
|
||||||
|
|
||||||
for _, file in ipairs(files) do
|
for _, file in ipairs(files) do
|
||||||
|
local isFile = love.filesystem.getInfo(pathOrFiles .. "/" .. file).type == "file"
|
||||||
|
|
||||||
|
if isFile and string.match(file, '%.lua$') ~= nil then
|
||||||
local name = file:sub(1, #file - 4)
|
local name = file:sub(1, #file - 4)
|
||||||
local path = pathOrFiles.."."..name
|
local path = pathOrFiles.."."..name
|
||||||
|
|
||||||
local value = require(path)
|
local value = require(path:gsub("%/", "."))
|
||||||
if namespace then namespace[name] = value end
|
if namespace then namespace[name] = value end
|
||||||
end
|
end
|
||||||
elseif (type(pathOrFiles == "table")) then
|
end
|
||||||
|
elseif type(pathOrFiles) == "table" then
|
||||||
for _, path in ipairs(pathOrFiles) do
|
for _, path in ipairs(pathOrFiles) do
|
||||||
if (type(path) ~= "string") then
|
if type(path) ~= "string" then
|
||||||
error("bad argument #2 to 'loadNamespace' (string/table of strings expected, got table containing "..type(path)..")", 2) -- luacheck: ignore
|
Utils.error(2, "bad argument #2 to 'loadNamespace' (string/table of strings expected, got table containing %s)", type(path)) -- luacheck: ignore
|
||||||
end
|
end
|
||||||
|
|
||||||
local name = path
|
local name = path
|
||||||
|
|
||||||
local dotIndex, slashIndex = path:match("^.*()%."), path:match("^.*()%/")
|
local dotIndex, slashIndex = path:match("^.*()%."), path:match("^.*()%/")
|
||||||
if (dotIndex or slashIndex) then
|
if dotIndex or slashIndex then
|
||||||
name = path:sub((dotIndex or slashIndex) + 1)
|
name = path:sub((dotIndex or slashIndex) + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local value = require(path)
|
local value = require(path:gsub("%/", "."))
|
||||||
if namespace then namespace[name] = value end
|
if namespace then namespace[name] = value end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
local PATH = (...):gsub('%.[^%.]+$', '')
|
local PATH = (...):gsub('%.[^%.]+$', '')
|
||||||
|
|
||||||
|
local Filter = require(PATH..".filter")
|
||||||
local Entity = require(PATH..".entity")
|
local Entity = require(PATH..".entity")
|
||||||
|
local Components = require(PATH..".components")
|
||||||
local Type = require(PATH..".type")
|
local Type = require(PATH..".type")
|
||||||
local List = require(PATH..".list")
|
local List = require(PATH..".list")
|
||||||
local Utils = require(PATH..".utils")
|
local Utils = require(PATH..".utils")
|
||||||
|
@ -18,6 +20,12 @@ World.__mt = {
|
||||||
__index = World,
|
__index = World,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local defaultGenerator = function (state)
|
||||||
|
local current = state
|
||||||
|
state = state +1
|
||||||
|
return string.format("%d", current), state
|
||||||
|
end
|
||||||
|
|
||||||
--- Creates a new World.
|
--- Creates a new World.
|
||||||
-- @treturn World The new World
|
-- @treturn World The new World
|
||||||
function World.new()
|
function World.new()
|
||||||
|
@ -28,14 +36,24 @@ function World.new()
|
||||||
__events = {},
|
__events = {},
|
||||||
__emitSDepth = 0,
|
__emitSDepth = 0,
|
||||||
|
|
||||||
|
__resources = {},
|
||||||
|
|
||||||
|
__hash = {
|
||||||
|
state = -2^53,
|
||||||
|
generator = defaultGenerator,
|
||||||
|
keys = {},
|
||||||
|
entities = {}
|
||||||
|
},
|
||||||
|
|
||||||
__added = List(), __backAdded = List(),
|
__added = List(), __backAdded = List(),
|
||||||
__removed = List(), __backRemoved = List(),
|
__removed = List(), __backRemoved = List(),
|
||||||
__dirty = List(), __backDirty = List(),
|
__dirty = List(), __backDirty = List(),
|
||||||
|
|
||||||
__systemLookup = {},
|
__systemLookup = {},
|
||||||
|
|
||||||
__name = nil,
|
|
||||||
__isWorld = true,
|
__isWorld = true,
|
||||||
|
|
||||||
|
__ignoreEmits = false
|
||||||
}, World.__mt)
|
}, World.__mt)
|
||||||
|
|
||||||
-- Optimization: We deep copy the World class into our instance of a world.
|
-- Optimization: We deep copy the World class into our instance of a world.
|
||||||
|
@ -53,7 +71,7 @@ end
|
||||||
-- @treturn World self
|
-- @treturn World self
|
||||||
function World:addEntity(e)
|
function World:addEntity(e)
|
||||||
if not Type.isEntity(e) then
|
if not Type.isEntity(e) then
|
||||||
error("bad argument #1 to 'World:addEntity' (Entity expected, got "..type(e)..")", 2)
|
Utils.error(2, "bad argument #1 to 'World:addEntity' (Entity expected, got %s)", type(e))
|
||||||
end
|
end
|
||||||
|
|
||||||
if e.__world then
|
if e.__world then
|
||||||
|
@ -66,12 +84,47 @@ function World:addEntity(e)
|
||||||
return self
|
return self
|
||||||
end
|
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.
|
--- Removes an Entity from the World.
|
||||||
-- @tparam Entity e Entity to remove
|
-- @tparam Entity e Entity to remove
|
||||||
-- @treturn World self
|
-- @treturn World self
|
||||||
function World:removeEntity(e)
|
function World:removeEntity(e)
|
||||||
if not Type.isEntity(e) then
|
if not Type.isEntity(e) then
|
||||||
error("bad argument #1 to 'World:removeEntity' (Entity expected, got "..type(e)..")", 2)
|
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")
|
||||||
end
|
end
|
||||||
|
|
||||||
self.__removed:add(e)
|
self.__removed:add(e)
|
||||||
|
@ -208,7 +261,7 @@ function World:addSystem(systemClass)
|
||||||
local ok, err = tryAddSystem(self, systemClass)
|
local ok, err = tryAddSystem(self, systemClass)
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
error("bad argument #1 to 'World:addSystem' ("..err..")", 2)
|
Utils.error(2, "bad argument #1 to 'World:addSystem' (%s)", err)
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
@ -226,7 +279,7 @@ function World:addSystems(...)
|
||||||
|
|
||||||
local ok, err = tryAddSystem(self, systemClass)
|
local ok, err = tryAddSystem(self, systemClass)
|
||||||
if not ok then
|
if not ok then
|
||||||
error("bad argument #"..i.." to 'World:addSystems' ("..err..")", 2)
|
Utils.error(2, "bad argument #%d to 'World:addSystems' (%s)", i, err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -238,7 +291,7 @@ end
|
||||||
-- @treturn boolean
|
-- @treturn boolean
|
||||||
function World:hasSystem(systemClass)
|
function World:hasSystem(systemClass)
|
||||||
if not Type.isSystemClass(systemClass) then
|
if not Type.isSystemClass(systemClass) then
|
||||||
error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2)
|
Utils.error(2, "bad argument #1 to 'World:hasSystem' (SystemClass expected, got %s)", type(systemClass))
|
||||||
end
|
end
|
||||||
|
|
||||||
return self.__systemLookup[systemClass] and true or false
|
return self.__systemLookup[systemClass] and true or false
|
||||||
|
@ -249,7 +302,7 @@ end
|
||||||
-- @treturn System System to get
|
-- @treturn System System to get
|
||||||
function World:getSystem(systemClass)
|
function World:getSystem(systemClass)
|
||||||
if not Type.isSystemClass(systemClass) then
|
if not Type.isSystemClass(systemClass) then
|
||||||
error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2)
|
Utils.error(2, "bad argument #1 to 'World:getSystem' (SystemClass expected, got %s)", type(systemClass))
|
||||||
end
|
end
|
||||||
|
|
||||||
return self.__systemLookup[systemClass]
|
return self.__systemLookup[systemClass]
|
||||||
|
@ -262,7 +315,7 @@ end
|
||||||
-- @treturn World self
|
-- @treturn World self
|
||||||
function World:emit(functionName, ...)
|
function World:emit(functionName, ...)
|
||||||
if not functionName or type(functionName) ~= "string" then
|
if not functionName or type(functionName) ~= "string" then
|
||||||
error("bad argument #1 to 'World:emit' (String expected, got "..type(functionName)..")")
|
Utils.error(2, "bad argument #1 to 'World:emit' (String expected, got %s)", type(functionName))
|
||||||
end
|
end
|
||||||
|
|
||||||
local shouldFlush = self.__emitSDepth == 0
|
local shouldFlush = self.__emitSDepth == 0
|
||||||
|
@ -271,6 +324,13 @@ function World:emit(functionName, ...)
|
||||||
|
|
||||||
local listeners = self.__events[functionName]
|
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
|
||||||
|
|
||||||
if listeners then
|
if listeners then
|
||||||
for i = 1, #listeners do
|
for i = 1, #listeners do
|
||||||
local listener = listeners[i]
|
local listener = listeners[i]
|
||||||
|
@ -285,6 +345,12 @@ function World:emit(functionName, ...)
|
||||||
end
|
end
|
||||||
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
|
self.__emitSDepth = self.__emitSDepth - 1
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
@ -316,49 +382,103 @@ function World:getSystems()
|
||||||
return self.__systems
|
return self.__systems
|
||||||
end
|
end
|
||||||
|
|
||||||
function World:serialize()
|
function World:serialize(ignoreKeys)
|
||||||
self:__flush()
|
self:__flush()
|
||||||
|
|
||||||
local data = {}
|
local data = { generator = self.__hash.state }
|
||||||
|
|
||||||
for i = 1, self.__entities.size do
|
for i = 1, self.__entities.size do
|
||||||
local entity = self.__entities[i]
|
local entity = self.__entities[i]
|
||||||
|
|
||||||
local entityData = entity:serialize()
|
if entity.serializable then
|
||||||
|
local entityData = entity:serialize(ignoreKeys)
|
||||||
data[i] = entityData
|
table.insert(data, entityData)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
function World:deserialize(data, append)
|
function World:deserialize(data, startClean, ignoreGenerator)
|
||||||
if (not append) then
|
if startClean then
|
||||||
self:clear()
|
self:clear()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if (not ignoreGenerator) then
|
||||||
|
self.__hash.state = data.generator
|
||||||
|
end
|
||||||
|
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
for i = 1, #data do
|
for i = 1, #data do
|
||||||
local entityData = data[i]
|
local entity = Entity(self)
|
||||||
|
|
||||||
local entity = Entity()
|
if data[i].key then
|
||||||
entity:deserialize(entityData)
|
local component = Components.key:__new(entity)
|
||||||
|
component:deserialize(data[i].key)
|
||||||
|
entity.key = component
|
||||||
|
|
||||||
self:addEntity(entity)
|
entity:__dirty()
|
||||||
|
end
|
||||||
|
|
||||||
|
entities[i] = entity
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #data do
|
||||||
|
entities[i]:deserialize(data[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
self:__flush()
|
self:__flush()
|
||||||
|
|
||||||
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns true if the World has a name.
|
function World:setKeyGenerator(generator, initialState)
|
||||||
-- @treturn boolean
|
if not Type.isCallable(generator) then
|
||||||
function World:hasName()
|
Utils.error(2, "bad argument #1 to 'World:setKeyGenerator' (function expected, got %s)", type(generator))
|
||||||
return self.__name and true or false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the name of the World.
|
self.__hash.generator = generator
|
||||||
-- @treturn string
|
self.__hash.state = initialState
|
||||||
function World:getName()
|
|
||||||
return self.__name
|
return self
|
||||||
|
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]
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Callback for when an Entity is added to the World.
|
--- Callback for when an Entity is added to the World.
|
||||||
|
@ -371,8 +491,25 @@ end
|
||||||
function World:onEntityRemoved(e) -- luacheck: ignore
|
function World:onEntityRemoved(e) -- luacheck: ignore
|
||||||
end
|
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, {
|
return setmetatable(World, {
|
||||||
__call = function(_, ...)
|
__call = function(_, ...)
|
||||||
|
---@diagnostic disable-next-line: redundant-parameter
|
||||||
return World.new(...)
|
return World.new(...)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue