From c640641b0916b2e996a4c960f6115910b3b1d63c Mon Sep 17 00:00:00 2001 From: Pablo Ariel Mayobre Date: Sat, 14 Mar 2020 07:45:25 -0300 Subject: [PATCH 01/12] CONCORD IS DEAD Long live Concord!! --- concord/assemblage.lua | 49 -------------- concord/assemblages.lua | 49 -------------- concord/component.lua | 18 ++++- concord/components.lua | 45 ++++++++----- concord/entity.lua | 80 ++++++++++++---------- concord/init.lua | 36 +++++----- concord/list.lua | 6 +- concord/pool.lua | 47 +++++++++---- concord/system.lua | 110 +++++++++++++------------------ concord/systems.lua | 48 -------------- concord/type.lua | 7 -- concord/world.lua | 54 ++++++++------- concord/worlds.lua | 48 -------------- examples/assemblageTest/init.lua | 38 +++++------ examples/serialization/init.lua | 14 ++-- examples/simpleDrawing/init.lua | 38 +++++------ 16 files changed, 263 insertions(+), 424 deletions(-) delete mode 100644 concord/assemblage.lua delete mode 100644 concord/assemblages.lua delete mode 100644 concord/systems.lua delete mode 100644 concord/worlds.lua diff --git a/concord/assemblage.lua b/concord/assemblage.lua deleted file mode 100644 index b90fb36..0000000 --- a/concord/assemblage.lua +++ /dev/null @@ -1,49 +0,0 @@ ---- Gives an entity a set of components. --- @classmod Assemblage - -local Assemblage = {} -Assemblage.__mt = { - __index = Assemblage, -} - ---- Creates a new Assemblage. --- @tparam function assemble Function that assembles an Entity --- @treturn Assemblage A new assemblage -function Assemblage.new(assemble) - local assemblage = setmetatable({ - __assemble = assemble, - - __name = nil, - __isAssemblage = true, - }, Assemblage.__mt) - - return assemblage -end - ---- Assembles an Entity. --- @tparam Entity e Entity to assemble --- @param ... additional arguments to pass to the assemble function --- @treturn Assemblage self -function Assemblage:assemble(e, ...) - self.__assemble(e, ...) - - return self -end - ---- Returns true if the Assemblage has a name. --- @treturn boolean -function Assemblage:hasName() - return self.__name and true or false -end - ---- Returns the name of the Assemblage. --- @treturn string -function Assemblage:getName() - return self.__name -end - -return setmetatable(Assemblage, { - __call = function(_, ...) - return Assemblage.new(...) - end, -}) diff --git a/concord/assemblages.lua b/concord/assemblages.lua deleted file mode 100644 index 58731a8..0000000 --- a/concord/assemblages.lua +++ /dev/null @@ -1,49 +0,0 @@ ---- A container for registered @{Assemblage}s --- @module Assemblages - -local PATH = (...):gsub('%.[^%.]+$', '') - -local Type = require(PATH..".type") - -local Assemblages = {} - ---- Registers an Assemblage. --- @string name Name to register under --- @tparam Assemblage 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(assemblage)..")", 3) - end - - if (rawget(Assemblages, name)) then - error("bad argument #2 to 'Assemblages.register' (Assemblage with name '"..name.."' was already registerd)", 3) - end - - Assemblages[name] = assemblage - assemblage.__name = name -end - ---- Returns true if the containter has an Assemblage with the specified name --- @string name Name of the Assemblage to check --- @treturn boolean -function Assemblages.has(name) - return Assemblages[name] and true or false -end - ---- Returns the Assemblage with the specified name --- @string name Name of the Assemblage to get --- @treturn Assemblage -function Assemblages.get(name) - return Assemblages[name] -end - - -return setmetatable(Assemblages, { - __index = function(_, name) - error("Attempt to index assemblage '"..tostring(name).."' that does not exist / was not registered", 2) - end -}) diff --git a/concord/component.lua b/concord/component.lua index 1b391ee..0ac83e6 100644 --- a/concord/component.lua +++ b/concord/component.lua @@ -1,6 +1,10 @@ --- A pure data container that is contained by a single entity. -- @classmod Component +local PATH = (...):gsub('%.[^%.]+$', '') + +local Components = require(PATH..".components") + local Component = {} Component.__mt = { __index = Component, @@ -9,7 +13,15 @@ Component.__mt = { --- Creates a new ComponentClass. -- @tparam function populate Function that populates a Component with values -- @treturn Component A new ComponentClass -function Component.new(populate) +function Component.new(name, populate) + if (type(name) ~= "string") then + error("bad argument #1 to 'Component.new' (string expected, got "..type(name)..")", 2) + end + + if (rawget(Components, name)) then + error("bad argument #1 to 'Component.new' (ComponentClass with name '"..name.."' was already registerd)", 2) -- luacheck: ignore + end + if (type(populate) ~= "function" and type(populate) ~= "nil") then error("bad argument #1 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2) end @@ -17,7 +29,7 @@ function Component.new(populate) local componentClass = setmetatable({ __populate = populate, - __name = nil, + __name = name, __isComponentClass = true, }, Component.__mt) @@ -25,6 +37,8 @@ function Component.new(populate) __index = componentClass } + Components[name] = componentClass + return componentClass end diff --git a/concord/components.lua b/concord/components.lua index d7063da..e4116fd 100644 --- a/concord/components.lua +++ b/concord/components.lua @@ -7,42 +7,51 @@ local Type = require(PATH..".type") local Components = {} ---- Registers a ComponentClass. --- @string name Name to register under --- @tparam Component 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) +local try = function (name) + if type(name) ~= "string" then + return false, "ComponentsClass name is expected to be a string, got "..type(name)..")" end - if (not Type.isComponentClass(componentClass)) then - error("bad argument #2 to 'Components.register' (ComponentClass expected, got "..type(componentClass)..")", 3) + local value = rawget(Components, name) + if not value then + return false, "ComponentClass '"..name.."' does not exist / was not registered" end - if (rawget(Components, name)) then - error("bad argument #2 to 'Components.register' (ComponentClass with name '"..name.."' was already registerd)", 3) -- luacheck: ignore - end - - Components[name] = componentClass - componentClass.__name = name + return true, value end --- Returns true if the containter has the ComponentClass with the specified name -- @string name Name of the ComponentClass to check -- @treturn boolean function Components.has(name) - return Components[name] and true or false + return rawget(Components, name) and true or false +end + +--- Returns true and the ComponentClass if one was registered with the specified name +-- or false and an error otherwise +-- @string name Name of the ComponentClass to check +-- @treturn boolean +-- @treturn Component or error string +function Components.try(name) + return try(name) end --- Returns the ComponentClass with the specified name -- @string name Name of the ComponentClass to get -- @treturn Component function Components.get(name) - return Components[name] + local ok, value = try(name) + + if not ok then error(value, 2) end + + return value end return setmetatable(Components, { __index = function(_, name) - error("Attempt to index ComponentClass '"..tostring(name).."' that does not exist / was not registered", 2) - end + local ok, value = try(name) + + if not ok then error(value, 2) end + + return value end }) diff --git a/concord/entity.lua b/concord/entity.lua index da446e5..acafe01 100644 --- a/concord/entity.lua +++ b/concord/entity.lua @@ -34,18 +34,18 @@ function Entity.new(world) return e end -local function give(e, componentClass, ...) +local function give(e, name, componentClass, ...) local component = componentClass:__initialize(...) - e[componentClass] = component - e.__components[componentClass] = component + e[name] = component + e.__components[name] = component e:__dirty() end -local function remove(e, componentClass) - e[componentClass] = nil - e.__components[componentClass] = nil +local function remove(e, name, componentClass) + e[name] = nil + e.__components[name] = nil e:__dirty() end @@ -55,12 +55,14 @@ end -- @tparam Component componentClass ComponentClass to add an instance of -- @param ... additional arguments to pass to the Component's populate function -- @treturn Entity self -function Entity:give(componentClass, ...) - if not Type.isComponentClass(componentClass) then - error("bad argument #1 to 'Entity:give' (ComponentClass expected, got "..type(componentClass)..")", 2) +function Entity:give(name, ...) + local ok, componentClass = Components.try(name) + + if not ok then + error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2) end - give(self, componentClass, ...) + give(self, name, componentClass, ...) return self end @@ -70,16 +72,18 @@ end -- @tparam Component componentClass ComponentClass to add an instance of -- @param ... additional arguments to pass to the Component's populate function -- @treturn Entity self -function Entity:ensure(componentClass, ...) - if not Type.isComponentClass(componentClass) then - error("bad argument #1 to 'Entity:ensure' (ComponentClass expected, got "..type(componentClass)..")", 2) +function Entity:ensure(name, ...) + local ok, componentClass = Components.try(name) + + if not ok then + error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2) end - if self[componentClass] then + if self[name] then return self end - give(self, componentClass, ...) + give(self, name, componentClass, ...) return self end @@ -87,26 +91,28 @@ end --- Removes a Component from an Entity. -- @tparam Component componentClass ComponentClass of the Component to remove -- @treturn Entity self -function Entity:remove(componentClass) - if not Type.isComponentClass(componentClass) then - error("bad argument #1 to 'Entity:remove' (ComponentClass expected, got "..type(componentClass)..")") +function Entity:remove(name) + local ok, componentClass = Components.try(name) + + if not ok then + error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2) end - remove(self, componentClass) + remove(self, name, componentClass) return self end --- Assembles an Entity. --- @tparam Assemblage assemblage Assemblage to assemble with --- @param ... additional arguments to pass to the Assemblage's assemble function. +-- @tparam function assemblage Function that will assemble an entity +-- @param ... additional arguments to pass to the assemblage function. -- @treturn Entity self function Entity:assemble(assemblage, ...) - if not Type.isAssemblage(assemblage) then - error("bad argument #1 to 'Entity:assemble' (Assemblage expected, got "..type(assemblage)..")") + if type(assemblage) ~= "function" then + error("bad argument #1 to 'Entity:assemble' (function expected, got "..type(assemblage)..")") end - assemblage:assemble(self, ...) + assemblage(self, ...) return self end @@ -135,23 +141,27 @@ end --- Returns true if the Entity has a Component. -- @tparam Component componentClass ComponentClass of the Component to check -- @treturn boolean -function Entity:has(componentClass) - if not Type.isComponentClass(componentClass) then - error("bad argument #1 to 'Entity:has' (ComponentClass expected, got "..type(componentClass)..")") +function Entity:has(name) + local ok, componentClass = Components.try(name) + + if not ok then + error("bad argument #1 to 'Entity:has' ("..componentClass..")", 2) end - return self[componentClass] ~= nil + return self[name] and true or false end --- Gets a Component from the Entity. -- @tparam Component componentClass ComponentClass of the Component to get -- @treturn table -function Entity:get(componentClass) - if not Type.isComponentClass(componentClass) then - error("bad argument #1 to 'Entity:get' (ComponentClass expected, got "..type(componentClass)..")") +function Entity:get(name) + local ok, componentClass = Components.try(name) + + if not ok then + error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2) end - return self[componentClass] + return self[name] end --- Returns a table of all Components the Entity has. @@ -193,7 +203,7 @@ function Entity:deserialize(data) for i = 1, #data do local componentData = data[i] - if (not Components[componentData.__name]) then + if (not Components.has(componentData.__name)) then error("bad argument #1 to 'Entity:deserialize' (ComponentClass "..type(componentData.__name).." wasn't yet loaded)") -- luacheck: ignore end @@ -202,8 +212,8 @@ function Entity:deserialize(data) local component = componentClass:__new() component:deserialize(componentData) - self[componentClass] = component - self.__components[componentClass] = component + self[componentData.__name] = component + self.__components[componentData.__name] = component self:__dirty() end diff --git a/concord/init.lua b/concord/init.lua index 7ff46d3..1653c46 100644 --- a/concord/init.lua +++ b/concord/init.lua @@ -38,13 +38,8 @@ Concord.component = require(PATH..".component") Concord.components = require(PATH..".components") Concord.system = require(PATH..".system") -Concord.systems = require(PATH..".systems") Concord.world = require(PATH..".world") -Concord.worlds = require(PATH..".worlds") - -Concord.assemblage = require(PATH..".assemblage") -Concord.assemblages = require(PATH..".assemblages") local function load(pathOrFiles, namespace) if (type(pathOrFiles) ~= "string" and type(pathOrFiles) ~= "table") then @@ -63,7 +58,8 @@ local function load(pathOrFiles, namespace) local name = file:sub(1, #file - 4) local path = pathOrFiles.."."..name - namespace.register(name, require(path)) + local value = require(path) + if namespace then namespace[name] = value end end elseif (type(pathOrFiles == "table")) then for _, path in ipairs(pathOrFiles) do @@ -78,37 +74,43 @@ local function load(pathOrFiles, namespace) name = path:sub((dotIndex or slashIndex) + 1) end - namespace.register(name, require(path)) + local value = require(path) + if namespace then namespace[name] = value end end end + + return namespace 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" function Concord.loadComponents(pathOrFiles) - load(pathOrFiles, Concord.components) + load(pathOrFiles, nil) + return 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" -function Concord.loadSystems(pathOrFiles) - load(pathOrFiles, Concord.systems) +function Concord.loadSystems(pathOrFiles, world) + local systems = load(pathOrFiles, {}) + + if world then + for _, system in pairs(systems) do + world:addSystem(system) + end + end + + return 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" function Concord.loadWorlds(pathOrFiles) - load(pathOrFiles, Concord.worlds) + return load(pathOrFiles, {}) 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" -function Concord.loadAssemblages(pathOrFiles) - load(pathOrFiles, Concord.assemblages) -end return Concord diff --git a/concord/list.lua b/concord/list.lua index 215d1bb..1263a72 100644 --- a/concord/list.lua +++ b/concord/list.lua @@ -19,7 +19,7 @@ end -- Object may not be the string 'size' -- @param obj Object to add -- @treturn List self -function List:__add(obj) +function List:add(obj) local size = self.size + 1 self[size] = obj @@ -32,7 +32,7 @@ end --- Removes an object from the List. -- @param obj Object to remove -- @treturn List self -function List:__remove(obj) +function List:remove(obj) local index = self[obj] if not index then return end local size = self.size @@ -56,7 +56,7 @@ end --- Clears the List completely. -- @treturn List self -function List:__clear() +function List:clear() for i = 1, self.size do local o = self[i] diff --git a/concord/pool.lua b/concord/pool.lua index ef22aed..e74227a 100644 --- a/concord/pool.lua +++ b/concord/pool.lua @@ -29,32 +29,53 @@ end --- Checks if an Entity is eligible for the Pool. -- @tparam Entity e Entity to check -- @treturn boolean -function Pool:__eligible(e) - for _, component in ipairs(self.__filter) do - if not e[component] then - return false - end +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 --- Internal: Adds an Entity to the Pool. +-- Adds an Entity to the Pool, if it can be eligible. -- @param e Entity to add -- @treturn Pool self -function Pool:__add(e) - List.__add(self, e) +-- @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 --- Internal: Removed an Entity from the Pool. --- @param e Entity to remove +--- Evaluate whether an Entity should be added or removed from the Pool. +-- @param e Entity to add or remove -- @treturn Pool self -function Pool:__remove(e) - List.__remove(self, e) - self:onEntityRemoved(e) +function Pool:evaluate(e) + local has = self:has(e) + local eligible = self:eligible(e) + + if not has and eligible then + self:add(e) + elseif has and not eligible then + self:remove(e) + end return self end diff --git a/concord/system.lua b/concord/system.lua index 6ff374e..3583010 100644 --- a/concord/system.lua +++ b/concord/system.lua @@ -5,8 +5,9 @@ local PATH = (...):gsub('%.[^%.]+$', '') -local Pool = require(PATH..".pool") -local Utils = require(PATH..".utils") +local Pool = require(PATH..".pool") +local Utils = require(PATH..".utils") +local Components = require(PATH..".components") local System = { ENABLE_OPTIMIZATION = true, @@ -25,21 +26,18 @@ System.mt = { __isSystemClass = false, -- Overwrite value from systemClass }, systemClass) - -- Optimization: We deep copy the World class into our instance of a world. + -- Optimization: We deep copy the System class into our instance of a system. -- This grants slightly faster access times at the cost of memory. -- Since there (generally) won't be many instances of worlds this is a worthwhile tradeoff if (System.ENABLE_OPTIMIZATION) then Utils.shallowCopy(systemClass, system) end - 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 - else - error("Pool with name '"..pool.name.."' already exists.") - end + for name, filter in pairs(systemClass.__filter) do + local pool = Pool(name, filter) + + system[name] = pool + system.__pools[#system.__pools + 1] = pool end system:init(world) @@ -48,12 +46,41 @@ System.mt = { end, } +local validateFilters = function (baseFilters) + local filters = {} + + for name, componentsList in pairs(baseFilters) do + if type(name) ~= 'string' then + error("Invalid name for filter (string key expected, got "..type(name)..")", 3) + end + + if type(componentsList) ~= 'table' then + error("Invalid component list for filter '"..name.."' (table expected, got "..type(componentsList)..")", 3) + end + + local filter = {} + for n, component in ipairs(componentsList) do + local ok, componentClass = Components.try(component) + + if not ok then + error("Invalid component for filter '"..name.."' at position #"..n.." ("..componentClass..")", 3) + end + + filter[#filter + 1] = componentClass + end + + filters[name] = filter + end + + return filters +end + --- Creates a new SystemClass. --- @param ... Variable amounts of filters +-- @param table filters A table containing filters (name = {components...}) -- @treturn System A new SystemClass -function System.new(...) +function System.new(filters) local systemClass = setmetatable({ - __filter = {...}, + __filter = validateFilters(filters), __name = nil, __isSystemClass = true, @@ -70,37 +97,12 @@ function System.new(...) return systemClass end --- Internal: Builds a Pool for the System. --- @param baseFilter The 'raw' Filter --- @return A new Pool -function System.__buildPool(baseFilter) - local name = "pool" - local filter = {} - - 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 - -- Internal: Evaluates an Entity for all the System's Pools. -- @param e The Entity to check -- @treturn System self function System:__evaluate(e) for _, pool in ipairs(self.__pools) do - local has = pool:has(e) - local eligible = pool:__eligible(e) - - if not has and eligible then - pool:__add(e) - elseif has and not eligible then - pool:__remove(e) - end + pool:evaluate(e) end return self @@ -112,7 +114,7 @@ end function System:__remove(e) for _, pool in ipairs(self.__pools) do if pool:has(e) then - pool:__remove(e) + pool:remove(e) end end @@ -123,36 +125,12 @@ end -- @treturn System self function System:__clear() for i = 1, #self.__pools do - self.__pools[i]:__clear() + self.__pools[i]:clear() end return self end ---- Enables the System. --- @treturn System self -function System:enable() - self:setEnabled(true) - - return self -end - ---- Disables the System. --- @treturn System self -function System:disable() - self:setEnabled(false) - - return self -end - ---- Toggles if the System is enabled. --- @treturn System self -function System:toggleEnabled() - self:setEnabled(not self.__enabled) - - return self -end - --- Sets if the System is enabled -- @tparam boolean enable -- @treturn System self diff --git a/concord/systems.lua b/concord/systems.lua deleted file mode 100644 index f29550d..0000000 --- a/concord/systems.lua +++ /dev/null @@ -1,48 +0,0 @@ ---- Container for registered SystemClasses --- @module Systems - -local PATH = (...):gsub('%.[^%.]+$', '') - -local Type = require(PATH..".type") - -local Systems = {} - ---- Registers a SystemClass. --- @tparam string name Name to register under --- @tparam System 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.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] = systemClass - systemClass.__name = name -end - ---- Returns true if the containter has the SystemClass with the name --- @tparam string name Name of the SystemClass to check --- @treturn boolean -function Systems.has(name) - return Systems[name] and true or false -end - ---- Returns the SystemClass with the name --- @tparam string name Name of the SystemClass to get --- @return SystemClass with the name -function Systems.get(name) - return Systems[name] -end - -return setmetatable(Systems, { - __index = function(_, name) - error("Attempt to index system '"..tostring(name).."' that does not exist / was not registered", 2) - end -}) diff --git a/concord/type.lua b/concord/type.lua index 62ef0f9..157a8d4 100644 --- a/concord/type.lua +++ b/concord/type.lua @@ -45,11 +45,4 @@ 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 --- @treturn boolean -function Type.isAssemblage(t) - return type(t) == "table" and t.__isAssemblage or false -end - return Type diff --git a/concord/world.lua b/concord/world.lua index 70551c0..f42a608 100644 --- a/concord/world.lua +++ b/concord/world.lua @@ -61,7 +61,7 @@ function World:addEntity(e) end e.__world = self - self.__added:__add(e) + self.__added:add(e) return self end @@ -74,7 +74,7 @@ function World:removeEntity(e) error("bad argument #1 to 'World:removeEntity' (Entity expected, got "..type(e)..")", 2) end - self.__removed:__add(e) + self.__removed:add(e) return self end @@ -83,7 +83,7 @@ end -- @param e Entity to mark as dirty function World:__dirtyEntity(e) if not self.__dirty:has(e) then - self.__dirty:__add(e) + self.__dirty:add(e) end end @@ -107,40 +107,46 @@ function World:__flush() for i = 1, self.__backAdded.size do e = self.__backAdded[i] - self.__entities:__add(e) + if e.__world == self then + self.__entities:add(e) - for j = 1, self.__systems.size do - self.__systems[j]:__evaluate(e) + for j = 1, self.__systems.size do + self.__systems[j]:__evaluate(e) + end + + self:onEntityAdded(e) end - - self:onEntityAdded(e) end - self.__backAdded:__clear() + self.__backAdded:clear() -- Process removed entities for i = 1, self.__backRemoved.size do e = self.__backRemoved[i] - e.__world = nil - self.__entities:__remove(e) + if e.__world == self then + e.__world = nil + self.__entities:remove(e) - for j = 1, self.__systems.size do - self.__systems[j]:__remove(e) + for j = 1, self.__systems.size do + self.__systems[j]:__remove(e) + end + + self:onEntityRemoved(e) end - - self:onEntityRemoved(e) end - self.__backRemoved:__clear() + self.__backRemoved:clear() -- Process dirty entities for i = 1, self.__backDirty.size do e = self.__backDirty[i] - for j = 1, self.__systems.size do - self.__systems[j]:__evaluate(e) + if e.__world == self then + for j = 1, self.__systems.size do + self.__systems[j]:__evaluate(e) + end end end - self.__backDirty:__clear() + self.__backDirty:clear() return self end @@ -171,7 +177,7 @@ function World:addSystem(systemClass) local system = systemClass(self) self.__systemLookup[systemClass] = system - self.__systems:__add(system) + self.__systems:add(system) for callbackName, callback in pairs(systemClass) do -- Skip callback if its blacklisted @@ -278,9 +284,13 @@ function World:clear() self:removeEntity(self.__entities[i]) end - for i = 1, self.__systems.size do - self.__systems[i]:__clear() + for i = 1, self.__added.size do + local e = self.__added[i] + e.__world = nil end + self.__added:clear() + + self:__flush() return self end diff --git a/concord/worlds.lua b/concord/worlds.lua deleted file mode 100644 index 6feba44..0000000 --- a/concord/worlds.lua +++ /dev/null @@ -1,48 +0,0 @@ ---- Worlds --- Container for registered Worlds - -local PATH = (...):gsub('%.[^%.]+$', '') - -local Type = require(PATH..".type") - -local Worlds = {} - ---- Registers a World. --- @tparam string 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) - end - - if (not Type.isWorld(world)) then - error("bad argument #2 to 'Worlds.register' (world expected, got "..type(world)..")", 3) - end - - if (rawget(Worlds, name)) then - error("bad argument #2 to 'Worlds.register' (World with name '"..name.."' is already registerd)", 3) - end - - Worlds[name] = world - world.__name = name -end - ---- Returns true if the containter has the World with the name --- @tparam string name Name of the World to check --- @treturn boolean -function Worlds.has(name) - return Worlds[name] and true or false -end - ---- Returns the World with the name --- @tparam string name Name of the World to get --- @return World with the name -function Worlds.get(name) - return Worlds[name] -end - -return setmetatable(Worlds, { - __index = function(_, name) - error("Attempt to index world '"..tostring(name).."' that does not exist / was not registered", 2) - end -}) diff --git a/examples/assemblageTest/init.lua b/examples/assemblageTest/init.lua index 01adb8b..4136936 100644 --- a/examples/assemblageTest/init.lua +++ b/examples/assemblageTest/init.lua @@ -5,61 +5,59 @@ local Component = Concord.component local System = Concord.system local Assemblage = Concord.assemblage -local Game = Concord.context() - -local Legs = Component(function(e, legCount) +local Legs = Component("Legs", function(e, legCount) e.legCount = legCount or 0 end) -local Instinct = Component(function(e) -- luacheck: ignore +local Instinct = Component("Instinct", function(e) -- luacheck: ignore end) -local Cool = Component(function(e, coolness) +local Cool = Component("Cool", function(e, coolness) e.coolness = coolness end) -local Wings = Component(function(e) +local Wings = Component("Wings", function(e) e.wingCount = 2 end) -local Animal = Assemblage(function(e, legCount) +local Animal = function(e, legCount) e - :give(Legs, legCount) - :give(Instinct) + :give("Legs", legCount) + :give("Instinct") print("Animal") -end) +end -local Lion = Assemblage(function(e, coolness) +local Lion = function(e, coolness) e :assemble(Animal, 4) :give(Cool, coolness) print("Lion") -end) +end -local Eagle = Assemblage(function(e) +local Eagle = function(e) e :assemble(Animal, 2) :give(Wings) print("Eagle") -end) +end -local Griffin = Assemblage(function(e, coolness) +local Griffin = function(e, coolness) e :assemble(Animal, 4) :assemble(Lion, coolness * 2) :assemble(Eagle) -end) +end local myAnimal = Entity() :assemble(Griffin, 5) --:apply() -print(myAnimal:has(Legs)) -print(myAnimal:has(Instinct)) -print(myAnimal:has(Cool)) -print(myAnimal:has(Wings)) +print(myAnimal:has("Legs")) +print(myAnimal:has("Instinct")) +print(myAnimal:has("Cool")) +print(myAnimal:has("Wings")) diff --git a/examples/serialization/init.lua b/examples/serialization/init.lua index 2b5e05d..c362265 100644 --- a/examples/serialization/init.lua +++ b/examples/serialization/init.lua @@ -11,11 +11,10 @@ local function display(t) end end -local test_component_1 = Concord.component(function(e, x, y) +local test_component_1 = Concord.component("test_component_1", function(e, x, y) e.x = x or 0 e.y = y or 0 end) -Concord.components.register("test_component_1", test_component_1) function test_component_1:serialize() return { @@ -29,10 +28,9 @@ function test_component_1:deserialize(data) self.y = data.y or 0 end -local test_component_2 = Concord.component(function(e, foo) +local test_component_2 = Concord.component("test_component_2", function(e, foo) e.foo = foo end) -Concord.components.register("test_component_2", test_component_2) function test_component_2:serialize() return { @@ -50,8 +48,8 @@ local world_2 = Concord.world() -- Test Entity Concord.entity(world_1) -:give(test_component_1, 100, 50) -:give(test_component_2, "Hello World!") +:give("test_component_1", 100, 50) +:give("test_component_2", "Hello World!") -- Serialize world local data = world_1:serialize() @@ -62,8 +60,8 @@ world_2:deserialize(data) -- Check result local test_entity_copy = world_2:getEntities()[1] -local test_comp_1 = test_entity_copy[test_component_1] -local test_comp_2 = test_entity_copy[test_component_2] +local test_comp_1 = test_entity_copy["test_component_1"] +local test_comp_2 = test_entity_copy["test_component_2"] print(test_comp_1.x, test_comp_1.y) print(test_comp_2.foo) \ No newline at end of file diff --git a/examples/simpleDrawing/init.lua b/examples/simpleDrawing/init.lua index 29b31d1..6afd986 100644 --- a/examples/simpleDrawing/init.lua +++ b/examples/simpleDrawing/init.lua @@ -6,33 +6,33 @@ local System = Concord.system local Game = Concord.world() -local Position = Component(function(e, x, y) +local Position = Component('Position', function(e, x, y) e.x = x e.y = y end) -local Rectangle = Component(function(e, w, h) +local Rectangle = Component('Rectangle', function(e, w, h) e.w = w e.h = h end) -local Circle = Component(function(e, r) +local Circle = Component('Circle', function(e, r) e.r = r end) -local Color = Component(function(e, r, g, b, a) +local Color = Component('Color', function(e, r, g, b, a) e.r = r e.g = g e.b = b e.a = a end) -local RectangleRenderer = System({Position, Rectangle}) +local RectangleRenderer = System{pool = {'Position', 'Rectangle'}} function RectangleRenderer:draw() for _, e in ipairs(self.pool) do - local position = e:get(Position) - local rectangle = e:get(Rectangle) - local color = e:get(Color) + local position = e:get('Position') + local rectangle = e:get('Rectangle') + local color = e:get('Color') love.graphics.setColor(255, 255, 255) if color then @@ -43,7 +43,7 @@ function RectangleRenderer:draw() end end -local CircleRenderer = System({Position, Circle}) +local CircleRenderer = System{pool = {'Position', 'Circle'}} function CircleRenderer:flush() for _, e in ipairs(self.pool.removed) do print(tostring(e).. " was removed from my pool D:") @@ -52,9 +52,9 @@ end function CircleRenderer:draw() for _, e in ipairs(self.pool) do - local position = e:get(Position) - local circle = e:get(Circle) - local color = e:get(Color) + local position = e:get('Position') + local circle = e:get('Circle') + local color = e:get('Color') love.graphics.setColor(255, 255, 255) if color then @@ -65,7 +65,7 @@ function CircleRenderer:draw() end end -local RandomRemover = System({}) +local RandomRemover = System{pool = {}} function RandomRemover:init() self.time = 0 @@ -93,11 +93,11 @@ Game:addSystem(CircleRenderer(), "draw") for _ = 1, 100 do local e = Entity() - e:give(Position, love.math.random(0, 700), love.math.random(0, 700)) - e:give(Rectangle, love.math.random(5, 20), love.math.random(5, 20)) + e:give('Position', love.math.random(0, 700), love.math.random(0, 700)) + e:give('Rectangle', love.math.random(5, 20), love.math.random(5, 20)) if love.math.random(0, 1) == 0 then - e:give(Color, love.math.random(), love.math.random(), love.math.random(), 1) + e:give('Color', love.math.random(), love.math.random(), love.math.random(), 1) end Game:addEntity(e) @@ -105,11 +105,11 @@ end for _ = 1, 100 do local e = Entity() - e:give(Position, love.math.random(0, 700), love.math.random(0, 700)) - e:give(Circle, love.math.random(5, 20)) + e:give('Position', love.math.random(0, 700), love.math.random(0, 700)) + e:give('Circle', love.math.random(5, 20)) if love.math.random(0, 1) == 0 then - e:give(Color, love.math.random(), love.math.random(), love.math.random(), 1) + e:give('Color', love.math.random(), love.math.random(), love.math.random(), 1) end Game:addEntity(e) From 16e111176ee0be9d4f3258751835155c2e9ea2bc Mon Sep 17 00:00:00 2001 From: Pablo Ariel Mayobre Date: Sat, 14 Mar 2020 14:55:11 -0300 Subject: [PATCH 02/12] Added Utils.loadNamespace Deleted the previous Concord.loadComponents/Systems/Worlds --- concord/init.lua | 76 +---------------------------------------------- concord/utils.lua | 53 +++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 78 deletions(-) diff --git a/concord/init.lua b/concord/init.lua index 1653c46..cf5f2f2 100644 --- a/concord/init.lua +++ b/concord/init.lua @@ -33,84 +33,10 @@ local Concord = { } Concord.entity = require(PATH..".entity") - Concord.component = require(PATH..".component") Concord.components = require(PATH..".components") - Concord.system = require(PATH..".system") - Concord.world = require(PATH..".world") - -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) - 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) - end - - local files = love.filesystem.getDirectoryItems(pathOrFiles) - - for _, file in ipairs(files) do - local name = file:sub(1, #file - 4) - local path = pathOrFiles.."."..name - - local value = require(path) - if namespace then namespace[name] = value end - end - elseif (type(pathOrFiles == "table")) then - for _, path in ipairs(pathOrFiles) do - if (type(path) ~= "string") then - error("bad argument #2 to 'load' (string/table of strings expected, got table containing "..type(path)..")", 3) -- luacheck: ignore - end - - local name = path - - local dotIndex, slashIndex = path:match("^.*()%."), path:match("^.*()%/") - if (dotIndex or slashIndex) then - name = path:sub((dotIndex or slashIndex) + 1) - end - - local value = require(path) - if namespace then namespace[name] = value end - end - end - - return namespace -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" -function Concord.loadComponents(pathOrFiles) - load(pathOrFiles, nil) - return 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" -function Concord.loadSystems(pathOrFiles, world) - local systems = load(pathOrFiles, {}) - - if world then - for _, system in pairs(systems) do - world:addSystem(system) - end - end - - return 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" -function Concord.loadWorlds(pathOrFiles) - return load(pathOrFiles, {}) -end - +Concord.utils = require(PATH..".utils") return Concord diff --git a/concord/utils.lua b/concord/utils.lua index 6372b5b..68ae22b 100644 --- a/concord/utils.lua +++ b/concord/utils.lua @@ -7,9 +7,56 @@ local Utils = {} -- @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 - end + for key, value in pairs(orig) do + target[key] = value + end +end + +--- Requires files and puts them in a table. +-- Accepts a table of paths to Lua files: {"path/to/file_1", "path/to/another/file_2", "etc"} +-- Accepts a path to a directory with Lua files: "my_files/here" +-- @param pathOrFiles The table of paths or a path to a directory. +-- @param namespace A table that will hold the required files +-- @treturn table The namespace table +function Utils.loadNamespace(pathOrFiles, namespace) + if (type(pathOrFiles) ~= "string" and type(pathOrFiles) ~= "table") then + error("bad argument #1 to 'loadNamespace' (string/table of strings expected, got "..type(pathOrFiles)..")", 2) + 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 'loadNamespace' (path '"..pathOrFiles.."' not found)", 2) + end + + local files = love.filesystem.getDirectoryItems(pathOrFiles) + + for _, file in ipairs(files) do + local name = file:sub(1, #file - 4) + local path = pathOrFiles.."."..name + + local value = require(path) + if namespace then namespace[name] = value end + end + elseif (type(pathOrFiles == "table")) then + for _, path in ipairs(pathOrFiles) do + if (type(path) ~= "string") then + error("bad argument #2 to 'loadNamespace' (string/table of strings expected, got table containing "..type(path)..")", 2) -- luacheck: ignore + end + + local name = path + + local dotIndex, slashIndex = path:match("^.*()%."), path:match("^.*()%/") + if (dotIndex or slashIndex) then + name = path:sub((dotIndex or slashIndex) + 1) + end + + local value = require(path) + if namespace then namespace[name] = value end + end + end + + return namespace end return Utils \ No newline at end of file From 9ae805aa43f0f60f98f5653a1fc62193040dc2c4 Mon Sep 17 00:00:00 2001 From: Pablo Ariel Mayobre Date: Sat, 14 Mar 2020 14:56:25 -0300 Subject: [PATCH 03/12] Fixed: Pool:evaluate should bypass Pool:add filter check --- concord/components.lua | 36 ++++++++++++++++++------------------ concord/pool.lua | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/concord/components.lua b/concord/components.lua index e4116fd..d4d6482 100644 --- a/concord/components.lua +++ b/concord/components.lua @@ -8,23 +8,23 @@ local Type = require(PATH..".type") local Components = {} local try = function (name) - if type(name) ~= "string" then - return false, "ComponentsClass name is expected to be a string, got "..type(name)..")" - end + if type(name) ~= "string" then + return false, "ComponentsClass name is expected to be a string, got "..type(name)..")" + end - local value = rawget(Components, name) - if not value then - return false, "ComponentClass '"..name.."' does not exist / was not registered" - end + local value = rawget(Components, name) + if not value then + return false, "ComponentClass '"..name.."' does not exist / was not registered" + end - return true, value + return true, value end --- Returns true if the containter has the ComponentClass with the specified name -- @string name Name of the ComponentClass to check -- @treturn boolean function Components.has(name) - return rawget(Components, name) and true or false + return rawget(Components, name) and true or false end --- Returns true and the ComponentClass if one was registered with the specified name @@ -33,25 +33,25 @@ end -- @treturn boolean -- @treturn Component or error string function Components.try(name) - return try(name) + return try(name) end --- Returns the ComponentClass with the specified name -- @string name Name of the ComponentClass to get -- @treturn Component function Components.get(name) - local ok, value = try(name) + local ok, value = try(name) - if not ok then error(value, 2) end + if not ok then error(value, 2) end - return value + return value end return setmetatable(Components, { - __index = function(_, name) - local ok, value = try(name) + __index = function(_, name) + local ok, value = try(name) - if not ok then error(value, 2) end - - return value end + if not ok then error(value, 2) end + + return value end }) diff --git a/concord/pool.lua b/concord/pool.lua index e74227a..f1c80eb 100644 --- a/concord/pool.lua +++ b/concord/pool.lua @@ -72,7 +72,7 @@ function Pool:evaluate(e) local eligible = self:eligible(e) if not has and eligible then - self:add(e) + self:add(e, true) --Bypass the check cause we already checked elseif has and not eligible then self:remove(e) end From 079c1d0e18ed52056fc23d1a4b8be3d25c7e601e Mon Sep 17 00:00:00 2001 From: Pablo Ariel Mayobre Date: Sat, 14 Mar 2020 18:45:15 -0300 Subject: [PATCH 04/12] Utils.shallowCopy as a default serializer/deserializer for Components --- concord/component.lua | 3 +++ concord/utils.lua | 2 ++ 2 files changed, 5 insertions(+) diff --git a/concord/component.lua b/concord/component.lua index 0ac83e6..04691a6 100644 --- a/concord/component.lua +++ b/concord/component.lua @@ -4,6 +4,7 @@ local PATH = (...):gsub('%.[^%.]+$', '') local Components = require(PATH..".components") +local Utils = require(PATH..".utils") local Component = {} Component.__mt = { @@ -47,9 +48,11 @@ function Component:__populate() -- luacheck: ignore end function Component:serialize() -- luacheck: ignore + return Utils.shallowCopy(self, {}) end function Component:deserialize(data) -- luacheck: ignore + Utils.shallowCopy(data, self) end -- Internal: Creates a new Component. diff --git a/concord/utils.lua b/concord/utils.lua index 68ae22b..da83c4d 100644 --- a/concord/utils.lua +++ b/concord/utils.lua @@ -10,6 +10,8 @@ function Utils.shallowCopy(orig, target) for key, value in pairs(orig) do target[key] = value end + + return target end --- Requires files and puts them in a table. From d8621e40701448680bda3b67a0091d67c5256c8d Mon Sep 17 00:00:00 2001 From: Pablo Ariel Mayobre Date: Sat, 14 Mar 2020 19:09:23 -0300 Subject: [PATCH 05/12] Fix serialization function to guard internal values --- concord/component.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/concord/component.lua b/concord/component.lua index 04691a6..d8678f0 100644 --- a/concord/component.lua +++ b/concord/component.lua @@ -47,11 +47,18 @@ end function Component:__populate() -- luacheck: ignore end -function Component:serialize() -- luacheck: ignore - return Utils.shallowCopy(self, {}) +function Component:serialize() + local data = Utils.shallowCopy(self, {}) + + --This values shouldn't be copied over + data.__componentClass = nil + data.__isComponent = nil + data.__isComponentClass = nil + + return data end -function Component:deserialize(data) -- luacheck: ignore +function Component:deserialize(data) Utils.shallowCopy(data, self) end From 78dc7ee937e01a595859c075f4dad2e7f02d715c Mon Sep 17 00:00:00 2001 From: Pablo Ariel Mayobre Date: Sun, 15 Mar 2020 00:20:06 -0300 Subject: [PATCH 06/12] Fixed some error messages --- concord/components.lua | 28 ++++++++++------------- concord/entity.lua | 2 +- concord/system.lua | 6 ++--- concord/world.lua | 51 ++++++++++++++++++++++++++---------------- 4 files changed, 48 insertions(+), 39 deletions(-) diff --git a/concord/components.lua b/concord/components.lua index d4d6482..bf20d2d 100644 --- a/concord/components.lua +++ b/concord/components.lua @@ -7,19 +7,6 @@ local Type = require(PATH..".type") local Components = {} -local try = function (name) - if type(name) ~= "string" then - return false, "ComponentsClass name is expected to be a string, got "..type(name)..")" - end - - local value = rawget(Components, name) - if not value then - return false, "ComponentClass '"..name.."' does not exist / was not registered" - end - - return true, value -end - --- Returns true if the containter has the ComponentClass with the specified name -- @string name Name of the ComponentClass to check -- @treturn boolean @@ -33,14 +20,23 @@ end -- @treturn boolean -- @treturn Component or error string function Components.try(name) - return try(name) + if type(name) ~= "string" then + return false, "ComponentsClass name is expected to be a string, got "..type(name)..")" + end + + local value = rawget(Components, name) + if not value then + return false, "ComponentClass '"..name.."' does not exist / was not registered" + end + + return true, value end --- Returns the ComponentClass with the specified name -- @string name Name of the ComponentClass to get -- @treturn Component function Components.get(name) - local ok, value = try(name) + local ok, value = Components.try(name) if not ok then error(value, 2) end @@ -49,7 +45,7 @@ end return setmetatable(Components, { __index = function(_, name) - local ok, value = try(name) + local ok, value = Components.try(name) if not ok then error(value, 2) end diff --git a/concord/entity.lua b/concord/entity.lua index acafe01..888353f 100644 --- a/concord/entity.lua +++ b/concord/entity.lua @@ -204,7 +204,7 @@ function Entity:deserialize(data) local componentData = data[i] if (not Components.has(componentData.__name)) then - error("bad argument #1 to 'Entity:deserialize' (ComponentClass "..type(componentData.__name).." wasn't yet loaded)") -- luacheck: ignore + error("bad argument #1 to 'Entity:deserialize' (ComponentClass '"..tostring(componentData.__name).."' wasn't yet loaded)") -- luacheck: ignore end local componentClass = Components[componentData.__name] diff --git a/concord/system.lua b/concord/system.lua index 3583010..756b124 100644 --- a/concord/system.lua +++ b/concord/system.lua @@ -51,11 +51,11 @@ local validateFilters = function (baseFilters) for name, componentsList in pairs(baseFilters) do if type(name) ~= 'string' then - error("Invalid name for filter (string key expected, got "..type(name)..")", 3) + 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) + error("invalid component list for filter '"..name.."' (table expected, got "..type(componentsList)..")", 3) end local filter = {} @@ -63,7 +63,7 @@ local validateFilters = function (baseFilters) local ok, componentClass = Components.try(component) if not ok then - error("Invalid component for filter '"..name.."' at position #"..n.." ("..componentClass..")", 3) + error("invalid component for filter '"..name.."' at position #"..n.." ("..componentClass..")", 3) end filter[#filter + 1] = componentClass diff --git a/concord/world.lua b/concord/world.lua index f42a608..e874881 100644 --- a/concord/world.lua +++ b/concord/world.lua @@ -158,37 +158,31 @@ local blacklistedSystemFunctions = { "onDisabled", } ---- Adds a System to the World. --- Callbacks are registered automatically --- Entities added before are added to the System retroactively --- @see World:emit --- @tparam System systemClass SystemClass of System to add --- @treturn World self -function World:addSystem(systemClass) +local tryAddSystem = function (world, systemClass) if (not Type.isSystemClass(systemClass)) then - error("bad argument #1 to 'World:addSystems' (SystemClass expected, got "..type(systemClass)..")", 2) + return false, "SystemClass expected, got "..type(systemClass) end - if (self.__systemLookup[systemClass]) then - error("bad argument #1 to 'World:addSystems' (SystemClass was already added to World)", 2) + if (world.__systemLookup[systemClass]) then + return false, "SystemClass was already added to World" end -- Create instance of system - local system = systemClass(self) + local system = systemClass(world) - self.__systemLookup[systemClass] = system - self.__systems:add(system) + world.__systemLookup[systemClass] = system + world.__systems:add(system) for callbackName, callback in pairs(systemClass) do -- Skip callback if its blacklisted 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] = {} + if (not world.__events[callbackName]) then + world.__events[callbackName] = {} end -- Add callback to listeners - local listeners = self.__events[callbackName] + local listeners = world.__events[callbackName] listeners[#listeners + 1] = { system = system, callback = callback, @@ -197,8 +191,24 @@ function World:addSystem(systemClass) end -- Evaluate all existing entities - for j = 1, self.__entities.size do - system:__evaluate(self.__entities[j]) + for j = 1, world.__entities.size do + system:__evaluate(world.__entities[j]) + end + + return true +end + +--- Adds a System to the World. +-- Callbacks are registered automatically +-- Entities added before are added to the System retroactively +-- @see World:emit +-- @tparam System systemClass SystemClass of System to add +-- @treturn World self +function World:addSystem(systemClass) + local ok, err = tryAddSystem(self, systemClass) + + if not ok then + error("bad argument #1 to 'World:addSystem' ("..err..")", 2) end return self @@ -214,7 +224,10 @@ function World:addSystems(...) for i = 1, select("#", ...) do local systemClass = select(i, ...) - self:addSystem(systemClass) + local ok, err = tryAddSystem(self, systemClass) + if not ok then + error("bad argument #"..i.." to 'World:addSystems' ("..err..")", 2) + end end return self From f64025885211e034ddd24edf6166a3b021dc2b07 Mon Sep 17 00:00:00 2001 From: Pablo Ariel Mayobre Date: Sun, 15 Mar 2020 01:04:39 -0300 Subject: [PATCH 07/12] Fix component:serialize can return nil --- concord/entity.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/concord/entity.lua b/concord/entity.lua index 888353f..854fa8c 100644 --- a/concord/entity.lua +++ b/concord/entity.lua @@ -190,9 +190,11 @@ function Entity:serialize() for _, component in pairs(self.__components) do if component.__name then local componentData = component:serialize() - componentData.__name = component.__name - data[#data + 1] = componentData + if componentData ~= nil then + componentData.__name = component.__name + data[#data + 1] = componentData + end end end From b906e2a910c460fa6c807ea990aa33c4dff15cef Mon Sep 17 00:00:00 2001 From: Justin van der Leij Date: Mon, 16 Mar 2020 10:35:55 +0100 Subject: [PATCH 08/12] Update docs --- README.md | 202 ++++++++++++++++++------------------------------------ 1 file changed, 68 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index d7aea42..d0a2a63 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,16 @@ Auto generated docs for Concord can be found in `docs` folder, or on the [Github - [Entities](#entities) - [Systems](#systems) - [Worlds](#worlds) -- [Assemblages](#assemblages) +- [Assemblages](#assemblages) [Quick Example](#quick-example) [Contributors](#contributors) [License](#licence) - --- ## Installation -Download the repository and copy the 'src' folder in your project. Rename it to something that makes sense (Probably 'concord'), then require it in your project like so: +Download the repository and copy the 'concord' folder into your project. Then require it in your project like so: ```lua local Concord = require("path.to.concord") ``` @@ -42,13 +41,9 @@ local Entity = Concord.entity local Component = Concord.component local System = Concord.system local World = Concord.world -local Assemblage = Concord.assemblage -- Containers local Components = Concord.components -local Systems = Concord.systems -local Worlds = Concord.worlds -local Assemblages = Concord.assemblages ``` --- @@ -71,7 +66,7 @@ What is most important is that Components are data and nothing more. They have 0 Entities are the actual objects in your game. Like a player, an enemy, a crate, or a bullet. Every Entity has it's own set of Components, with their own values. -A crate might have the following components: +A crate might have the following components (Note: Not actual Concord syntax): ```lua { position = { x = 100, y = 200 }, @@ -100,7 +95,7 @@ For this they will only act on Entities that have the Components needed for this In Concord this is done something alike this: ```lua -drawSystem = System({position, texture}) -- Define a System that takes all Entities with a position and texture Component +drawSystem = System({pool = {position, texture}}) -- Define a System that takes all Entities with a position and texture Component function drawSystem:draw() -- Give it a draw function for _, entity in ipairs(self.pool) do -- Iterate over all Entities that this System acts on @@ -140,40 +135,26 @@ When you define a Component or System you are actually defining a `ComponentClas For example. If you want to get a specific Component from an Entity, you'd do `Component = Entity:get(ComponentClass)`. When ComponentClasses or SystemClasses are required it will be written clearly in the Documentation. -#### Containers -Since you'll be defining or creating lots of Components, Systems, Worlds and Assemblages, Concord adds container tables for each of them so that they are easily accessible. +#### Requiring files + +Since you'll have lots of Components and Systems in your game Concord makes it a bit easier to load things in. -These containers can be accessed through ```lua -local Components = require("path.to.concord").components -local Systems = require("path.to.concord").systems -local Worlds = require("path.to.concord").worlds -local Assemblages = require("path.to.concord").aorlds +-- 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.loadNamespace("path.to.systems", Systems) + +print(Systems.systemName) + +-- Loads all files in the directory. Components automatically register to Concord, so loading them into a namespace isn't necessary. +Concord.loadNamespace("path.to.components") + +print(Systems.componentName) ``` -Concord has helper functions to fill these containers. There are the following options depending on your needs / preference: -```lua --- Loads each file. They are put in the container according to it's filename ( 'src.components.component_1.lua' becomes 'component_1' ) -Concord.loadComponents({"path.to.component_1", "path.to.component_2", "etc"}) - --- Loads all files in the directory. They are put in the container according to it's filename ( 'src.components.component_1.lua' becomes 'component_1' ) -Concord.loadComponents("path.to.directory.containing.components") - - --- Put the ComponentClass into the container directly. Useful if you want more manual control. Note that you need to require the file in this case -Components.register("componentName", ComponentClass) -``` -Things can then be accessed through their names: -```lua -local component_1_class = Components.component_1 -local componentName_class = Components.componentName -``` - -All the above applies the same to all the other containers. - #### Method chaining ```lua --- All functions that do something ( eg. Don't return anything ) will return self +-- Most (if not all) methods will return self -- This allowes you to chain methods entity @@ -182,8 +163,6 @@ entity :remove(position) :destroy() --- - world :addEntity(fooEntity) :addEntity(barEntity) @@ -192,13 +171,13 @@ world ``` ### Components -When defining a ComponentClass you usually pass in a `populate` function. This will fill the Component with values. +When defining a ComponentClass you need to pass in a name and usually a `populate` function. This will fill the Component with values. ```lua --- Create the ComponentClass with a populate function +-- Create the position class with a populate function -- The component variable is the actual Component given to an Entity -- The x and y variables are values we pass in when we create the Component -local positionComponentClass = Concord.component(function(component, x, y) +Concord.component("position" function(component, x, y) component.x = x or 0 component.y = y or 0 end) @@ -206,19 +185,11 @@ end) -- Create a ComponentClass without a populate function -- Components of this type won't have any fields. -- This can be useful to indiciate state. -local pushableComponentClass = Concord.component() -``` - -```lua --- Manually register the ComponentClass to the container if we want -Concord.components.register("positionComponent", positionComponentClass) - --- Otherwise return the ComponentClass so it can be required -return positionComponentClass +local pushableComponentClass = Concord.component("position") ``` ### Entities -Entities can be freely made and be given Components. You pass the ComponentClass and the values you want to pass. It will then create the Component for you. +Entities can be freely made and be given Components. You pass the name of the ComponentClass and the values you want to pass. It will then create the Component for you. Entities can only have a maximum of one of each Component. Entities can not share Components. @@ -233,43 +204,41 @@ local myEntity = Entity(myWorld) -- To add it to a world immediately ( See World ```lua -- Give the entity the position Component defined above -- x will become 100. y will become 50 -myEntity:give(positionComponentClass, 100, 50) +myEntity:give("position", 100, 50) ``` ```lua -- Retrieve a Component -local positionComponent = myEntity[positionComponentClass] --- or -local positionComponent = myEntity:get(positionComponentClass) +local position = myEntity.position -print(positionComponent.x, positionComponent.y) -- 100, 50 +print(position.x, position.y) -- 100, 50 ``` ```lua -- Remove a Component -myEntity:remove(positionComponentClass) +myEntity:remove("position") ``` ```lua -- Check if the Entity has a Component -local hasPositionComponent = myEntity:has(positionComponentClass) -print(hasPositionComponent) -- false +local hasPosition = myEntity.position and true of false +print(hasPosition) -- false ``` ```lua -- Entity:give will override a Component if the Entity already has it -- Entity:ensure will only put the Component if the Entity does not already have it -Entity:ensure(positionComponentClass, 0, 0) -- Will give +Entity:ensure("position", 0, 0) -- Will give -- Position is {x = 0, y = 0} -Entity:give(positionComponentClass, 50, 50) -- Will override +Entity:give("position", 50, 50) -- Will override -- Position is {x = 50, y = 50} -Entity:give(positionComponentClass, 100, 100) -- Will override +Entity:give("position", 100, 100) -- Will override -- Position is {x = 100, y = 100} -Entity:ensure(positionComponentClass, 0, 0) -- Wont do anything +Entity:ensure("position", 0, 0) -- Wont do anything -- Position is {x = 100, y = 100} ``` @@ -285,7 +254,7 @@ end ```lua -- Assemble the Entity ( See Assemblages ) -myEntity:assemble(myAssemblage, 100, true, "foo") +myEntity:assemble(assemblageFunction, 100, true, "foo") ``` ```lua @@ -310,30 +279,21 @@ Systems can have multiple pools. ```lua -- Create a System -local mySystemClass = Concord.system({positionComponentClass}) -- Pool will contain all Entities with a position Component +local mySystemClass = Concord.system(pool = {"position"}) -- Pool named 'pool' will contain all Entities with a position Component -- Create a System with multiple pools local mySystemClass = Concord.system( - { -- This Pool's name will default to 'pool' - positionCompomponentClass, - velocityComponentClass, + pool = { -- This pool will be named 'pool' + "position", + "velocity", }, - { -- This Pool's name will be 'secondPool' - healthComponentClass, - damageableComponentClass, - "secondPool", + secondPool = { -- This pool's name will be 'secondPool' + "health", + "damageable", } ) ``` -```lua --- Manually register the SystemClass to the container if we want -Concord.system.register("mySystem", mySystemClass) - --- Otherwise return the SystemClass so it can be required -return mySystemClass -``` - ```lua -- If a System has a :init function it will be called on creation @@ -348,16 +308,11 @@ end function mySystemClass:update(dt) -- Iterate over all entities in the Pool for _, e in ipairs(self.pool) - -- Get the Entity's Components - local positionComponent = e[positionComponentClass] - local velocityComponent = e[velocityComponentClass] - -- Do something with the Components - positionComponent.x = positionComponent.x + velocityComponent.x * dt - positionComponent.y = positionComponent.y + velocityComponent.y * dt + e.position.x = e.position.x + e.velocity.x * dt + e.position.y = e.position.y + e.velocity.y * dt end - -- Iterate over all entities in the second Pool for _, e in ipairs(self.secondPool) -- Do something @@ -367,24 +322,20 @@ end ```lua -- Systems can be enabled and disabled +-- When systems are disabled their callbacks won't be executed. +-- Note that pools will still be updated +-- This is mainly useful for systems that display debug information -- Systems are enabled by default -- Enable a System -mySystem:enable() --- or mySystem:setEnable(true) -- Disable a System -mySystem:disable() --- or mySystem:setEnable(false) --- Toggle the enable state -mySystem:toggleEnabled() - -- Get enabled state local isEnabled = mySystem:isEnabled() -print(isEnabled) -- true +print(isEnabled) -- false ``` ```lua @@ -405,14 +356,6 @@ Worlds can have any number of Entities. local myWorld = Concord.world() ``` -```lua --- Manually register the World to the container if we want -Concord.worlds.register("myWorld", myWorld) - --- Otherwise return the World so it can be required -return myWorld -``` - ```lua -- Add an Entity to the World myWorld:addEntity(myEntity) @@ -471,33 +414,31 @@ end ### Assemblages -Assemblages are helpers to 'make' Entities something. +Assemblages are functions to 'make' Entities something. An important distinction is that they _append_ Components. ```lua --- Make an Assemblage +-- Make an Assemblage function -- e is the Entity being assembled. -- cuteness and legs are variables passed in -local animalAssemblage(function(e, cuteness, legs) +function animal(e, cuteness, legs) e :give(cutenessComponentClass, cuteness) :give(limbs, legs, 0) -- Variable amount of legs. 0 arm. end) --- Make an Assemblage that used animalAssemblage +-- Make an Assemblage that uses animal -- cuteness is a variables passed in -local catAssemblage(function(e, cuteness) +function cat(e, cuteness) e - :assemble(animalAssemblage, cuteness * 2, 4) -- Cats are twice as cute, and have 4 legs. + :assemble(animal, cuteness * 2, 4) -- Cats are twice as cute, and have 4 legs. :give(soundComponent, "meow.mp3") end) ``` ```lua -- Use an Assemblage -myEntity:assemble(catAssemblage, 100) -- 100 cuteness --- or -catAssemblage:assemble(myEntity, 100) -- 100 cuteness +myEntity:assemble(cat, 100) -- 100 cuteness ``` --- @@ -506,14 +447,13 @@ catAssemblage:assemble(myEntity, 100) -- 100 cuteness ```lua local Concord = require("concord") --- Defining ComponentClasses --- I use UpperCamelCase to indicate its a class -local Position = Concord.component(function(c, x, y) +-- Defining components +Concord.component("position", function(c, x, y) c.x = x or 0 c.y = y or 0 end) -local Velocity = Concord.component(function(c, x, y) +Concord.component("velocity", function(c, x, y) c.x = x or 0 c.y = y or 0 end) @@ -522,27 +462,21 @@ local Drawable = Concord.component() -- Defining Systems -local MoveSystem = Concord.system({Position, Velocity}) +local MoveSystem = Concord.system(pool = {"position", "velocity"}) function MoveSystem:update(dt) for _, e in ipairs(self.pool) do - -- I use lowerCamelCase to indicate its an instance - local position = e[Position] - local velocity = e[Velocity] - - position.x = position.x + velocity.x * dt - position.y = position.y + velocity.y * dt + e.position.x = e.position.x + e.velocity.x * dt + e.position.y = e.position.y + e.velocity.y * dt end end -local DrawSystem = Concord.system({Position, Drawable}) +local DrawSystem = Concord.system(pool = {"position", "drawable"}) function DrawSystem:draw() for _, e in ipairs(self.pool) do - local position = e[Position] - - love.graphics.circle("fill", position.x, position.y, 5) + love.graphics.circle("fill", e.position.x, e.position.y, 5) end end @@ -555,18 +489,18 @@ world:addSystems(MoveSystem, DrawSystem) -- This Entity will be rendered on the screen, and move to the right at 100 pixels a second local entity_1 = Concord.entity(world) -:give(Position, 100, 100) -:give(Velocity, 100, 0) -:give(Drawable) +:give("position", 100, 100) +:give("velocity", 100, 0) +:give("drawable") -- This Entity will be rendered on the screen, and stay at 50, 50 local entity_2 = Concord.entity(world) -:give(Position, 50, 50) -:give(Drawable) +:give("position", 50, 50) +:give("drawable") -- This Entity does exist in the World, but since it doesn't match any System's filters it won't do anything local entity_3 = Concord.entity(world) -:give(Position, 200, 200) +:give("position", 200, 200) -- Emit the events From c889b15d446999483bfaa6d3ddcdc6c506b924f6 Mon Sep 17 00:00:00 2001 From: Tjakka5 Date: Sat, 2 May 2020 20:42:26 +0200 Subject: [PATCH 09/12] Remove old examples --- examples/assemblageTest/init.lua | 63 ---------- examples/baseLayout/main.lua | 6 - examples/baseLayout/src/components/init.lua | 5 - examples/baseLayout/src/systems/init.lua | 5 - examples/serialization/init.lua | 67 ----------- examples/simpleDrawing/init.lua | 125 -------------------- main.lua | 1 - 7 files changed, 272 deletions(-) delete mode 100644 examples/assemblageTest/init.lua delete mode 100644 examples/baseLayout/main.lua delete mode 100644 examples/baseLayout/src/components/init.lua delete mode 100644 examples/baseLayout/src/systems/init.lua delete mode 100644 examples/serialization/init.lua delete mode 100644 examples/simpleDrawing/init.lua delete mode 100644 main.lua diff --git a/examples/assemblageTest/init.lua b/examples/assemblageTest/init.lua deleted file mode 100644 index 4136936..0000000 --- a/examples/assemblageTest/init.lua +++ /dev/null @@ -1,63 +0,0 @@ -local Concord = require("src") - -local Entity = Concord.entity -local Component = Concord.component -local System = Concord.system -local Assemblage = Concord.assemblage - -local Legs = Component("Legs", function(e, legCount) - e.legCount = legCount or 0 -end) - -local Instinct = Component("Instinct", function(e) -- luacheck: ignore -end) - -local Cool = Component("Cool", function(e, coolness) - e.coolness = coolness -end) - -local Wings = Component("Wings", function(e) - e.wingCount = 2 -end) - - -local Animal = function(e, legCount) - e - :give("Legs", legCount) - :give("Instinct") - - print("Animal") -end - -local Lion = function(e, coolness) - e - :assemble(Animal, 4) - :give(Cool, coolness) - - print("Lion") -end - -local Eagle = function(e) - e - :assemble(Animal, 2) - :give(Wings) - - print("Eagle") -end - -local Griffin = function(e, coolness) - e - :assemble(Animal, 4) - :assemble(Lion, coolness * 2) - :assemble(Eagle) -end - - -local myAnimal = Entity() -:assemble(Griffin, 5) ---:apply() - -print(myAnimal:has("Legs")) -print(myAnimal:has("Instinct")) -print(myAnimal:has("Cool")) -print(myAnimal:has("Wings")) diff --git a/examples/baseLayout/main.lua b/examples/baseLayout/main.lua deleted file mode 100644 index 6d6717f..0000000 --- a/examples/baseLayout/main.lua +++ /dev/null @@ -1,6 +0,0 @@ -local PATH = (...):gsub('%.[^%.]+$', '') - -local Concord = require("src") - -local C = require(PATH..".src.components") -local S = require(PATH..".src.systems") diff --git a/examples/baseLayout/src/components/init.lua b/examples/baseLayout/src/components/init.lua deleted file mode 100644 index 60a5a4c..0000000 --- a/examples/baseLayout/src/components/init.lua +++ /dev/null @@ -1,5 +0,0 @@ -local PATH = (...):gsub('%.init$', '') - -return { - -} \ No newline at end of file diff --git a/examples/baseLayout/src/systems/init.lua b/examples/baseLayout/src/systems/init.lua deleted file mode 100644 index 0273ef6..0000000 --- a/examples/baseLayout/src/systems/init.lua +++ /dev/null @@ -1,5 +0,0 @@ -local PATH = (...):gsub('%.init$', '') - -return { - -} \ No newline at end of file diff --git a/examples/serialization/init.lua b/examples/serialization/init.lua deleted file mode 100644 index c362265..0000000 --- a/examples/serialization/init.lua +++ /dev/null @@ -1,67 +0,0 @@ -local Concord = require("concord") - -local function display(t) - print("Table: " ..tostring(t)) - for key, value in pairs(t) do - if type(value) == "table" then - display(value) - else - print(key, value) - end - end -end - -local test_component_1 = Concord.component("test_component_1", function(e, x, y) - e.x = x or 0 - e.y = y or 0 -end) - -function test_component_1:serialize() - return { - x = self.x, - y = self.y, - } -end - -function test_component_1:deserialize(data) - self.x = data.x or 0 - self.y = data.y or 0 -end - -local test_component_2 = Concord.component("test_component_2", function(e, foo) - e.foo = foo -end) - -function test_component_2:serialize() - return { - foo = self.foo - } -end - -function test_component_2:deserialize(data) - self.foo = data.foo -end - --- Test worlds -local world_1 = Concord.world() -local world_2 = Concord.world() - --- Test Entity -Concord.entity(world_1) -:give("test_component_1", 100, 50) -:give("test_component_2", "Hello World!") - --- Serialize world -local data = world_1:serialize() - --- Deserialize world -world_2:deserialize(data) - --- Check result -local test_entity_copy = world_2:getEntities()[1] - -local test_comp_1 = test_entity_copy["test_component_1"] -local test_comp_2 = test_entity_copy["test_component_2"] - -print(test_comp_1.x, test_comp_1.y) -print(test_comp_2.foo) \ No newline at end of file diff --git a/examples/simpleDrawing/init.lua b/examples/simpleDrawing/init.lua deleted file mode 100644 index 6afd986..0000000 --- a/examples/simpleDrawing/init.lua +++ /dev/null @@ -1,125 +0,0 @@ -local Concord = require("src") - -local Entity = Concord.entity -local Component = Concord.component -local System = Concord.system - -local Game = Concord.world() - -local Position = Component('Position', function(e, x, y) - e.x = x - e.y = y -end) - -local Rectangle = Component('Rectangle', function(e, w, h) - e.w = w - e.h = h -end) - -local Circle = Component('Circle', function(e, r) - e.r = r -end) - -local Color = Component('Color', function(e, r, g, b, a) - e.r = r - e.g = g - e.b = b - e.a = a -end) - -local RectangleRenderer = System{pool = {'Position', 'Rectangle'}} -function RectangleRenderer:draw() - for _, e in ipairs(self.pool) do - local position = e:get('Position') - local rectangle = e:get('Rectangle') - local color = e:get('Color') - - love.graphics.setColor(255, 255, 255) - if color then - love.graphics.setColor(color.r, color.g, color.b, color.a) - end - - love.graphics.rectangle("fill", position.x, position.y, rectangle.w, rectangle.h) - end -end - -local CircleRenderer = System{pool = {'Position', 'Circle'}} -function CircleRenderer:flush() - for _, e in ipairs(self.pool.removed) do - print(tostring(e).. " was removed from my pool D:") - end -end - -function CircleRenderer:draw() - for _, e in ipairs(self.pool) do - local position = e:get('Position') - local circle = e:get('Circle') - local color = e:get('Color') - - love.graphics.setColor(255, 255, 255) - if color then - love.graphics.setColor(color.r, color.g, color.b, color.a) - end - - love.graphics.circle("fill", position.x, position.y, circle.r) - end -end - -local RandomRemover = System{pool = {}} - -function RandomRemover:init() - self.time = 0 -end - -function RandomRemover:update(dt) - self.time = self.time + dt - - if self.time >= 0.05 then - self.time = 0 - - if self.pool.size > 0 then - local i = love.math.random(1, self.pool.size) - - self.pool:get(i):destroy() - end - end - - love.window.setTitle(love.timer.getFPS()) -end - -Game:addSystem(RandomRemover(), "update") -Game:addSystem(RectangleRenderer(), "draw") -Game:addSystem(CircleRenderer(), "draw") - -for _ = 1, 100 do - local e = Entity() - e:give('Position', love.math.random(0, 700), love.math.random(0, 700)) - e:give('Rectangle', love.math.random(5, 20), love.math.random(5, 20)) - - if love.math.random(0, 1) == 0 then - e:give('Color', love.math.random(), love.math.random(), love.math.random(), 1) - end - - Game:addEntity(e) -end - -for _ = 1, 100 do - local e = Entity() - e:give('Position', love.math.random(0, 700), love.math.random(0, 700)) - e:give('Circle', love.math.random(5, 20)) - - if love.math.random(0, 1) == 0 then - e:give('Color', love.math.random(), love.math.random(), love.math.random(), 1) - end - - Game:addEntity(e) -end - - -function love.update(dt) - Game:emit("update", dt) -end - -function love.draw() - Game:emit("draw") -end diff --git a/main.lua b/main.lua deleted file mode 100644 index 7cfd88a..0000000 --- a/main.lua +++ /dev/null @@ -1 +0,0 @@ -require("examples.serialization") \ No newline at end of file From d56821205def03e7a149fecadfe06ffb3c7c67a0 Mon Sep 17 00:00:00 2001 From: Tjakka5 Date: Sat, 2 May 2020 20:44:33 +0200 Subject: [PATCH 10/12] Make repo submodule friendly --- init.lua | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 init.lua diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..2a73dea --- /dev/null +++ b/init.lua @@ -0,0 +1,3 @@ +local PATH = (...):gsub('%.init$', '') + +return require(PATH..".concord") \ No newline at end of file From c95a1f27685b503f31f87a6042a5947e03db7273 Mon Sep 17 00:00:00 2001 From: Tjakka5 Date: Sat, 2 May 2020 20:44:58 +0200 Subject: [PATCH 11/12] Update license --- concord/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concord/init.lua b/concord/init.lua index cf5f2f2..705289a 100644 --- a/concord/init.lua +++ b/concord/init.lua @@ -9,7 +9,7 @@ local Concord = { _LICENCE = [[ MIT LICENSE - Copyright (c) 2018 Justin van der Leij / Tjakka5 + Copyright (c) 2020 Justin van der Leij / Tjakka5 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the From 3e9c596187d35c850b06bb71ad7045794fd16588 Mon Sep 17 00:00:00 2001 From: Tjakka5 Date: Sat, 2 May 2020 20:51:15 +0200 Subject: [PATCH 12/12] Fix mistakes in readme --- README.md | 45 ++++++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index d0a2a63..fb549c5 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,8 @@ drawSystem = System({pool = {position, texture}}) -- Define a System that takes function drawSystem:draw() -- Give it a draw function for _, entity in ipairs(self.pool) do -- Iterate over all Entities that this System acts on - local position = entity[position] -- Get the position Component of this Entity - local texture = entity[texture] -- Get the texture Component of this Entity + local position = entity.position -- Get the position Component of this Entity + local texture = entity.texture -- Get the texture Component of this Entity -- Draw the Entity love.graphics.draw(texture.image, position.x, position.y) @@ -128,13 +128,6 @@ And all that without writing a single extra line of code. Just reusing code that Concord does a few things that might not be immediately clear. This segment should help understanding. -#### Classes - -When you define a Component or System you are actually defining a `ComponentClass` and `SystemClass` respectively. From these instances of them can be created. They also act as identifiers for Concord. - -For example. If you want to get a specific Component from an Entity, you'd do `Component = Entity:get(ComponentClass)`. -When ComponentClasses or SystemClasses are required it will be written clearly in the Documentation. - #### Requiring files Since you'll have lots of Components and Systems in your game Concord makes it a bit easier to load things in. @@ -142,14 +135,14 @@ Since you'll have lots of Components and Systems in your game Concord makes it a ```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.loadNamespace("path.to.systems", Systems) +Concord.utils.loadNamespace("path/to/systems", Systems) print(Systems.systemName) --- Loads all files in the directory. Components automatically register to Concord, so loading them into a namespace isn't necessary. -Concord.loadNamespace("path.to.components") +-- Loads all files in the directory. Components automatically register into Concord.components, so loading them into a namespace isn't necessary. +Concord.utils.loadNamespace("path/to/components") -print(Systems.componentName) +print(Concord.components.componentName) ``` #### Method chaining @@ -157,13 +150,13 @@ print(Systems.componentName) -- Most (if not all) methods will return self -- This allowes you to chain methods -entity -:give(position, 100, 50) -:give(velocity, 200, 0) -:remove(position) +myEntity +:give("position", 100, 50) +:give("velocity", 200, 0) +:remove("position") :destroy() -world +myWorld :addEntity(fooEntity) :addEntity(barEntity) :clear() @@ -219,12 +212,6 @@ print(position.x, position.y) -- 100, 50 myEntity:remove("position") ``` -```lua --- Check if the Entity has a Component -local hasPosition = myEntity.position and true of false -print(hasPosition) -- false -``` - ```lua -- Entity:give will override a Component if the Entity already has it -- Entity:ensure will only put the Component if the Entity does not already have it @@ -462,7 +449,9 @@ local Drawable = Concord.component() -- Defining Systems -local MoveSystem = Concord.system(pool = {"position", "velocity"}) +local MoveSystem = Concord.system({ + pool = {"position", "velocity"} +}) function MoveSystem:update(dt) for _, e in ipairs(self.pool) do @@ -472,7 +461,9 @@ function MoveSystem:update(dt) end -local DrawSystem = Concord.system(pool = {"position", "drawable"}) +local DrawSystem = Concord.system({ + pool = {"position", "drawable"} +}) function DrawSystem:draw() for _, e in ipairs(self.pool) do @@ -525,5 +516,5 @@ end --- -## Licence +## License MIT Licensed - Copyright Justin van der Leij (Tjakka5)