Document full project. Lots of small fixes

This commit is contained in:
Tjakka5 2020-01-04 01:04:18 +01:00
parent f6669b2e63
commit 69a9e83759
14 changed files with 342 additions and 156 deletions

View file

@ -1,10 +1,15 @@
--- Assemblage --- Assemblage
-- An Assemblage is a function that 'makes' an entity something.
-- It does this by :give'ing or :ensure'ing Components, or by :assemble'ing the Entity.
local Assemblage = {} local Assemblage = {}
Assemblage.__mt = { Assemblage.__mt = {
__index = Assemblage, __index = Assemblage,
} }
--- Creates a new Assemblage.
-- @param assemble Function that assembles an Entity
-- @return A new Assemblage
function Assemblage.new(assemble) function Assemblage.new(assemble)
local assemblage = setmetatable({ local assemblage = setmetatable({
__assemble = assemble, __assemble = assemble,
@ -15,6 +20,11 @@ function Assemblage.new(assemble)
return assemblage return assemblage
end end
--- Assembles an Entity.
-- @see Entity:assemble
-- @param e Entity to assemble
-- @param ... Varargs to pass to the assemble function
-- @ return self
function Assemblage:assemble(e, ...) function Assemblage:assemble(e, ...)
self.__assemble(e, ...) self.__assemble(e, ...)

View file

@ -1,4 +1,5 @@
-- Assemblages --- Assemblages
-- Container for registered Assemblages
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
@ -6,17 +7,20 @@ local Type = require(PATH..".type")
local Assemblages = {} local Assemblages = {}
--- Registers an Assemblage.
-- @param name Name to register under
-- @param assemblage Assemblage to register
function Assemblages.register(name, assemblage) function Assemblages.register(name, assemblage)
if (type(name) ~= "string") then if (type(name) ~= "string") then
error("bad argument #1 to 'Assemblages.register' (string expected, got "..type(name)..")", 3) error("bad argument #1 to 'Assemblages.register' (string expected, got "..type(name)..")", 3)
end end
if (not Type.isAssemblage(assemblage)) then if (not Type.isAssemblage(assemblage)) then
error("bad argument #2 to 'Assemblages.register' (assemblage expected, got "..type(world)..")", 3) error("bad argument #2 to 'Assemblages.register' (assemblage expected, got "..type(assemblage)..")", 3)
end end
if (rawget(Assemblages, name)) then if (rawget(Assemblages, name)) then
error("bad argument #2 to 'Assemblages.register' (Assemblage with name '"..name.."' is already registerd)", 3) error("bad argument #2 to 'Assemblages.register' (Assemblage with name '"..name.."' was already registerd)", 3)
end end
Assemblages[name] = assemblage Assemblages[name] = assemblage

View file

@ -1,43 +1,46 @@
--- Component --- Component
-- A Component is a pure data container.
-- A Component is contained by a single entity.
local Component = {} local Component = {}
Component.__mt = { Component.__mt = {
__index = Component, __index = Component,
} }
--- Creates a new Component. --- Creates a new ComponentClass.
-- @param populate A function that populates the Bag with values -- @param populate Function that populates a Component with values
-- @return A Component object -- @return A new ComponentClass
function Component.new(populate) function Component.new(populate)
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) error("bad argument #1 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2)
end end
local baseComponent = setmetatable({ local componentClass = setmetatable({
__populate = populate, __populate = populate,
__isBaseComponent = true, __isComponentClass = true,
}, Component.__mt) }, Component.__mt)
baseComponent.__mt = { componentClass.__mt = {
__index = baseComponent __index = componentClass
} }
return baseComponent return componentClass
end end
--- Internal: Populates a Component with values
function Component:__populate() -- luacheck: ignore function Component:__populate() -- luacheck: ignore
end end
--- Creates and initializes a new Component. --- Internal: Creates and populates a new Component.
-- @param ... The values passed to the populate function -- @param ... Varargs passed to the populate function
-- @return A new initialized Component -- @return A new populated Component
function Component:__initialize(...) function Component:__initialize(...)
local component = setmetatable({ local component = setmetatable({
__baseComponent = self, __componentClass = self,
__isComponent = true, __isComponent = true,
__isBaseComponent = false, __isComponentClass = false,
}, self) }, self)
self.__populate(component, ...) self.__populate(component, ...)

View file

@ -1,4 +1,5 @@
-- Components -- Components
-- Container for registered ComponentClasss
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
@ -6,24 +7,27 @@ local Type = require(PATH..".type")
local Components = {} local Components = {}
function Components.register(name, baseComponent) --- Registers a ComponentClass.
-- @param name Name to register under
-- @param componentClass ComponentClass to register
function Components.register(name, componentClass)
if (type(name) ~= "string") then if (type(name) ~= "string") then
error("bad argument #1 to 'Components.register' (string expected, got "..type(name)..")", 3) error("bad argument #1 to 'Components.register' (string expected, got "..type(name)..")", 3)
end end
if (not Type.isBaseComponent(baseComponent)) then if (not Type.isComponentClass(componentClass)) then
error("bad argument #2 to 'Components.register' (BaseComponent expected, got "..type(baseComponent)..")", 3) error("bad argument #2 to 'Components.register' (ComponentClass expected, got "..type(componentClass)..")", 3)
end end
if (rawget(Components, name)) then if (rawget(Components, name)) then
error("bad argument #2 to 'Components.register' (BaseComponent with name '"..name.."' is already registerd)", 3) error("bad argument #2 to 'Components.register' (ComponentClass with name '"..name.."' was already registerd)", 3)
end end
Components[name] = baseComponent Components[name] = componentClass
end end
return setmetatable(Components, { return setmetatable(Components, {
__index = function(_, name) __index = function(_, name)
error("Attempt to index BaseComponent '"..tostring(name).."' that does not exist / was not registered", 2) error("Attempt to index ComponentClass '"..tostring(name).."' that does not exist / was not registered", 2)
end end
}) })

View file

@ -1,4 +1,7 @@
--- Entity --- Entity
-- Entities are the concrete objects that exist in your project.
-- An Entity have Components and are processed by Systems.
-- An Entity is contained by a maximum of 1 World.
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
@ -9,7 +12,8 @@ Entity.__mt = {
__index = Entity, __index = Entity,
} }
--- Creates and initializes a new Entity. --- Creates a new Entity. Optionally adds it to a World.
-- @param world Optional World to add the entity to
-- @return A new Entity -- @return 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
@ -30,63 +34,73 @@ function Entity.new(world)
return e return e
end end
local function give(e, baseComponent, ...) local function give(e, componentClass, ...)
local component = baseComponent:__initialize(...) local component = componentClass:__initialize(...)
e[baseComponent] = component e[componentClass] = component
e.__components[baseComponent] = component e.__components[componentClass] = component
e:__dirty() e:__dirty()
end end
local function remove(e, baseComponent) local function remove(e, componentClass)
e[baseComponent] = nil e[componentClass] = nil
e.__components[baseComponent] = nil e.__components[componentClass] = nil
e:__dirty() e:__dirty()
end end
--- Gives an Entity a component with values. --- Gives an Entity a Component.
-- @param component The Component to add -- If the Component already exists, it's overridden by this new Component
-- @param ... The values passed to the Component -- @param componentClass ComponentClass to add an instance of
-- @param ... varargs passed to the Component's populate function
-- @return self -- @return self
function Entity:give(baseComponent, ...) function Entity:give(componentClass, ...)
if not Type.isBaseComponent(baseComponent) then if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:give' (BaseComponent expected, got "..type(baseComponent)..")", 2) error("bad argument #1 to 'Entity:give' (ComponentClass expected, got "..type(componentClass)..")", 2)
end end
give(self, baseComponent, ...) give(self, componentClass, ...)
return self return self
end end
function Entity:ensure(baseComponent, ...) --- Ensures an Entity to have a Component.
if not Type.isBaseComponent(baseComponent) then -- If the Component already exists, no action is taken
error("bad argument #1 to 'Entity:ensure' (BaseComponent expected, got "..type(baseComponent)..")", 2) -- @param componentClass ComponentClass to add an instance of
end -- @param ... varargs passed to the Component's populate function
if self[baseComponent] then
return self
end
give(self, baseComponent, ...)
return self
end
--- Removes a component from an Entity.
-- @param component The Component to remove
-- @return self -- @return self
function Entity:remove(baseComponent) function Entity:ensure(componentClass, ...)
if not Type.isBaseComponent(baseComponent) then if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:remove' (BaseComponent expected, got "..type(baseComponent)..")") error("bad argument #1 to 'Entity:ensure' (ComponentClass expected, got "..type(componentClass)..")", 2)
end end
remove(self, baseComponent) if self[componentClass] then
return self
end
give(self, componentClass, ...)
return self return self
end end
--- Removes a Component from an Entity.
-- @param componentClass ComponentClass of the Component to remove
-- @return self
function Entity:remove(componentClass)
if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:remove' (ComponentClass expected, got "..type(componentClass)..")")
end
remove(self, componentClass)
return self
end
--- Assembles an Entity.
-- @see Assemblage:assemble
-- @param assemblage Assemblage to assemble with
-- @param ... Varargs to pass to the Assemblage's assemble function.
function Entity:assemble(assemblage, ...) function Entity:assemble(assemblage, ...)
if not Type.isAssemblage(assemblage) then if not Type.isAssemblage(assemblage) then
error("bad argument #1 to 'Entity:assemble' (Assemblage expected, got "..type(assemblage)..")") error("bad argument #1 to 'Entity:assemble' (Assemblage expected, got "..type(assemblage)..")")
@ -98,6 +112,7 @@ function Entity:assemble(assemblage, ...)
end end
--- Destroys the Entity. --- Destroys the Entity.
-- Removes the Entity from it's World if it's in one.
-- @return self -- @return self
function Entity:destroy() function Entity:destroy()
if self.__world then if self.__world then
@ -107,42 +122,54 @@ function Entity:destroy()
return self return self
end end
--- Internal: Tells the World it's in that this Entity is dirty.
-- @return self
function Entity:__dirty() function Entity:__dirty()
if self.__world then if self.__world then
self.__world:__dirtyEntity(self) self.__world:__dirtyEntity(self)
end end
return self
end
--- Returns true if the Entity has a Component.
-- @param componentClass ComponentClass of the Component to check
-- @return True if the Entity has the Component, false otherwise
function Entity:has(componentClass)
if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:has' (ComponentClass expected, got "..type(componentClass)..")")
end
return self[componentClass] ~= nil
end end
--- Gets a Component from the Entity. --- Gets a Component from the Entity.
-- @param component The Component to get -- @param componentClass ComponentClass of the Component to get
-- @return The Bag from the Component -- @return The Component
function Entity:get(baseComponent) function Entity:get(componentClass)
if not Type.isBaseComponent(baseComponent) then if not Type.isComponentClass(componentClass) then
error("bad argument #1 to 'Entity:get' (BaseComponent expected, got "..type(baseComponent)..")") error("bad argument #1 to 'Entity:get' (ComponentClass expected, got "..type(componentClass)..")")
end end
return self[baseComponent] return self[componentClass]
end
--- Returns true if the Entity has the Component.
-- @param component The Component to check against
-- @return True if the entity has the Bag. False otherwise
function Entity:has(baseComponent)
if not Type.isBaseComponent(baseComponent) then
error("bad argument #1 to 'Entity:has' (BaseComponent expected, got "..type(baseComponent)..")")
end
return self[baseComponent] ~= nil
end end
--- Returns a table of all Components the Entity has.
-- Warning: Do not modify this table.
-- Use Entity:give/ensure/remove instead
-- @return Table of all Components the Entity has
function Entity:getComponents() function Entity:getComponents()
return self.__components return self.__components
end end
function Entity:hasWorld() --- Returns true if the Entity is in a World.
-- @return True if the Entity is in a World, false otherwise
function Entity:inWorld()
return self.__world and true or false return self.__world and true or false
end end
--- Returns the World the Entity is in.
-- @return The World the Entity is in.
function Entity:getWorld() function Entity:getWorld()
return self.__world return self.__world
end end

View file

@ -47,13 +47,13 @@ Concord.assemblages = require(PATH..".assemblages")
local function load(pathOrFiles, namespace) local function load(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 'load' (string/table of strings expected, got "..type(pathOrFiles)..")", 3) -- luacheck: ignore error("bad argument #1 to 'load' (string/table of strings expected, got "..type(pathOrFiles)..")", 3)
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 'load' (path '"..pathOrFiles.."' not found)", 3) -- luacheck: ignore error("bad argument #1 to 'load' (path '"..pathOrFiles.."' not found)", 3)
end end
local files = love.filesystem.getDirectoryItems(pathOrFiles) local files = love.filesystem.getDirectoryItems(pathOrFiles)
@ -82,18 +82,34 @@ local function load(pathOrFiles, namespace)
end end
end end
--- Loads ComponentClasses and puts them in the Components container.
-- Accepts a table of paths to files: {"component_1", "component_2", "etc"}
-- Accepts a path to a directory with ComponentClasses: "components"
--@see Components
function Concord.loadComponents(pathOrFiles) function Concord.loadComponents(pathOrFiles)
load(pathOrFiles, Concord.components) load(pathOrFiles, Concord.components)
end end
--- Loads SystemClasses and puts them in the Systems container.
-- Accepts a table of paths to files: {"system_1", "system_2", "etc"}
-- Accepts a path to a directory with SystemClasses: "systems"
--@see Systems
function Concord.loadSystems(pathOrFiles) function Concord.loadSystems(pathOrFiles)
load(pathOrFiles, Concord.systems) load(pathOrFiles, Concord.systems)
end end
--- Loads Worlds and puts them in the Worlds container.
-- Accepts a table of paths to files: {"world_1", "world_2", "etc"}
-- Accepts a path to a directory with Worlds: "worlds"
--@see Worlds
function Concord.loadWorlds(pathOrFiles) function Concord.loadWorlds(pathOrFiles)
load(pathOrFiles, Concord.worlds) load(pathOrFiles, Concord.worlds)
end end
--- Loads Assemblages and puts them in the Assemblages container.
-- Accepts a table of paths to files: {"assemblage_1", "assemblage_2", "etc"}
-- Accepts a path to a directory with Assemblages: "assemblages"
--@see Assemblages
function Concord.loadAssemblages(pathOrFiles) function Concord.loadAssemblages(pathOrFiles)
load(pathOrFiles, Concord.assemblages) load(pathOrFiles, Concord.assemblages)
end end

View file

@ -1,4 +1,5 @@
--- List --- List
-- Data structure that allows for fast removal at the cost of containing order.
local List = {} local List = {}
List.__mt = { List.__mt = {
@ -6,7 +7,7 @@ List.__mt = {
} }
--- Creates a new List. --- Creates a new List.
-- @return A new list -- @return A new List
function List.new() function List.new()
return setmetatable({ return setmetatable({
size = 0, size = 0,
@ -14,9 +15,11 @@ function List.new()
end end
--- Adds an object to the List. --- Adds an object to the List.
-- @param obj The object to add -- Object must be of reference type
-- Object may not be the string 'size'
-- @param obj Object to add
-- @return self -- @return self
function List:__add(obj) -- obj can not be a number and also not the string "size" function List:__add(obj)
local size = self.size + 1 local size = self.size + 1
self[size] = obj self[size] = obj
@ -27,7 +30,7 @@ function List:__add(obj) -- obj can not be a number and also not the string "siz
end end
--- Removes an object from the List. --- Removes an object from the List.
-- @param obj The object to remove -- @param obj Object to remove
-- @return self -- @return self
function List:__remove(obj) function List:__remove(obj)
local index = self[obj] local index = self[obj]
@ -66,17 +69,23 @@ function List:__clear()
return self return self
end end
--- Gets if the List has the object. --- Returns true if the List has the object.
-- @param obj The object to search for -- @param obj Object to check for
-- true if the list has the object, false otherwise -- @return True if the List has the object, false otherwise
function List:has(obj) function List:has(obj)
return self[obj] and true or false return self[obj] and true or false
end end
--- Returns the object at an index.
-- @param i Index to get from
-- @return Object at the index
function List:get(i) function List:get(i)
return self[i] return self[i]
end end
--- Returns the index of an object in the List.
-- @param obj Object to get index of
-- @return index of object in the List.
function List:indexOf(obj) function List:indexOf(obj)
if (not self[obj]) then if (not self[obj]) then
error("bad argument #1 to 'List:indexOf' (Object was not in List)", 2) error("bad argument #1 to 'List:indexOf' (Object was not in List)", 2)

View file

@ -1,4 +1,6 @@
--- Pool --- Pool
-- A Pool is used to iterate over Entities with a specific Components
-- A Pool contain a any amount of Entities.
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
@ -10,8 +12,8 @@ Pool.__mt = {
} }
--- Creates a new Pool --- Creates a new Pool
-- @param name Identifier for the Pool. -- @param name Name for the Pool.
-- @param filter Table containing the required Components -- @param filter Table containing the required BaseComponents
-- @return The new Pool -- @return The new Pool
function Pool.new(name, filter) function Pool.new(name, filter)
local pool = setmetatable(List(), Pool.__mt) local pool = setmetatable(List(), Pool.__mt)
@ -25,7 +27,7 @@ function Pool.new(name, filter)
end end
--- Checks if an Entity is eligible for the Pool. --- Checks if an Entity is eligible for the Pool.
-- @param e The Entity to check -- @param e Entity to check
-- @return True if the entity is eligible, false otherwise -- @return True if the entity is eligible, false otherwise
function Pool:__eligible(e) function Pool:__eligible(e)
for _, component in ipairs(self.__filter) do for _, component in ipairs(self.__filter) do
@ -37,27 +39,46 @@ function Pool:__eligible(e)
return true return true
end end
--- Internal: Adds an Entity to the Pool.
-- @param e Entity to add
-- @return self
function Pool:__add(e) function Pool:__add(e)
List.__add(self, e) List.__add(self, e)
self:onEntityAdded(e) self:onEntityAdded(e)
return self
end end
--- Internal: Removed an Entity from the Pool.
-- @param e Entity to remove
-- @return self
function Pool:__remove(e) function Pool:__remove(e)
List.__remove(self, e) List.__remove(self, e)
self:onEntityRemoved(e) self:onEntityRemoved(e)
return self
end end
--- Gets the name of the Pool
-- @return Name of the Pool.
function Pool:getName() function Pool:getName()
return self.__name return self.__name
end end
--- Gets the filter of the Pool.
-- Warning: Do not modify this filter.
-- @return Filter of the Pool.
function Pool:getFilter() function Pool:getFilter()
return self.__filter return self.__filter
end end
--- Callback for when an Entity is added to the Pool.
-- @param e Entity that was added.
function Pool:onEntityAdded(e) -- luacheck: ignore function Pool:onEntityAdded(e) -- luacheck: ignore
end end
-- Callback for when an Entity is removed from the Pool.
-- @param e Entity that was removed.
function Pool:onEntityRemoved(e) -- luacheck: ignore function Pool:onEntityRemoved(e) -- luacheck: ignore
end end

View file

@ -1,4 +1,7 @@
--- System --- System
-- A System iterates over Entities. From these Entities its get Components and modify them.
-- A System contains 1 or more Pools.
-- A System is contained by 1 World.
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
@ -11,7 +14,7 @@ local System = {
System.mt = { System.mt = {
__index = System, __index = System,
__call = function(baseSystem, world) __call = function(systemClass, world)
local system = setmetatable({ local system = setmetatable({
__enabled = true, __enabled = true,
@ -19,8 +22,8 @@ System.mt = {
__world = world, __world = world,
__isSystem = true, __isSystem = true,
__isBaseSystem = false, -- Overwrite value from baseSystem __isSystemClass = false, -- Overwrite value from systemClass
}, baseSystem) }, systemClass)
-- 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.
-- This grants slightly faster access times at the cost of memory. -- This grants slightly faster access times at the cost of memory.
@ -29,8 +32,8 @@ System.mt = {
Utils.shallowCopy(System, system) Utils.shallowCopy(System, system)
end end
for _, filter in pairs(baseSystem.__filter) do for _, filter in pairs(systemClass.__filter) do
local pool = system:__buildPool(filter) local pool = system.__buildPool(filter)
if not system[pool.__name] then if not system[pool.__name] then
system[pool.__name] = pool system[pool.__name] = pool
system.__pools[#system.__pools + 1] = pool system.__pools[#system.__pools + 1] = pool
@ -45,39 +48,40 @@ System.mt = {
end, end,
} }
--- Creates a new System prototype. --- Creates a new SystemClass.
-- @param ... Variable amounts of filters -- @param ... Variable amounts of filters
-- @return A new System prototype -- @return A new SystemClass
function System.new(...) function System.new(...)
local baseSystem = setmetatable({ local systemClass = setmetatable({
__isBaseSystem = true, __isSystemClass = true,
__filter = {...}, __filter = {...},
}, System.mt) }, System.mt)
baseSystem.__index = baseSystem systemClass.__index = systemClass
return baseSystem return systemClass
end end
--- Builds a Pool for the System. --- Internal: Builds a Pool for the System.
-- @param baseFilter The 'raw' Filter -- @param baseFilter The 'raw' Filter
-- @return A new Pool -- @return A new Pool
function System:__buildPool(baseFilter) -- luacheck: ignore function System.__buildPool(baseFilter)
local name = "pool" local name = "pool"
local filter = {} local filter = {}
for _, v in ipairs(baseFilter) do for _, value in ipairs(baseFilter) do
if type(v) == "table" then if type(value) == "table" then
filter[#filter + 1] = v filter[#filter + 1] = value
elseif type(v) == "string" then elseif type(value) == "string" then
name = v name = value
end end
end end
return Pool(name, filter) return Pool(name, filter)
end end
--- Checks and applies an Entity to the System's pools. --- Internal: Evaluates an Entity for all the System's Pools.
-- @param e The Entity to check -- @param e The Entity to check
-- @return self
function System:__evaluate(e) function System:__evaluate(e)
for _, pool in ipairs(self.__pools) do for _, pool in ipairs(self.__pools) do
local has = pool:has(e) local has = pool:has(e)
@ -93,8 +97,9 @@ function System:__evaluate(e)
return self return self
end end
--- Remove an Entity from the System. --- Internal: Removes an Entity from the System.
-- @param e The Entity to remove -- @param e The Entity to remove
-- @return self
function System:__remove(e) function System:__remove(e)
for _, pool in ipairs(self.__pools) do for _, pool in ipairs(self.__pools) do
if pool:has(e) then if pool:has(e) then
@ -105,6 +110,8 @@ function System:__remove(e)
return self return self
end end
--- Internal: Clears all Entities from the System.
-- @return self
function System:clear() function System:clear()
for i = 1, #self.__pools do for i = 1, #self.__pools do
self.__pools[i]:__clear() self.__pools[i]:__clear()
@ -113,59 +120,68 @@ function System:clear()
return self return self
end end
--- Enables the System.
-- @return self
function System:enable() function System:enable()
self:setEnabled(true) self:setEnabled(true)
return self return self
end end
--- Disables the System.
-- @return self
function System:disable() function System:disable()
self:setEnabled(false) self:setEnabled(false)
return self return self
end end
--- Toggles if the System is enabled.
-- @return self
function System:toggleEnable() function System:toggleEnable()
self:setEnabled(not self.__enabled) self:setEnabled(not self.__enabled)
return self return self
end end
--- Sets if the System is enabled
-- @param enable Enable
-- @return self
function System:setEnabled(enable) function System:setEnabled(enable)
if (not self.__enabled and enable) then if (not self.__enabled and enable) then
self.__enabled = true self.__enabled = true
self:onEnabledCallback() self:onEnabled()
elseif (self.__enabled and not enable) then elseif (self.__enabled and not enable) then
self.__enabled = false self.__enabled = false
self:onDisabledCallback() self:onDisabled()
end end
return self return self
end end
--- Returns is the System is enabled
-- @return True if the System is enabled, false otherwise
function System:isEnabled() function System:isEnabled()
return self.__enabled return self.__enabled
end end
--- Returns the World the System is in. --- Returns the World the System is in.
-- @return The world the system is in -- @return The World the System is in
function System:getWorld() function System:getWorld()
return self.__world return self.__world
end end
--- Default callback for system initialization. --- Callback for system initialization.
-- @param world The World the System was added to -- @param world The World the System was added to
function System:init(world) -- luacheck: ignore function System:init(world) -- luacheck: ignore
end end
-- Default callback for when a System's callback is enabled. -- Callback for when a System is enabled.
-- @param callbackName The name of the callback that was enabled function System:onEnabled() -- luacheck: ignore
function System:onEnabledCallback(callbackName) -- luacheck: ignore
end end
-- Default callback for when a System's callback is disabled. -- Callback for when a System is disabled.
-- @param callbackName The name of the callback that was disabled function System:onDisabled() -- luacheck: ignore
function System:onDisabledCallback(callbackName) -- luacheck: ignore
end end
return setmetatable(System, { return setmetatable(System, {

View file

@ -1,4 +1,5 @@
-- Systems --- Systems
-- Container for registered SystemClasses
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
@ -6,20 +7,23 @@ local Type = require(PATH..".type")
local Systems = {} local Systems = {}
function Systems.register(name, system) --- Registers a SystemClass.
-- @param name Name to register under
-- @param systemClass SystemClass to register
function Systems.register(name, systemClass)
if (type(name) ~= "string") then if (type(name) ~= "string") then
error("bad argument #1 to 'Systems.register' (string expected, got "..type(name)..")", 3) error("bad argument #1 to 'Systems.register' (string expected, got "..type(name)..")", 3)
end end
if (not Type.isBaseSystem(system)) then if (not Type.isSystemClass(systemClass)) then
error("bad argument #2 to 'Systems.register' (baseSystem expected, got "..type(system)..")", 3) error("bad argument #2 to 'Systems.register' (systemClass expected, got "..type(systemClass)..")", 3)
end end
if (rawget(Systems, name)) then if (rawget(Systems, name)) then
error("bad argument #2 to 'Systems.register' (System with name '"..name.."' is already registerd)", 3) error("bad argument #2 to 'Systems.register' (System with name '"..name.."' is already registerd)", 3)
end end
Systems[name] = system Systems[name] = systemClass
end end
return setmetatable(Systems, { return setmetatable(Systems, {

View file

@ -1,31 +1,53 @@
-- Type --- Type
-- Helper module to do easy type checking for Concord types
local Type = {} local Type = {}
--- Returns if object is an Entity.
-- @param t Object to check
-- @return True if object is an Entity, false otherwise
function Type.isEntity(t) function Type.isEntity(t)
return type(t) == "table" and t.__isEntity or false return type(t) == "table" and t.__isEntity or false
end end
function Type.isBaseComponent(t) --- Returns if object is a ComponentClass.
return type(t) == "table" and t.__isBaseComponent or false -- @param t Object to check
-- @return True if object is an ComponentClass, false otherwise
function Type.isComponentClass(t)
return type(t) == "table" and t.__isComponentClass or false
end end
--- Returns if object is a Component.
-- @param t Object to check
-- @return True if object is an Component, false otherwise
function Type.isComponent(t) function Type.isComponent(t)
return type(t) == "table" and t.__isComponent or false return type(t) == "table" and t.__isComponent or false
end end
function Type.isBaseSystem(t) --- Returns if object is a SystemClass.
return type(t) == "table" and t.__isBaseSystem or false -- @param t Object to check
-- @return True if object is an SystemClass, false otherwise
function Type.isSystemClass(t)
return type(t) == "table" and t.__isSystemClass or false
end end
--- Returns if object is a System.
-- @param t Object to check
-- @return True if object is an System, false otherwise
function Type.isSystem(t) function Type.isSystem(t)
return type(t) == "table" and t.__isSystem or false return type(t) == "table" and t.__isSystem or false
end end
--- Returns if object is a World.
-- @param t Object to check
-- @return True if object is an World, false otherwise
function Type.isWorld(t) 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 an Assemblage.
-- @param t Object to check
-- @return True if object is an Assemblage, false otherwise
function Type.isAssemblage(t) function Type.isAssemblage(t)
return type(t) == "table" and t.__isAssemblage or false return type(t) == "table" and t.__isAssemblage or false
end end

View file

@ -1,5 +1,11 @@
--- Utils
-- Helper module for misc operations
local Utils = {} local Utils = {}
--- 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
function Utils.shallowCopy(orig, target) function Utils.shallowCopy(orig, target)
for key, value in pairs(orig) do for key, value in pairs(orig) do
target[key] = value target[key] = value

View file

@ -1,4 +1,8 @@
--- World --- World
-- A World is a collection of Systems and Entities
-- A world emits to let Systems iterate
-- A World contains any amount of Systems
-- A World contains any amount of Entities
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
@ -42,7 +46,7 @@ function World.new()
end end
--- Adds an Entity to the World. --- Adds an Entity to the World.
-- @param e The Entity to add -- @param e Entity to add
-- @return self -- @return self
function World:addEntity(e) function World:addEntity(e)
if not Type.isEntity(e) then if not Type.isEntity(e) then
@ -59,8 +63,8 @@ function World:addEntity(e)
return self return self
end end
--- Removes an entity from the World. --- Removes an Entity from the World.
-- @param e The Entity to mark -- @param e Entity to remove
-- @return self -- @return self
function World:removeEntity(e) function World:removeEntity(e)
if not Type.isEntity(e) then if not Type.isEntity(e) then
@ -72,13 +76,18 @@ function World:removeEntity(e)
return self return self
end end
--- Internal: Marks an Entity as dirty.
-- @see Entity:__dirty
-- @param e Entity to mark as dirty
function World:__dirtyEntity(e) function World:__dirtyEntity(e)
if not self.__dirty:has(e) then if not self.__dirty:has(e) then
self.__dirty:__add(e) self.__dirty:__add(e)
end end
end end
--- Internal: Flushes all changes to Entities.
-- This processes all entities. Adding and removing entities, as well as reevaluating dirty entities.
-- @see System:__evaluate
-- @return self -- @return self
function World:__flush() function World:__flush()
-- Early out -- Early out
@ -135,26 +144,37 @@ function World:__flush()
return self return self
end end
local blacklistedSystemMethods = { -- These functions won't be seen as callbacks that will be emitted to.
local blacklistedSystemFunctions = {
"init", "init",
"onEnabled",
"onDisabled",
} }
function World:addSystem(baseSystem) --- Adds a System to the World.
if (not Type.isBaseSystem(baseSystem)) then -- Callbacks are registered automatically
error("bad argument #1 to 'World:addSystems' (baseSystem expected, got "..type(baseSystem)..")", 2) -- Entities added before are added to the System retroactively
-- @see World:emit
-- @param systemClass SystemClass of System to add
-- @return self
function World:addSystem(systemClass)
if (not Type.isSystemClass(systemClass)) then
error("bad argument #1 to 'World:addSystems' (SystemClass expected, got "..type(systemClass)..")", 2)
end end
-- TODO: Check if baseSystem was already added if (self.__systemLookup[systemClass]) then
error("bad argument #1 to 'World:addSystems' (SystemClass was already added to World)", 2)
end
-- Create instance of system -- Create instance of system
local system = baseSystem(self) local system = systemClass(self)
self.__systemLookup[baseSystem] = system self.__systemLookup[systemClass] = system
self.systems:__add(system) self.systems:__add(system)
for callbackName, callback in pairs(baseSystem) do for callbackName, callback in pairs(systemClass) do
-- Skip callback if its blacklisted -- Skip callback if its blacklisted
if (not blacklistedSystemMethods[callbackName]) then if (not blacklistedSystemFunctions[callbackName]) then
-- Make container for all listeners of the callback if it does not exist yet -- Make container for all listeners of the callback if it does not exist yet
if (not self.events[callbackName]) then if (not self.events[callbackName]) then
self.events[callbackName] = {} self.events[callbackName] = {}
@ -173,43 +193,59 @@ function World:addSystem(baseSystem)
for j = 1, self.entities.size do for j = 1, self.entities.size do
system:__evaluate(self.entities[j]) system:__evaluate(self.entities[j])
end end
return self
end end
--- Adds multiple Systems to the World.
-- Callbacks are registered automatically
-- @see World:addSystem
-- @see World:emit
-- @param ... SystemClasses of Systems to add
-- @return self
function World:addSystems(...) function World:addSystems(...)
for i = 1, select("#", ...) do for i = 1, select("#", ...) do
local baseSystem = select(i, ...) local systemClass = select(i, ...)
self:addSystem(baseSystem) self:addSystem(systemClass)
end
end
function World:hasSystem(baseSystem)
if not Type.isBaseSystem(baseSystem) then
error("bad argument #1 to 'World:getSystem' (baseSystem expected, got "..type(baseSystem)..")", 2)
end end
return self.__systemLookup[baseSystem] and true or false return self
end end
function World:getSystem(baseSystem) --- Returns if the World has a System.
if not Type.isBaseSystem(baseSystem) then -- @param systemClass SystemClass of System to check for
error("bad argument #1 to 'World:getSystem' (baseSystem expected, got "..type(baseSystem)..")", 2) -- @return True if World has System, false otherwise
function World:hasSystem(systemClass)
if not Type.isSystemClass(systemClass) then
error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2)
end end
return self.__systemLookup[baseSystem] return self.__systemLookup[systemClass] and true or false
end end
--- Emits an Event in the World. --- Gets a System from the World.
-- @param eventName The Event that should be emitted -- @param systemClass SystemClass of System to get
-- @param ... Parameters passed to listeners -- @return System to get
function World:getSystem(systemClass)
if not Type.isSystemClass(systemClass) then
error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2)
end
return self.__systemLookup[systemClass]
end
--- Emits a callback in the World.
-- Calls all functions with the functionName of added Systems
-- @param functionName Name of functions to call.
-- @param ... Parameters passed to System's functions
-- @return self -- @return self
function World:emit(callbackName, ...) function World:emit(functionName, ...)
if not callbackName or type(callbackName) ~= "string" then if not functionName or type(functionName) ~= "string" then
error("bad argument #1 to 'World:emit' (String expected, got "..type(callbackName)..")") error("bad argument #1 to 'World:emit' (String expected, got "..type(functionName)..")")
end end
local listeners = self.events[callbackName] local listeners = self.events[functionName]
if listeners then if listeners then
for i = 1, #listeners do for i = 1, #listeners do
@ -233,15 +269,19 @@ function World:clear()
self:removeEntity(self.entities[i]) self:removeEntity(self.entities[i])
end end
for i = 1, self.systems.size do
self.systems[i]:clear()
end
return self return self
end end
--- Default callback for adding an Entity. --- Callback for when an Entity is added to the World.
-- @param e The Entity that was added -- @param e The Entity that was added
function World:onEntityAdded(e) -- luacheck: ignore function World:onEntityAdded(e) -- luacheck: ignore
end end
--- Default callback for removing an Entity. --- Callback for when an Entity is removed from the World.
-- @param e The Entity that was removed -- @param e The Entity that was removed
function World:onEntityRemoved(e) -- luacheck: ignore function World:onEntityRemoved(e) -- luacheck: ignore
end end

View file

@ -1,4 +1,5 @@
-- Worlds --- Worlds
-- Container for registered Worlds
local PATH = (...):gsub('%.[^%.]+$', '') local PATH = (...):gsub('%.[^%.]+$', '')
@ -6,6 +7,9 @@ local Type = require(PATH..".type")
local Worlds = {} local Worlds = {}
--- Registers a World.
-- @param name Name to register under
-- @param world World to register
function Worlds.register(name, world) function Worlds.register(name, world)
if (type(name) ~= "string") then if (type(name) ~= "string") then
error("bad argument #1 to 'Worlds.register' (string expected, got "..type(name)..")", 3) error("bad argument #1 to 'Worlds.register' (string expected, got "..type(name)..")", 3)