From 892f4d47000e72678a20518607bc5a14f2ae78fc Mon Sep 17 00:00:00 2001 From: Pablo Ariel Mayobre Date: Tue, 14 Feb 2023 18:14:24 -0300 Subject: [PATCH] Error handling overhaul --- concord/component.lua | 8 ++++---- concord/entity.lua | 16 ++++++++-------- concord/filter.lua | 35 ++++++++++++++++++++++++++++++----- concord/system.lua | 2 +- concord/utils.lua | 10 +++++++--- concord/world.lua | 14 +++++++------- 6 files changed, 57 insertions(+), 28 deletions(-) diff --git a/concord/component.lua b/concord/component.lua index 5122e9a..351d00c 100644 --- a/concord/component.lua +++ b/concord/component.lua @@ -16,19 +16,19 @@ Component.__mt = { -- @treturn Component A new ComponentClass function Component.new(name, populate) if (type(name) ~= "string") then - error("bad argument #1 to 'Component.new' (string expected, got "..type(name)..")", 2) + Utils.error(2, "bad argument #1 to 'Component.new' (string expected, got %s)", type(name)) end if (string.match(name, Components.__REJECT_MATCH) ~= "") then - error("bad argument #1 to 'Component.new' (Component names can't start with '"..Components.__REJECT_PREFIX.."', got "..name..")", 2) + Utils.error(2, "bad argument #1 to 'Component.new' (Component names can't start with '%s', got %s)", Components.__REJECT_PREFIX, name) end if (rawget(Components, name)) then - error("bad argument #1 to 'Component.new' (ComponentClass with name '"..name.."' was already registerd)", 2) -- luacheck: ignore + Utils.error(2, "bad argument #1 to 'Component.new' (ComponentClass with name '%s' was already registerd)", name) -- luacheck: ignore end if (type(populate) ~= "function" and type(populate) ~= "nil") then - error("bad argument #1 to 'Component.new' (function/nil expected, got "..type(populate)..")", 2) + Utils.error(2, "bad argument #1 to 'Component.new' (function/nil expected, got %s)", type(populate)) end local componentClass = setmetatable({ diff --git a/concord/entity.lua b/concord/entity.lua index 7bdbdc2..fb4617c 100644 --- a/concord/entity.lua +++ b/concord/entity.lua @@ -18,7 +18,7 @@ Entity.__mt = { -- @treturn Entity A new Entity function Entity.new(world) if (world ~= nil and not Type.isWorld(world)) then - error("bad argument #1 to 'Entity.new' (world/nil expected, got "..type(world)..")", 2) + Utils.error(2, "bad argument #1 to 'Entity.new' (world/nil expected, got %s)", type(world)) end local e = setmetatable({ @@ -68,7 +68,7 @@ function Entity:give(name, ...) local ok, componentClass = Components.try(name) if not ok then - error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2) + Utils.error(2, "bad argument #1 to 'Entity:get' (%s)", componentClass) end give(self, name, componentClass, ...) @@ -85,7 +85,7 @@ function Entity:ensure(name, ...) local ok, componentClass = Components.try(name) if not ok then - error("bad argument #1 to 'Entity:ensure' ("..componentClass..")", 2) + Utils.error(2, "bad argument #1 to 'Entity:ensure' (%s)", componentClass) end if self[name] then @@ -104,7 +104,7 @@ function Entity:remove(name) local ok, componentClass = Components.try(name) if not ok then - error("bad argument #1 to 'Entity:remove' ("..componentClass..")", 2) + Utils.error(2, "bad argument #1 to 'Entity:remove' (%s)", componentClass) end remove(self, name) @@ -118,7 +118,7 @@ end -- @treturn Entity self function Entity:assemble(assemblage, ...) if type(assemblage) ~= "function" then - error("bad argument #1 to 'Entity:assemble' (function expected, got "..type(assemblage)..")") + Utils.error(2, "bad argument #1 to 'Entity:assemble' (function expected, got %s)", type(assemblage)) end assemblage(self, ...) @@ -154,7 +154,7 @@ function Entity:has(name) local ok, componentClass = Components.try(name) if not ok then - error("bad argument #1 to 'Entity:has' ("..componentClass..")", 2) + Utils.error(2, "bad argument #1 to 'Entity:has' (%s)", componentClass) end return self[name] and true or false @@ -167,7 +167,7 @@ function Entity:get(name) local ok, componentClass = Components.try(name) if not ok then - error("bad argument #1 to 'Entity:get' ("..componentClass..")", 2) + Utils.error(2, "bad argument #1 to 'Entity:get' (%s)", componentClass) end return self[name] @@ -219,7 +219,7 @@ function Entity:deserialize(data) local componentData = data[i] if (not Components.has(componentData.__name)) then - error("bad argument #1 to 'Entity:deserialize' (ComponentClass '"..tostring(componentData.__name).."' wasn't yet loaded)") -- luacheck: ignore + Utils.error(2, "bad argument #1 to 'Entity:deserialize' (ComponentClass '%s' wasn't yet loaded)", tostring(componentData.__name)) -- luacheck: ignore end local componentClass = Components[componentData.__name] diff --git a/concord/filter.lua b/concord/filter.lua index a959609..f0f9262 100644 --- a/concord/filter.lua +++ b/concord/filter.lua @@ -6,6 +6,7 @@ local PATH = (...):gsub('%.[^%.]+$', '') local List = require(PATH..".list") local Type = require(PATH..".type") +local Utils = require(PATH..".utils") local Components = require(PATH..".components") @@ -20,18 +21,18 @@ Filter.__mt = { -- @tparam onComponent Optional function, called when a component is valid. function Filter.validate (name, def, onComponent) if type(def) ~= 'table' then - error("invalid component list for filter '"..name.."' (table expected, got "..type(def)..")", 3) + Utils.error(3, "invalid component list for filter '%s' (table expected, got %s)", name, type(def)) end if not onComponent and def.constructor and not Type.isCallable(def.constructor) then - error("invalid pool constructor (callable expected, got "..type(def.constructor)..")", 3) + Utils.error(3, "invalid pool constructor for filter '%s' (callable expected, got %s)", name, type(def.constructor)) end for n, component in ipairs(def) do local ok, err, reject = Components.try(component, true) if not ok then - error("invalid component for filter '"..name.."' at position #"..n.." ("..err..")", 3) + Utils.error(3, "invalid component for filter '%s' at position #%d (%s)", name, n, err) end if onComponent then @@ -61,14 +62,38 @@ function Filter.parse (name, def) return required, rejected end +local REQUIRED_METHODS = {"add", "remove", "has", "clear"} +local VALID_POOL_TYPES = {table=true, userdata=true, lightuserdata=true, cdata=true} + +function Filter.isValidPool (name, pool) + local poolType = type(pool) + --Check that pool is not nil + if not VALID_POOL_TYPES[poolType] then + Utils.error(3, "invalid value returned by pool '%s' constructor (table expected, got %s).", name, type(pool)) + end + + --Check if methods are callables + for _, method in ipairs(REQUIRED_METHODS) do + if not Type.isCallable(pool[method]) then + Utils.error(3, "invalid :%s method on pool '%s' (callable expected, got %s).", method, name, type(pool[method])) + end + end +end + --- Creates a new Filter -- @string name Name for the Filter. -- @tparam table definition Table containing the Filter Definition -- @treturn Filter The new Filter -- @treturn Pool The associated Pool function Filter.new (name, def) - local constructor = def.constructor or List - local pool = constructor(def) + local pool + + if def.constructor then + pool = def.constructor(def) + Filter.isValidPool(name, pool) + else + pool = List() + end local required, rejected = Filter.parse(name, def) diff --git a/concord/system.lua b/concord/system.lua index 2d3a802..6ed6d6c 100644 --- a/concord/system.lua +++ b/concord/system.lua @@ -50,7 +50,7 @@ System.mt = { function System.new(definition) for name, def in pairs(definition) do if type(name) ~= 'string' then - error("invalid name for filter (string key expected, got "..type(name)..")", 2) + Utils.error(2, "invalid name for filter (string key expected, got %s)", type(name)) end Filter.validate(name, def) diff --git a/concord/utils.lua b/concord/utils.lua index 2d3d742..1bf8710 100644 --- a/concord/utils.lua +++ b/concord/utils.lua @@ -3,6 +3,10 @@ local Utils = {} +function Utils.error(level, str, ...) + error(string.format(str, ...), level + 1) +end + --- Does a shallow copy of a table and appends it to a target table. -- @param orig Table to copy -- @param target Table to append to @@ -22,13 +26,13 @@ end -- @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) + Utils.error(2, "bad argument #1 to 'loadNamespace' (string/table of strings expected, got %s)", type(pathOrFiles)) 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) + Utils.error(2, "bad argument #1 to 'loadNamespace' (path '%s' not found)", pathOrFiles) end local files = love.filesystem.getDirectoryItems(pathOrFiles) @@ -50,7 +54,7 @@ function Utils.loadNamespace(pathOrFiles, namespace) 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 + Utils.error(2, "bad argument #2 to 'loadNamespace' (string/table of strings expected, got table containing %s)", type(path)) -- luacheck: ignore end local name = path diff --git a/concord/world.lua b/concord/world.lua index b55f030..bdd0af5 100644 --- a/concord/world.lua +++ b/concord/world.lua @@ -52,7 +52,7 @@ end -- @treturn World self function World:addEntity(e) if not Type.isEntity(e) then - error("bad argument #1 to 'World:addEntity' (Entity expected, got "..type(e)..")", 2) + Utils.error(2, "bad argument #1 to 'World:addEntity' (Entity expected, got %s)", type(e)) end if e.__world then @@ -70,7 +70,7 @@ end -- @treturn World self function World:removeEntity(e) if not Type.isEntity(e) then - error("bad argument #1 to 'World:removeEntity' (Entity expected, got "..type(e)..")", 2) + Utils.error(2, "bad argument #1 to 'World:removeEntity' (Entity expected, got %s)", type(e)) end self.__removed:add(e) @@ -207,7 +207,7 @@ function World:addSystem(systemClass) local ok, err = tryAddSystem(self, systemClass) if not ok then - error("bad argument #1 to 'World:addSystem' ("..err..")", 2) + Utils.error(2, "bad argument #1 to 'World:addSystem' (%s)", err) end return self @@ -225,7 +225,7 @@ function World:addSystems(...) local ok, err = tryAddSystem(self, systemClass) if not ok then - error("bad argument #"..i.." to 'World:addSystems' ("..err..")", 2) + Utils.error(2, "bad argument #%d to 'World:addSystems' (%s)", i, err) end end @@ -237,7 +237,7 @@ end -- @treturn boolean function World:hasSystem(systemClass) if not Type.isSystemClass(systemClass) then - error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2) + Utils.error(2, "bad argument #1 to 'World:hasSystem' (SystemClass expected, got %s)", type(systemClass)) end return self.__systemLookup[systemClass] and true or false @@ -248,7 +248,7 @@ end -- @treturn System System to get function World:getSystem(systemClass) if not Type.isSystemClass(systemClass) then - error("bad argument #1 to 'World:getSystem' (systemClass expected, got "..type(systemClass)..")", 2) + Utils.error(2, "bad argument #1 to 'World:getSystem' (SystemClass expected, got %s)", type(systemClass)) end return self.__systemLookup[systemClass] @@ -261,7 +261,7 @@ end -- @treturn World self function World:emit(functionName, ...) if not functionName or type(functionName) ~= "string" then - error("bad argument #1 to 'World:emit' (String expected, got "..type(functionName)..")") + Utils.error(2, "bad argument #1 to 'World:emit' (String expected, got %s)", type(functionName)) end local shouldFlush = self.__emitSDepth == 0