diff --git a/.gitmodules b/.gitmodules index a08c951d775c733d157e782cebe3568af47a0e8e..edbd4839edd6b6ab71197dea705862cf485d7f73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "module/lily"] path = module/lily url = https://github.com/gphg/lily.git +[submodule "module/log"] + path = module/log + url = https://github.com/gphg/log.lua.git diff --git a/game/init.lua b/game/init.lua index 22909873dae81cab5f8965673f8df779bc8d165a..ac54346b7a302ba76647498d8ed3695342143b49 100644 --- a/game/init.lua +++ b/game/init.lua @@ -22,6 +22,7 @@ Game.util = require 'game.util' -- must be loaded first! Game.base = require 'game.base' Game.display = require 'game.display' Game.handler = require 'game.handler' +Game.logger = require 'game.logger' Game.manager = require 'game.manager' Game.overlay = require 'game.overlay' Game.pool = require 'game.pool' diff --git a/game/logger.lua b/game/logger.lua new file mode 100644 index 0000000000000000000000000000000000000000..66b74dd725f39a49342cb421a81ea2ec02691a63 --- /dev/null +++ b/game/logger.lua @@ -0,0 +1,100 @@ +---@class Logger: Super +---@overload fun(label?: string, logfile?: string, basepath?: string): self +local Logger = require 'game.base':extend({}, ...) + +Logger.basepath = '' -- either empty or string with trailling slash +Logger.outfile = 'latest.log' +Logger._instances = {} +Logger.debugthread = 3 +Logger.level = 'trace' + +---@alias LoggerMode +---| 'trace' +---| 'debug' +---| 'info' +---| 'warn' +---| 'error' +---| 'fatal' + +local love, table, Util = love, table, require 'util' +local filesystem, loggerModule = love.filesystem, Util.log +local rawget, rawset, ipairs = rawget, rawset, ipairs + +---@param label? string +---@param outfile? string +---@param basepath? string +function Logger:constructor(label, outfile, basepath) + self.label = label + self.outfile = outfile + self.basepath = basepath + + local dict = Logger._instances + rawset(dict, #dict + 1, self) +end + +---@param lmode LoggerMode|string +---@return self +---@return string? formattedString +function Logger:log(lmode, ...) + local mode = loggerModule[lmode] + + if not Util.isCallable(mode) then + return self + end + + loggerModule.level = self.level + loggerModule.debugthread = self.debugthread + local str = mode(...) + loggerModule.debugthread = 2 + + if not str or str == '' then + return self + end + + rawset(self, #self + 1, str) + return self, str +end + +---@param outfile string? +function Logger:flush(outfile) + outfile = outfile or self.outfile + local logpath = self.basepath .. outfile + + if self == Logger then + self:log('debug', 'Detected as Logger class instead an instance.') + :log('debug', 'Flushing all registered instances.') + + local dict = Logger._instances + for _, obj in ipairs(dict) do + ---@cast obj Logger + local n, s = #obj, obj.label or tostring(obj) + Logger.flush(obj, obj.outfile or outfile) + if n > 0 then + self:log('debug', 'Flushed', s) + end + end + -- Flushed the external module too + loggerModule.flush() + end + + for i = 1, #self do + filesystem.append(logpath, rawget(self, i)) + rawset(self, i, nil) + end + + self:log('debug', 'Logs saved on', logpath) +end + +---Release self and unregestered self from Logger instance table. +---Does not deference self! +function Logger:release() + self:flush() + local dict = Logger._instances + for i = #dict, 1, -1 do + table.remove(dict, i) + end +end + +loggerModule.outfile = loggerModule.outfile or Logger.outfile + +return Logger diff --git a/game/scene/boot.lua b/game/scene/boot.lua index 7241bb1416a4f4aa1723adbd469b1fccf70e9880..a51fdecda615aaceeda508b36c5d6a205fb0cbba 100644 --- a/game/scene/boot.lua +++ b/game/scene/boot.lua @@ -1,13 +1,24 @@ ---Boot is supposed to be called on love.load +---@class BootScene: Scene local boot = require 'game.scene':extend({}, ...) -local Timer, meta = require 'game.ticker', require 'meta' - -boot.handler = require 'game.handler' () +local Game, meta = require 'game', require 'meta' +local Timer, Handler, Logger = Game.ticker, Game.handler, Game.logger ---@param _ Scene? ---@param tobeloaded string function boot:load(_, tobeloaded, ...) + --- Expectation: trace to debug is not available on release + local loggerLevel = os.getenv('RUNTIME_LOG_LEVEL') + Logger.level = loggerLevel + or not package.loaded.lldebugger and 'info' + or Logger.level + + Logger:log('info', '|', os.date(), '| Booting up...') + + boot.handler = boot.handler or Handler() + + local timeout = meta.timeout or 0 if timeout > 0 then Timer:after(timeout, function() love.event.quit(0) end) diff --git a/game/scene/title.lua b/game/scene/title.lua index d54b8fe273f7e58741082ed39685d0344c59c68b..36b5f075055839dbb7f322482e980bbb718659d7 100644 --- a/game/scene/title.lua +++ b/game/scene/title.lua @@ -25,11 +25,8 @@ function title:load(...) local fontHeight = self.Font:getHeight() - -- Please forgive me. - rawset(self, #self + 1, (function(parent) - local demo = love.filesystem.getInfo('demo') - if not demo or demo.type ~= 'directory' then return end - + local demo = love.filesystem.getInfo('demo') + if demo and demo.type == 'directory' then -- Insert simple sample entity (object). It simply loop over. local demoT = require 'game.base' () function demoT:draw(i) @@ -39,12 +36,12 @@ function title:load(...) function demoT.keypressed(_, key) if key == 'd' then - parent._manager:push('demo') + self._manager:push('demo') end end - return demoT - end)(self)) + rawset(self, #self + 1, demoT) + end ---Push the loading screen ---@module "scene.loading" diff --git a/game/util.lua b/game/util.lua index 6fed7a5dc8ef405774dceb08dfb1bb0e2c227553..c19b7ee298401493403f548c20152e841a27bd10 100644 --- a/game/util.lua +++ b/game/util.lua @@ -24,7 +24,8 @@ Util.reload('util', Util) local table, math = table, math local print, type, ipairs, select, getmetatable = print, type, ipairs, select, getmetatable -Util.print = print +Util.log = require 'module.log.log' +Util.print = Util.log and Util.log.info or print Util.memo = require 'module.knife.memoize' Util.bind = require 'module.knife.bind' diff --git a/main.lua b/main.lua index 70a1c78a8bbd33724572d1655e963dad180d2e14..e73d081533067b23d5839dca3c4ed329a2dafce1 100644 --- a/main.lua +++ b/main.lua @@ -18,8 +18,7 @@ setmetatable(_G, { --- local Game = require 'game' -local Util, Ticker, Handler, Overlay = Game.util, Game.ticker, Game.handler, Game.overlay -local print = Util.print or print +local Ticker, Handler, Overlay, Logger = Game.ticker, Game.handler, Game.overlay, Game.logger Overlay.init() @@ -27,14 +26,17 @@ Overlay.init() ---Called when shutdown --- ---@param message string? +---@param level LoggerMode|string? ---@return boolean -function love.shutdown(message) - if message then print('Shutting down: ' .. message) end +function love.shutdown(message, level) + Logger:log('info', '|', os.date(), '| Shutting down...') + if message then Logger:log(level or 'info', 'Reason: ' .. message) end if love.audio then love.audio.stop() end for _, archive in pairs(meta.mounted) do love.filesystem.unmount(archive[1]) archive[#archive]:release() end + Logger:flush() return true end @@ -48,7 +50,8 @@ end local old_errhand = love.errhand --[[@as function]] or love.errorhandler function love.errorhandler(msg) - love.shutdown('errorhandler: ' .. msg) + -- Known Issue: doesn't log trackback. + love.shutdown(msg, 'error') return old_errhand(msg) end diff --git a/module/log b/module/log new file mode 160000 index 0000000000000000000000000000000000000000..b4c72ff40f9ac640b500a67e85cd29bb3645601b --- /dev/null +++ b/module/log @@ -0,0 +1 @@ +Subproject commit b4c72ff40f9ac640b500a67e85cd29bb3645601b