From d4efca976c8669f0ab27b0759e7d73b826249d74 Mon Sep 17 00:00:00 2001 From: Tjakka5 Date: Thu, 19 Dec 2019 10:34:02 +0100 Subject: [PATCH] Added named systems --- main.lua | 45 +++++++++++++++-------- src/component.lua | 12 +++--- src/components.lua | 29 +++++++++++++++ src/init.lua | 2 + src/list.lua | 2 + src/pool.lua | 4 +- src/system.lua | 37 +++++++++++-------- src/systems.lua | 29 +++++++++++++++ src/type.lua | 16 ++++---- src/world.lua | 92 ++++++++++++++++++++++++---------------------- 10 files changed, 176 insertions(+), 92 deletions(-) create mode 100644 src/components.lua create mode 100644 src/systems.lua diff --git a/main.lua b/main.lua index dad60e1..26ef1f8 100644 --- a/main.lua +++ b/main.lua @@ -6,50 +6,63 @@ require(file) ]=]-- local Concord = require("src") -local Component = require("src.component") -local test_comp_1 = Concord.component("test_comp_1", function(e, x, y) +local Component = require("src.component") +local Components = require("src.components") + +local System = Concord.system +local Systems = Concord.systems + +local Entity = Concord.entity + +local World = Concord.world + +Component("test_comp_1", function(e, x, y) e.x = x e.y = y end) -local test_comp_2 = Concord.component("test_comp_2", function(e, a) +Component("test_comp_2", function(e, a) e.a = a end) -local test_comp_3 = Concord.component("test_comp_3", function(e, b) +Component("test_comp_3", function(e, b) e.b = b end) -local test_system = Concord.system({Component.test_comp_1}) +local test_system = System("test_system", {Components.test_comp_1}) -function onEntityAdded(e) +local function onEntityAdded(e) -- luacheck: ignore print("Added") end -function onEntityRemoved(e) +local function onEntityRemoved(e) -- luacheck: ignore print("Removed") end function test_system:init() - self.pool.onEntityAdded = onEntityAdded + self.pool.onEntityAdded = onEntityAdded self.pool.onEntityRemoved = onEntityRemoved end -function test_system:update(dt) +function test_system:update(dt) -- luacheck: ignore + --print(#self.pool) +end + +function test_system:update2(dt) -- luacheck: ignore --print(#self.pool) end +local world = World() -local world = Concord.world() - -local entity = Concord.entity() -entity:give(Component.test_comp_1, 100, 100) +local entity = Entity() +entity:give(Components.test_comp_1, 100, 100) world:addEntity(entity) -world:addSystem(test_system(), "update") +world:addSystem(Systems.test_system, "update") +world:addSystem(Systems.test_system, "update", "update2") function love.update(dt) world:flush() @@ -59,10 +72,10 @@ end function love.keypressed(key) if key == "q" then - entity:remove(Component.test_comp_1) + entity:remove(Components.test_comp_1) end if key == "w" then - entity:give(Component.test_comp_1) + entity:give(Components.test_comp_1) end if key == "e" then world:removeEntity(entity) diff --git a/src/component.lua b/src/component.lua index 102e722..43d4ecc 100644 --- a/src/component.lua +++ b/src/component.lua @@ -1,5 +1,9 @@ --- Component +local PATH = (...):gsub('%.[^%.]+$', '') + +local Components = require(PATH..".components") + local Component = {} Component.__index = Component @@ -15,10 +19,6 @@ function Component.new(name, populate) error("bad argument #2 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2) end - if (Component[name] ~= nil) then - error("bad argument #2 to 'Component.new' (Component with name '"..name.."' already exists", 2) - end - local component = setmetatable({ __name = name, __populate = populate, @@ -28,7 +28,7 @@ function Component.new(name, populate) component.__mt = {__index = component} - Component[name] = component + Components.register(name, component) return component end @@ -51,4 +51,4 @@ return setmetatable(Component, { __call = function(_, ...) return Component.new(...) end, -}) \ No newline at end of file +}) \ No newline at end of file diff --git a/src/components.lua b/src/components.lua new file mode 100644 index 0000000..63d40e0 --- /dev/null +++ b/src/components.lua @@ -0,0 +1,29 @@ +-- Components + +local PATH = (...):gsub('%.[^%.]+$', '') + +local Type = require(PATH..".type") + +local Components = {} + +function Components.register(name, component) + if (type(name) ~= "string") then + error("bad argument #1 to 'Components.register' (string expected, got "..type(name)..")", 3) + end + + if (not Type.isComponent(component)) then + error("bad argument #2 to 'Components.register' (component expected, got "..type(component)..")", 3) + end + + if (rawget(Components, name)) then + error("bad argument #2 to 'Components.register' (Component with name '"..name.."' is already registerd)", 3) + end + + Components[name] = component +end + +return setmetatable(Components, { + __index = function(_, name) + error("Attempt to index component '"..tostring(name).."' that does not exist / was not registered", 2) + end +}) \ No newline at end of file diff --git a/src/init.lua b/src/init.lua index e13f190..4eb9b99 100644 --- a/src/init.lua +++ b/src/init.lua @@ -33,7 +33,9 @@ local Concord = { Concord.entity = require(PATH..".entity") 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.assemblage = require(PATH..".assemblage") diff --git a/src/list.lua b/src/list.lua index 16819da..b3e8fdf 100644 --- a/src/list.lua +++ b/src/list.lua @@ -60,6 +60,8 @@ function List:remove(obj) self[obj] = nil self.size = size - 1 + + return self end --- Gets an object by numerical index. diff --git a/src/pool.lua b/src/pool.lua index e82735a..92373e2 100644 --- a/src/pool.lua +++ b/src/pool.lua @@ -45,10 +45,10 @@ function Pool:remove(e) self:onEntityRemoved(e) end -function Pool:onEntityAdded(e) +function Pool:onEntityAdded(e) -- luacheck: ignore end -function Pool:onEntityRemoved(e) +function Pool:onEntityRemoved(e) -- luacheck: ignore end return setmetatable(Pool, { diff --git a/src/system.lua b/src/system.lua index b078a50..ecafd6b 100644 --- a/src/system.lua +++ b/src/system.lua @@ -2,20 +2,21 @@ local PATH = (...):gsub('%.[^%.]+$', '') -local Pool = require(PATH..".pool") +local Systems = require(PATH..".systems") +local Pool = require(PATH..".pool") local System = {} System.mt = { __index = System, - __call = function(systemProto, ...) + __call = function(baseSystem, world) local system = setmetatable({ __pools = {}, - __world = nil, + __world = world, __isSystem = true, - }, systemProto) + }, baseSystem) - for _, filter in pairs(systemProto.__filter) do + for _, filter in pairs(baseSystem.__filter) do local pool = system:__buildPool(filter) if not system[pool.name] then system[pool.name] = pool @@ -25,7 +26,8 @@ System.mt = { end end - system:init(...) + system:init(world) + return system end, } @@ -33,13 +35,21 @@ System.mt = { --- Creates a new System prototype. -- @param ... Variable amounts of filters -- @return A new System prototype -function System.new(...) - local systemProto = setmetatable({ +function System.new(name, ...) + if (type(name) ~= "string") then + error("bad argument #1 to 'System.new' (string expected, got "..type(name)..")", 2) + end + + local baseSystem = setmetatable({ + __name = name, + __isBaseSystem = true, __filter = {...}, }, System.mt) - systemProto.__index = systemProto + baseSystem.__index = baseSystem - return systemProto + Systems.register(name, baseSystem) + + return baseSystem end --- Builds a Pool for the System. @@ -98,13 +108,8 @@ function System:getWorld() end --- Default callback for system initialization. --- @param ... Varags -function System:init(...) -- luacheck: ignore -end - --- Default callback for when the System is added to an World. -- @param world The World the System was added to -function System:addedTo(world) -- luacheck: ignore +function System:init(world) -- luacheck: ignore end -- Default callback for when a System's callback is enabled. diff --git a/src/systems.lua b/src/systems.lua new file mode 100644 index 0000000..4f84472 --- /dev/null +++ b/src/systems.lua @@ -0,0 +1,29 @@ +-- Systems + +local PATH = (...):gsub('%.[^%.]+$', '') + +local Type = require(PATH..".type") + +local Systems = {} + +function Systems.register(name, system) + 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) + 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 +end + +return setmetatable(Systems, { + __index = function(_, name) + error("Attempt to index system '"..tostring(name).."' that does not exist / was not registered", 2) + end +}) \ No newline at end of file diff --git a/src/type.lua b/src/type.lua index 999c42c..ae79628 100644 --- a/src/type.lua +++ b/src/type.lua @@ -3,23 +3,23 @@ local Type = {} function Type.isComponent(t) - return type(t) == "table" and t.__isComponent + return type(t) == "table" and t.__isComponent or false end function Type.isEntity(t) - return type(t) == "table" and t.__isEntity + return type(t) == "table" and t.__isEntity or false +end + +function Type.isBaseSystem(t) + return type(t) == "table" and t.__isBaseSystem or false end function Type.isSystem(t) - return type(t) == "table" and t.__isSystem -end - -function Type.isContext(t) - return type(t) == "table" and t.__isContext + return type(t) == "table" and t.__isSystem or false end function Type.isAssemblage(t) - return type(t) == "table" and t.__isAssemblage + return type(t) == "table" and t.__isAssemblage or false end return Type diff --git a/src/world.lua b/src/world.lua index 2712cb6..2bb13e5 100644 --- a/src/world.lua +++ b/src/world.lua @@ -14,11 +14,14 @@ function World.new() local world = setmetatable({ entities = List(), systems = List(), + events = {}, __added = {}, __removed = {}, + __systemLookup = {}, + __isWorld = true, }, World) @@ -68,8 +71,8 @@ function World:flush() e.__wasAdded = false e.__isDirty = false - for i = 1, self.systems.size do - self.systems:get(i):__evaluate(e) + for j = 1, self.systems.size do + self.systems:get(j):__evaluate(e) end self:onEntityAdded(e) @@ -79,16 +82,16 @@ function World:flush() e.world = nil self.entities:remove(e) - for i = 1, self.systems.size do - self.systems:get(i):__remove(e) + for j = 1, self.systems.size do + self.systems:get(j):__remove(e) end e.__wasRemoved = false end if e.__isDirty then - for i = 1, self.systems.size do - self.systems:get(i):__evaluate(e) + for j = 1, self.systems.size do + self.systems:get(j):__evaluate(e) end e.__isDirty = false @@ -99,43 +102,46 @@ function World:flush() end --- Adds a System to the World. --- @param system The System to add --- @param eventName The Event to register to --- @param callback The function name to call. Defaults to eventName +-- @param baseSystem The BaseSystem of the system to add +-- @param callbackName The callbackName to register to +-- @param callback The function name to call. Defaults to callbackName -- @param enabled If the system is enabled. Defaults to true -- @return self -function World:addSystem(system, eventName, callback, enabled) - if not Type.isSystem(system) then - error("bad argument #1 to 'World:addSystem' (System expected, got "..type(system)..")", 2) +function World:addSystem(baseSystem, callbackName, callback, enabled) + if not Type.isBaseSystem(baseSystem) then + error("bad argument #1 to 'World:addSystem' (baseSystem expected, got "..type(baseSystem)..")", 2) end - if system.__World and system.__World ~= self then - error("System already in World '" ..tostring(system.__World).."'") - end + local system = self.__systemLookup[baseSystem] + if (not system) then + -- System was not created for this world yet, so we create it ourselves + + print("Created system") + + system = baseSystem(self) + + self.__systemLookup[baseSystem] = system - if not self.systems:has(system) then self.systems:add(system) - system.__World = self - - system:addedTo(self) - - for i = 1, self.entities.size do - system:__evaluate(self.entities:get(i)) - end end - if eventName then - self.events[eventName] = self.events[eventName] or {} + -- Retroactively evaluate all entities for this system + for i = 1, self.entities.size do + system:__evaluate(self.entities:get(i)) + end - local i = #self.events[eventName] + 1 - self.events[eventName][i] = { + if callbackName then + self.events[callbackName] = self.events[callbackName] or {} + + local i = #self.events[callbackName] + 1 + self.events[callbackName][i] = { system = system, - callback = callback or eventName, + callback = callback or callbackName, enabled = enabled == nil or enabled, } if enabled == nil or enabled then - system:enabledCallback(callback or eventName) + system:enabledCallback(callback or callbackName) end end @@ -144,15 +150,15 @@ end --- Enables a System in the World. -- @param system The System to enable --- @param eventName The Event it was registered to +-- @param callbackName The Event it was registered to -- @param callback The callback it was registered with. Defaults to eventName -- @return self -function World:enableSystem(system, eventName, callback) +function World:enableSystem(system, callbackName, callback) if not Type.isSystem(system) then error("bad argument #1 to 'World:enableSystem' (System expected, got "..type(system)..")", 2) end - return self:setSystem(system, eventName, callback, true) + return self:setSystem(system, callbackName, callback, true) end --- Disables a System in the World. @@ -160,12 +166,12 @@ end -- @param eventName The Event it was registered to -- @param callback The callback it was registered with. Defaults to eventName -- @return self -function World:disableSystem(system, eventName, callback) +function World:disableSystem(system, callbackName, callback) if not Type.isSystem(system) then error("bad argument #1 to 'World:disableSystem' (System expected, got "..type(system)..")", 2) end - return self:setSystem(system, eventName, callback, false) + return self:setSystem(system, callbackName, callback, false) end --- Sets a System 'enable' in the World. @@ -174,15 +180,15 @@ end -- @param callback The callback it was registered with. Defaults to eventName -- @param enable The state to set it to -- @return self -function World:setSystem(system, eventName, callback, enable) +function World:setSystem(system, callbackName, callback, enable) if not Type.isSystem(system) then error("bad argument #1 to 'World:setSystem' (System expected, got "..type(system)..")", 2) end - callback = callback or eventName + callback = callback or callbackName if callback then - local listeners = self.events[eventName] + local listeners = self.events[callbackName] if listeners then for i = 1, #listeners do @@ -210,12 +216,12 @@ end -- @param eventName The Event that should be emitted -- @param ... Parameters passed to listeners -- @return self -function World:emit(eventName, ...) - if not eventName or type(eventName) ~= "string" then - error("bad argument #1 to 'World:emit' (String expected, got "..type(eventName)..")") +function World:emit(callbackName, ...) + if not callbackName or type(callbackName) ~= "string" then + error("bad argument #1 to 'World:emit' (String expected, got "..type(callbackName)..")") end - local listeners = self.events[eventName] + local listeners = self.events[callbackName] if listeners then for i = 1, #listeners do @@ -234,11 +240,9 @@ end -- @return self function World:clear() for i = 1, self.entities.size do - self.entities:get(i):destroy() + self.removeEntity(self.entities:get(i)) end - self:flush() - return self end