diff --git a/src/assemblage.lua b/src/assemblage.lua index 241525d..c2303f2 100644 --- a/src/assemblage.lua +++ b/src/assemblage.lua @@ -1,10 +1,15 @@ --- 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 = {} Assemblage.__mt = { __index = Assemblage, } +--- Creates a new Assemblage. +-- @param assemble Function that assembles an Entity +-- @return A new Assemblage function Assemblage.new(assemble) local assemblage = setmetatable({ __assemble = assemble, @@ -15,6 +20,11 @@ function Assemblage.new(assemble) return assemblage 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, ...) self.__assemble(e, ...) diff --git a/src/assemblages.lua b/src/assemblages.lua index f86f629..f34e3b9 100644 --- a/src/assemblages.lua +++ b/src/assemblages.lua @@ -1,4 +1,5 @@ --- Assemblages +--- Assemblages +-- Container for registered Assemblages local PATH = (...):gsub('%.[^%.]+$', '') @@ -6,17 +7,20 @@ local Type = require(PATH..".type") local Assemblages = {} +--- Registers an Assemblage. +-- @param name Name to register under +-- @param assemblage Assemblage to register function Assemblages.register(name, assemblage) if (type(name) ~= "string") then error("bad argument #1 to 'Assemblages.register' (string expected, got "..type(name)..")", 3) end if (not Type.isAssemblage(assemblage)) then - error("bad argument #2 to 'Assemblages.register' (assemblage expected, got "..type(world)..")", 3) + error("bad argument #2 to 'Assemblages.register' (assemblage expected, got "..type(assemblage)..")", 3) end if (rawget(Assemblages, name)) then - error("bad argument #2 to 'Assemblages.register' (Assemblage with name '"..name.."' is already registerd)", 3) + error("bad argument #2 to 'Assemblages.register' (Assemblage with name '"..name.."' was already registerd)", 3) end Assemblages[name] = assemblage diff --git a/src/component.lua b/src/component.lua index d288815..bf862a0 100644 --- a/src/component.lua +++ b/src/component.lua @@ -1,43 +1,46 @@ --- Component +-- A Component is a pure data container. +-- A Component is contained by a single entity. local Component = {} Component.__mt = { __index = Component, } ---- Creates a new Component. --- @param populate A function that populates the Bag with values --- @return A Component object +--- Creates a new ComponentClass. +-- @param populate Function that populates a Component with values +-- @return A new ComponentClass function Component.new(populate) if (type(populate) ~= "function" and type(populate) ~= "nil") then error("bad argument #1 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2) end - local baseComponent = setmetatable({ + local componentClass = setmetatable({ __populate = populate, - __isBaseComponent = true, + __isComponentClass = true, }, Component.__mt) - baseComponent.__mt = { - __index = baseComponent + componentClass.__mt = { + __index = componentClass } - return baseComponent + return componentClass end +--- Internal: Populates a Component with values function Component:__populate() -- luacheck: ignore end ---- Creates and initializes a new Component. --- @param ... The values passed to the populate function --- @return A new initialized Component +--- Internal: Creates and populates a new Component. +-- @param ... Varargs passed to the populate function +-- @return A new populated Component function Component:__initialize(...) local component = setmetatable({ - __baseComponent = self, + __componentClass = self, __isComponent = true, - __isBaseComponent = false, + __isComponentClass = false, }, self) self.__populate(component, ...) diff --git a/src/components.lua b/src/components.lua index 2249774..76780de 100644 --- a/src/components.lua +++ b/src/components.lua @@ -1,4 +1,5 @@ -- Components +-- Container for registered ComponentClasss local PATH = (...):gsub('%.[^%.]+$', '') @@ -6,24 +7,27 @@ local Type = require(PATH..".type") 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 error("bad argument #1 to 'Components.register' (string expected, got "..type(name)..")", 3) end - if (not Type.isBaseComponent(baseComponent)) then - error("bad argument #2 to 'Components.register' (BaseComponent expected, got "..type(baseComponent)..")", 3) + if (not Type.isComponentClass(componentClass)) then + error("bad argument #2 to 'Components.register' (ComponentClass expected, got "..type(componentClass)..")", 3) end 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 - Components[name] = baseComponent + Components[name] = componentClass end return setmetatable(Components, { __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 }) \ No newline at end of file diff --git a/src/entity.lua b/src/entity.lua index 38cc60d..c084039 100644 --- a/src/entity.lua +++ b/src/entity.lua @@ -1,4 +1,7 @@ --- 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('%.[^%.]+$', '') @@ -9,7 +12,8 @@ Entity.__mt = { __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 function Entity.new(world) if (world ~= nil and not Type.isWorld(world)) then @@ -30,63 +34,73 @@ function Entity.new(world) return e end -local function give(e, baseComponent, ...) - local component = baseComponent:__initialize(...) +local function give(e, componentClass, ...) + local component = componentClass:__initialize(...) - e[baseComponent] = component - e.__components[baseComponent] = component + e[componentClass] = component + e.__components[componentClass] = component e:__dirty() end -local function remove(e, baseComponent) - e[baseComponent] = nil - e.__components[baseComponent] = nil +local function remove(e, componentClass) + e[componentClass] = nil + e.__components[componentClass] = nil e:__dirty() end ---- Gives an Entity a component with values. --- @param component The Component to add --- @param ... The values passed to the Component +--- Gives an Entity a Component. +-- If the Component already exists, it's overridden by this new Component +-- @param componentClass ComponentClass to add an instance of +-- @param ... varargs passed to the Component's populate function -- @return self -function Entity:give(baseComponent, ...) - if not Type.isBaseComponent(baseComponent) then - error("bad argument #1 to 'Entity:give' (BaseComponent expected, got "..type(baseComponent)..")", 2) +function Entity:give(componentClass, ...) + if not Type.isComponentClass(componentClass) then + error("bad argument #1 to 'Entity:give' (ComponentClass expected, got "..type(componentClass)..")", 2) end - give(self, baseComponent, ...) + give(self, componentClass, ...) return self end -function Entity:ensure(baseComponent, ...) - if not Type.isBaseComponent(baseComponent) then - error("bad argument #1 to 'Entity:ensure' (BaseComponent expected, got "..type(baseComponent)..")", 2) +--- Ensures an Entity to have a Component. +-- If the Component already exists, no action is taken +-- @param componentClass ComponentClass to add an instance of +-- @param ... varargs passed to the Component's populate function +-- @return self +function Entity:ensure(componentClass, ...) + if not Type.isComponentClass(componentClass) then + error("bad argument #1 to 'Entity:ensure' (ComponentClass expected, got "..type(componentClass)..")", 2) end - if self[baseComponent] then + if self[componentClass] then return self end - give(self, baseComponent, ...) + give(self, componentClass, ...) return self end ---- Removes a component from an Entity. --- @param component The Component to remove +--- Removes a Component from an Entity. +-- @param componentClass ComponentClass of the Component to remove -- @return self -function Entity:remove(baseComponent) - if not Type.isBaseComponent(baseComponent) then - error("bad argument #1 to 'Entity:remove' (BaseComponent expected, got "..type(baseComponent)..")") +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, baseComponent) + 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, ...) if not Type.isAssemblage(assemblage) then error("bad argument #1 to 'Entity:assemble' (Assemblage expected, got "..type(assemblage)..")") @@ -98,6 +112,7 @@ function Entity:assemble(assemblage, ...) end --- Destroys the Entity. +-- Removes the Entity from it's World if it's in one. -- @return self function Entity:destroy() if self.__world then @@ -107,42 +122,54 @@ function Entity:destroy() return self end +--- Internal: Tells the World it's in that this Entity is dirty. +-- @return self function Entity:__dirty() if self.__world then self.__world:__dirtyEntity(self) 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 --- Gets a Component from the Entity. --- @param component The Component to get --- @return The Bag from the Component -function Entity:get(baseComponent) - if not Type.isBaseComponent(baseComponent) then - error("bad argument #1 to 'Entity:get' (BaseComponent expected, got "..type(baseComponent)..")") +-- @param componentClass ComponentClass of the Component to get +-- @return The Component +function Entity:get(componentClass) + if not Type.isComponentClass(componentClass) then + error("bad argument #1 to 'Entity:get' (ComponentClass expected, got "..type(componentClass)..")") end - return self[baseComponent] -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 + return self[componentClass] 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() return self.__components 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 end +--- Returns the World the Entity is in. +-- @return The World the Entity is in. function Entity:getWorld() return self.__world end @@ -151,4 +178,4 @@ return setmetatable(Entity, { __call = function(_, ...) return Entity.new(...) end, -}) +}) \ No newline at end of file diff --git a/src/init.lua b/src/init.lua index 66916db..b373989 100644 --- a/src/init.lua +++ b/src/init.lua @@ -47,13 +47,13 @@ Concord.assemblages = require(PATH..".assemblages") local function load(pathOrFiles, namespace) 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 if (type(pathOrFiles) == "string") then local info = love.filesystem.getInfo(pathOrFiles) -- luacheck: ignore 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 local files = love.filesystem.getDirectoryItems(pathOrFiles) @@ -82,18 +82,34 @@ local function load(pathOrFiles, namespace) 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) load(pathOrFiles, Concord.components) end +--- Loads SystemClasses and puts them in the Systems container. +-- Accepts a table of paths to files: {"system_1", "system_2", "etc"} +-- Accepts a path to a directory with SystemClasses: "systems" +--@see Systems function Concord.loadSystems(pathOrFiles) load(pathOrFiles, Concord.systems) end +--- Loads Worlds and puts them in the Worlds container. +-- Accepts a table of paths to files: {"world_1", "world_2", "etc"} +-- Accepts a path to a directory with Worlds: "worlds" +--@see Worlds function Concord.loadWorlds(pathOrFiles) load(pathOrFiles, Concord.worlds) 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) load(pathOrFiles, Concord.assemblages) end diff --git a/src/list.lua b/src/list.lua index 94fee52..38ff949 100644 --- a/src/list.lua +++ b/src/list.lua @@ -1,4 +1,5 @@ --- List +-- Data structure that allows for fast removal at the cost of containing order. local List = {} List.__mt = { @@ -6,7 +7,7 @@ List.__mt = { } --- Creates a new List. --- @return A new list +-- @return A new List function List.new() return setmetatable({ size = 0, @@ -14,9 +15,11 @@ function List.new() end --- 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 -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 self[size] = obj @@ -27,7 +30,7 @@ function List:__add(obj) -- obj can not be a number and also not the string "siz end --- Removes an object from the List. --- @param obj The object to remove +-- @param obj Object to remove -- @return self function List:__remove(obj) local index = self[obj] @@ -66,17 +69,23 @@ function List:__clear() return self end ---- Gets if the List has the object. --- @param obj The object to search for --- true if the list has the object, false otherwise +--- Returns true if the List has the object. +-- @param obj Object to check for +-- @return True if the List has the object, false otherwise function List:has(obj) return self[obj] and true or false end +--- Returns the object at an index. +-- @param i Index to get from +-- @return Object at the index function List:get(i) return self[i] 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) if (not self[obj]) then error("bad argument #1 to 'List:indexOf' (Object was not in List)", 2) diff --git a/src/pool.lua b/src/pool.lua index 1b7be39..4cde567 100644 --- a/src/pool.lua +++ b/src/pool.lua @@ -1,4 +1,6 @@ --- Pool +-- A Pool is used to iterate over Entities with a specific Components +-- A Pool contain a any amount of Entities. local PATH = (...):gsub('%.[^%.]+$', '') @@ -10,8 +12,8 @@ Pool.__mt = { } --- Creates a new Pool --- @param name Identifier for the Pool. --- @param filter Table containing the required Components +-- @param name Name for the Pool. +-- @param filter Table containing the required BaseComponents -- @return The new Pool function Pool.new(name, filter) local pool = setmetatable(List(), Pool.__mt) @@ -25,7 +27,7 @@ function Pool.new(name, filter) end --- 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 function Pool:__eligible(e) for _, component in ipairs(self.__filter) do @@ -37,27 +39,46 @@ function Pool:__eligible(e) return true end +--- Internal: Adds an Entity to the Pool. +-- @param e Entity to add +-- @return self function Pool:__add(e) List.__add(self, e) self:onEntityAdded(e) + + return self end +--- Internal: Removed an Entity from the Pool. +-- @param e Entity to remove +-- @return self function Pool:__remove(e) List.__remove(self, e) self:onEntityRemoved(e) + + return self end +--- Gets the name of the Pool +-- @return Name of the Pool. 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. +-- @param e Entity that was added. function Pool:onEntityAdded(e) -- luacheck: ignore end +-- Callback for when an Entity is removed from the Pool. +-- @param e Entity that was removed. function Pool:onEntityRemoved(e) -- luacheck: ignore end diff --git a/src/system.lua b/src/system.lua index 4e0b778..5c12d39 100644 --- a/src/system.lua +++ b/src/system.lua @@ -1,4 +1,7 @@ --- 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('%.[^%.]+$', '') @@ -11,7 +14,7 @@ local System = { System.mt = { __index = System, - __call = function(baseSystem, world) + __call = function(systemClass, world) local system = setmetatable({ __enabled = true, @@ -19,8 +22,8 @@ System.mt = { __world = world, __isSystem = true, - __isBaseSystem = false, -- Overwrite value from baseSystem - }, baseSystem) + __isSystemClass = false, -- Overwrite value from systemClass + }, systemClass) -- Optimization: We deep copy the World class into our instance of a world. -- This grants slightly faster access times at the cost of memory. @@ -29,8 +32,8 @@ System.mt = { Utils.shallowCopy(System, system) end - for _, filter in pairs(baseSystem.__filter) do - local pool = system:__buildPool(filter) + for _, filter in pairs(systemClass.__filter) do + local pool = system.__buildPool(filter) if not system[pool.__name] then system[pool.__name] = pool system.__pools[#system.__pools + 1] = pool @@ -45,39 +48,40 @@ System.mt = { end, } ---- Creates a new System prototype. +--- Creates a new SystemClass. -- @param ... Variable amounts of filters --- @return A new System prototype +-- @return A new SystemClass function System.new(...) - local baseSystem = setmetatable({ - __isBaseSystem = true, + local systemClass = setmetatable({ + __isSystemClass = true, __filter = {...}, }, System.mt) - baseSystem.__index = baseSystem + systemClass.__index = systemClass - return baseSystem + return systemClass end ---- Builds a Pool for the System. +--- Internal: Builds a Pool for the System. -- @param baseFilter The 'raw' Filter -- @return A new Pool -function System:__buildPool(baseFilter) -- luacheck: ignore +function System.__buildPool(baseFilter) local name = "pool" local filter = {} - for _, v in ipairs(baseFilter) do - if type(v) == "table" then - filter[#filter + 1] = v - elseif type(v) == "string" then - name = v + for _, value in ipairs(baseFilter) do + if type(value) == "table" then + filter[#filter + 1] = value + elseif type(value) == "string" then + name = value end end return Pool(name, filter) end ---- 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 +-- @return self function System:__evaluate(e) for _, pool in ipairs(self.__pools) do local has = pool:has(e) @@ -93,8 +97,9 @@ function System:__evaluate(e) return self end ---- Remove an Entity from the System. +--- Internal: Removes an Entity from the System. -- @param e The Entity to remove +-- @return self function System:__remove(e) for _, pool in ipairs(self.__pools) do if pool:has(e) then @@ -105,6 +110,8 @@ function System:__remove(e) return self end +--- Internal: Clears all Entities from the System. +-- @return self function System:clear() for i = 1, #self.__pools do self.__pools[i]:__clear() @@ -113,59 +120,68 @@ function System:clear() return self end +--- Enables the System. +-- @return self function System:enable() self:setEnabled(true) return self end +--- Disables the System. +-- @return self function System:disable() self:setEnabled(false) return self end +--- Toggles if the System is enabled. +-- @return self function System:toggleEnable() self:setEnabled(not self.__enabled) return self end +--- Sets if the System is enabled +-- @param enable Enable +-- @return self function System:setEnabled(enable) if (not self.__enabled and enable) then self.__enabled = true - self:onEnabledCallback() + self:onEnabled() elseif (self.__enabled and not enable) then self.__enabled = false - self:onDisabledCallback() + self:onDisabled() end return self end +--- Returns is the System is enabled +-- @return True if the System is enabled, false otherwise function System:isEnabled() return self.__enabled end --- Returns the World the System is in. --- @return The world the system is in +-- @return The World the System is in function System:getWorld() return self.__world end ---- Default callback for system initialization. +--- Callback for system initialization. -- @param world The World the System was added to function System:init(world) -- luacheck: ignore end --- Default callback for when a System's callback is enabled. --- @param callbackName The name of the callback that was enabled -function System:onEnabledCallback(callbackName) -- luacheck: ignore +-- Callback for when a System is enabled. +function System:onEnabled() -- luacheck: ignore end --- Default callback for when a System's callback is disabled. --- @param callbackName The name of the callback that was disabled -function System:onDisabledCallback(callbackName) -- luacheck: ignore +-- Callback for when a System is disabled. +function System:onDisabled() -- luacheck: ignore end return setmetatable(System, { diff --git a/src/systems.lua b/src/systems.lua index 4f84472..81e6b2e 100644 --- a/src/systems.lua +++ b/src/systems.lua @@ -1,4 +1,5 @@ --- Systems +--- Systems +-- Container for registered SystemClasses local PATH = (...):gsub('%.[^%.]+$', '') @@ -6,20 +7,23 @@ local Type = require(PATH..".type") 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 error("bad argument #1 to 'Systems.register' (string expected, got "..type(name)..")", 3) end - if (not Type.isBaseSystem(system)) then - error("bad argument #2 to 'Systems.register' (baseSystem expected, got "..type(system)..")", 3) + if (not Type.isSystemClass(systemClass)) then + error("bad argument #2 to 'Systems.register' (systemClass expected, got "..type(systemClass)..")", 3) end if (rawget(Systems, name)) then error("bad argument #2 to 'Systems.register' (System with name '"..name.."' is already registerd)", 3) end - Systems[name] = system + Systems[name] = systemClass end return setmetatable(Systems, { diff --git a/src/type.lua b/src/type.lua index a7ba264..9a882a8 100644 --- a/src/type.lua +++ b/src/type.lua @@ -1,31 +1,53 @@ --- Type +--- Type +-- Helper module to do easy type checking for Concord types 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) return type(t) == "table" and t.__isEntity or false end -function Type.isBaseComponent(t) - return type(t) == "table" and t.__isBaseComponent or false +--- Returns if object is a ComponentClass. +-- @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 +--- Returns if object is a Component. +-- @param t Object to check +-- @return True if object is an Component, false otherwise function Type.isComponent(t) return type(t) == "table" and t.__isComponent or false end -function Type.isBaseSystem(t) - return type(t) == "table" and t.__isBaseSystem or false +--- Returns if object is a SystemClass. +-- @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 +--- Returns if object is a System. +-- @param t Object to check +-- @return True if object is an System, false otherwise function Type.isSystem(t) return type(t) == "table" and t.__isSystem or false 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) return type(t) == "table" and t.__isWorld or false 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) return type(t) == "table" and t.__isAssemblage or false end diff --git a/src/utils.lua b/src/utils.lua index 43adf6c..6372b5b 100644 --- a/src/utils.lua +++ b/src/utils.lua @@ -1,5 +1,11 @@ +--- Utils +-- Helper module for misc operations + 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) for key, value in pairs(orig) do target[key] = value diff --git a/src/world.lua b/src/world.lua index f15a14d..a8c7953 100644 --- a/src/world.lua +++ b/src/world.lua @@ -1,4 +1,8 @@ --- 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('%.[^%.]+$', '') @@ -42,7 +46,7 @@ function World.new() end --- Adds an Entity to the World. --- @param e The Entity to add +-- @param e Entity to add -- @return self function World:addEntity(e) if not Type.isEntity(e) then @@ -59,8 +63,8 @@ function World:addEntity(e) return self end ---- Removes an entity from the World. --- @param e The Entity to mark +--- Removes an Entity from the World. +-- @param e Entity to remove -- @return self function World:removeEntity(e) if not Type.isEntity(e) then @@ -72,13 +76,18 @@ function World:removeEntity(e) return self end +--- Internal: Marks an Entity as dirty. +-- @see Entity:__dirty +-- @param e Entity to mark as dirty function World:__dirtyEntity(e) if not self.__dirty:has(e) then self.__dirty:__add(e) 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 function World:__flush() -- Early out @@ -135,26 +144,37 @@ function World:__flush() return self end -local blacklistedSystemMethods = { +-- These functions won't be seen as callbacks that will be emitted to. +local blacklistedSystemFunctions = { "init", + "onEnabled", + "onDisabled", } -function World:addSystem(baseSystem) - if (not Type.isBaseSystem(baseSystem)) then - error("bad argument #1 to 'World:addSystems' (baseSystem expected, got "..type(baseSystem)..")", 2) +--- Adds a System to the World. +-- Callbacks are registered automatically +-- 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 - -- 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 - local system = baseSystem(self) + local system = systemClass(self) - self.__systemLookup[baseSystem] = system + self.__systemLookup[systemClass] = system self.systems:__add(system) - for callbackName, callback in pairs(baseSystem) do + for callbackName, callback in pairs(systemClass) do -- 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 if (not self.events[callbackName]) then self.events[callbackName] = {} @@ -173,43 +193,59 @@ function World:addSystem(baseSystem) for j = 1, self.entities.size do system:__evaluate(self.entities[j]) end + + return self 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(...) for i = 1, select("#", ...) do - local baseSystem = select(i, ...) + local systemClass = select(i, ...) - self:addSystem(baseSystem) - 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) + self:addSystem(systemClass) end - return self.__systemLookup[baseSystem] and true or false + return self end -function World:getSystem(baseSystem) - if not Type.isBaseSystem(baseSystem) then - error("bad argument #1 to 'World:getSystem' (baseSystem expected, got "..type(baseSystem)..")", 2) +--- Returns if the World has a System. +-- @param systemClass SystemClass of System to check for +-- @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 - return self.__systemLookup[baseSystem] + return self.__systemLookup[systemClass] and true or false end ---- Emits an Event in the World. --- @param eventName The Event that should be emitted --- @param ... Parameters passed to listeners +--- Gets a System from the World. +-- @param systemClass SystemClass of System to get +-- @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 -function World:emit(callbackName, ...) - if not callbackName or type(callbackName) ~= "string" then - error("bad argument #1 to 'World:emit' (String expected, got "..type(callbackName)..")") +function World:emit(functionName, ...) + if not functionName or type(functionName) ~= "string" then + error("bad argument #1 to 'World:emit' (String expected, got "..type(functionName)..")") end - local listeners = self.events[callbackName] + local listeners = self.events[functionName] if listeners then for i = 1, #listeners do @@ -233,15 +269,19 @@ function World:clear() self:removeEntity(self.entities[i]) end + for i = 1, self.systems.size do + self.systems[i]:clear() + end + return self end ---- Default callback for adding an Entity. +--- Callback for when an Entity is added to the World. -- @param e The Entity that was added function World:onEntityAdded(e) -- luacheck: ignore end ---- Default callback for removing an Entity. +--- Callback for when an Entity is removed from the World. -- @param e The Entity that was removed function World:onEntityRemoved(e) -- luacheck: ignore end diff --git a/src/worlds.lua b/src/worlds.lua index 2f8f5a8..0ec2574 100644 --- a/src/worlds.lua +++ b/src/worlds.lua @@ -1,4 +1,5 @@ --- Worlds +--- Worlds +-- Container for registered Worlds local PATH = (...):gsub('%.[^%.]+$', '') @@ -6,6 +7,9 @@ local Type = require(PATH..".type") local Worlds = {} +--- Registers a World. +-- @param name Name to register under +-- @param world World to register function Worlds.register(name, world) if (type(name) ~= "string") then error("bad argument #1 to 'Worlds.register' (string expected, got "..type(name)..")", 3)