local tiny = require("lib.tiny") require("lib.inspect") ---@class TinyDebug local tinyDebug = {} -- TODO: Track some debugName field when debugging? local function worldMetaIndex() local worldMetaTable = getmetatable(tiny.world()) return worldMetaTable.__index end local function getCurrentTimeMilliseconds() return love.timer.getTime() * 1000 end --- Only applies to already-created systems! --- Performs a `world:refresh()` to ensure that all added systems are installed. ---@param world World function tinyDebug.logSystemUpdateTime(world) world:refresh() local systems = world.systems for i = 1, #systems do local system = systems[i] local wrappedUpdate = system.update system.update = function(...) local currentMs = getCurrentTimeMilliseconds() local ret = wrappedUpdate(...) local endTimeMs = getCurrentTimeMilliseconds() print(tostring(endTimeMs - currentMs) .. "ms taken to update system '" .. (system.name or i) .. "'") return ret end end end function tinyDebug.trackEntityAges() ENTITY_INIT_MS = { "ENTITY_INIT_MS" } -- tiny._trackEntityAges = true tiny._wrapAddEntity(function(_, entity) entity[ENTITY_INIT_MS] = getCurrentTimeMilliseconds() end) end --- Throws an error if trackEntityAges() has not been called function tinyDebug.getEntityAgeMs(entity) if ENTITY_INIT_MS == nil then error("trackEntityAges() has not been enabled!") end return getCurrentTimeMilliseconds() - entity[ENTITY_INIT_MS] end function tinyDebug.warnWhenNonDataOnEntities() local function checkForNonData(e, nested, tableCache) nested = nested or false tableCache = tableCache or {} local valType = type(e) if valType == "table" then if tableCache[e] then return end tableCache[e] = true for k, v in pairs(e) do local keyWarning = checkForNonData(k, true, tableCache) if keyWarning then return keyWarning end local valueWarning = checkForNonData(v, true, tableCache) if valueWarning then return valueWarning end end elseif valType == "function" or valType == "thread" or valType == "userdata" then return valType end end tiny._wrapAddEntity(function(_, entity) local nonDataType = checkForNonData(entity) if nonDataType then print("Detected non-data type '" .. nonDataType .. "' on entity") end end) end function tinyDebug.throwOnUnusedEntities() ---@return boolean local function entityMatchesSomeSystem(world, entity) local systems = world.systems for i = 1, #systems do local system = systems[i] local filter = system.filter if filter and filter(system, entity) then return true end end return false end ---@return Entity[] local function anyUnmatchedEntities(world) local unmatchedEntities = {} local el = world.entities for i = 1, #el do if not entityMatchesSomeSystem(world, el[i]) then unmatchedEntities[#unmatchedEntities + 1] = el[i] end end return unmatchedEntities end local tinyUpdate = tiny.update worldMetaIndex().update = function(world, dt, filter) local ret = tinyUpdate(world, dt, filter) local ecAll = world:getEntityCount() local unmatchedEntities = anyUnmatchedEntities(world) if #unmatchedEntities ~= 0 then error("UNUSED ENTITY COUNT: " .. #unmatchedEntities .. "/" .. ecAll .. " " .. Inspect(unmatchedEntities, { depth = 2 })) end return ret end end return tinyDebug