├── .gitignore ├── screenshots └── loveprofiled-demo.gif ├── drivers ├── canvas.lua └── console.lua ├── LICENSE ├── defaults.lua ├── README.md └── init.lua /.gitignore: -------------------------------------------------------------------------------- 1 | loveprofiler.log 2 | -------------------------------------------------------------------------------- /screenshots/loveprofiled-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dknight/loveprofiler/HEAD/screenshots/loveprofiled-demo.gif -------------------------------------------------------------------------------- /drivers/canvas.lua: -------------------------------------------------------------------------------- 1 | -- LoveProfiler console driver implementation. 2 | 3 | local canvas = {} 4 | 5 | canvas.print = function(t, i) 6 | love.graphics.setColor(table.unpack(t._out[i].color)) 7 | love.graphics.print( 8 | t._out[i].msg, 9 | t.config.draw_x, 10 | t.config.draw_y + (i - 1) * t.config.font_size 11 | ) 12 | end 13 | 14 | canvas.flush = function(t) 15 | t._out = {} 16 | love.graphics.reset() 17 | end 18 | 19 | return canvas 20 | 21 | -------------------------------------------------------------------------------- /drivers/console.lua: -------------------------------------------------------------------------------- 1 | -- LoveProfiler console driver implementation. 2 | 3 | ---Checks that OS is Microsoft Windows. 4 | ---@return boolean 5 | local function isWindows() 6 | return type(package) == "table" and type(package.config) == "string" and package.config:sub(1, 1) == "\\" 7 | end 8 | 9 | ---Checks that termianl supports colors. 10 | ---@return boolean 11 | local function isColorSupported() 12 | if isWindows() then 13 | return not not os.getenv("ANSICON") 14 | end 15 | return true 16 | end 17 | 18 | ---@class console 19 | local console = {} 20 | 21 | ---@param t table 22 | ---@param i number 23 | console.print = function(t, i) 24 | -- Color not yet implemented. Maybe it is need to use dependency lcurses. 25 | io.write(t._out[i].msg) 26 | end 27 | 28 | ---@param t table 29 | console.flush = function(t) 30 | t._out = {} 31 | -- clear the terminal ANSI sequence. Should work on Windows if ANSI.SYS 32 | -- is still supported. 33 | if isColorSupported() then 34 | io.write("\027[H\027[2J") 35 | end 36 | end 37 | 38 | return console 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Dmitri Smirnov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /defaults.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Defaults of the LoveProfiler configuration. 3 | -- 4 | local defaults = { 5 | ---------------------------------------------- 6 | -- Drivers -- 7 | ---------------------------------------------- 8 | -- Implemented drivers: 9 | -- "canvas" outputs on the canvas inside Love2D application; 10 | -- "console" outputs in the console (terminal). 11 | driver = "canvas", 12 | 13 | ---------------------------------------------- 14 | -- Drawing options (only for canvas driver) -- 15 | ---------------------------------------------- 16 | -- Default out put color (R, G, B, A). 17 | color = {0, 1, 0, 1}, 18 | 19 | -- Font size of output. 20 | font_size = 14, 21 | 22 | -- x position of the output. 23 | draw_x = 5, 24 | 25 | -- y position of the output. 26 | draw_y = 5, 27 | 28 | ---------------------------------------------- 29 | -- Enabled these options in output -- 30 | ---------------------------------------------- 31 | -- Display the name of the operating system. 32 | show_os = true, 33 | 34 | -- Display FPS (Frames Per Second) 35 | show_fps = true, 36 | 37 | -- Display memory usage. 38 | show_mem = true, 39 | 40 | -- Shows the time between 2 frames. 41 | show_delta = true, 42 | 43 | -- Shows the duration of programm running 44 | show_duration = true, 45 | 46 | -- Show mouse coordindates. 47 | show_coords = false, 48 | 49 | -- Coordindates offest, only for canvas driver. 50 | x_coords_offset = 10, 51 | y_coords_offset = -10, 52 | 53 | -- TODO: implement more id needed 54 | 55 | ---------------------------------------------- 56 | -- Log options -- 57 | ---------------------------------------------- 58 | -- Enables of disables log completely. 59 | log_enabled = true, 60 | 61 | -- How many last messages will be displayed. 62 | log_size = 10, 63 | 64 | -- Logs output into file, use nil or false to disable file logging. 65 | log_path = "loveprofiler.log", 66 | 67 | -- Date format in the logs. 68 | log_date_format = "%Y-%m-%dT%H:%M:%S", 69 | 70 | -- Log info level color (R, G, B, A). 71 | log_color_info = {1, 1, 1, 1}, 72 | 73 | -- Log warning level color (R, G, B, A). 74 | log_color_warn = {1, 1, 0, 1}, 75 | 76 | -- Log error level color (R, G, B, A). 77 | log_color_error = {1, 0, 0, 1} 78 | } 79 | 80 | return defaults 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LoveProfile for Löve2D 2 | 3 | LoveProfiler is an extremely simple logger and profiler for [Löve2D](https://love2d.org/) 4 | framework. It displays useful information about the current state of a running game. 5 | It also can log events at three levels: information, warnings 6 | and errors. The output of the profiler is displayed on the Löve2D canvas or 7 | in the console (terminal). 8 | 9 | ![LoveProfiler Demo](/screenshots/loveprofiled-demo.gif?raw=true) 10 | 11 | ## Installation 12 | 13 | Installation is very easy. Just use git clone or copy directory `loveprofiler` 14 | into your game's directory where file `main.lua` is. 15 | 16 | ```sh 17 | git clone https://github.com/dknight/loveprofiler 18 | ``` 19 | 20 | Example project structure: 21 | 22 | ``` 23 | love2dproject/ 24 | loveprofiler/ 25 | main.lua 26 | ``` 27 | 28 | ## Usage 29 | 30 | The most basic usage, create an instance of `love.load()` then call start() method inside `love.draw()`. 31 | 32 | ```lua 33 | local LoveProfiler = require("loveprofiler") 34 | 35 | function love.load() 36 | profiler = LoveProfiler:new() 37 | end 38 | 39 | function love.draw() 40 | profiler:start() 41 | end 42 | 43 | ``` 44 | 45 | ## Configuration 46 | 47 | You can find all possible configuration options with detailed description in `defaults.lua` file. You can override any option when you create a new instance. 48 | 49 | ```lua 50 | local LoveProfiler = require("loveprofiler") 51 | 52 | function love.load() 53 | profiler = LoveProfiler:new{config = { 54 | driver = "console", 55 | font_size = 17, 56 | draw_x = 400, 57 | color = {0, 0, 1, 1} 58 | }} 59 | end 60 | 61 | function love.draw() 62 | profiler:start() 63 | end 64 | ``` 65 | 66 | ## Drivers 67 | 68 | At the moment only 2 drivers are supported: canvas and console. If a driver is set to **canvas** then information is displayed directly on the Löve2D canvas. If **console** then output will be displayed in the console (system terminal). See `defaults.lua` file for more details. 69 | 70 | ## Logging 71 | 72 | There are 3 levels of messages: 73 | 74 | - information; 75 | - warning; 76 | - error. 77 | 78 | You can add a log entry like this: 79 | 80 | ```lua 81 | local LoveProfiler = require("loveprofiler") 82 | 83 | function love.load() 84 | profiler = LoveProfiler:new() 85 | profiler:addMessage("Your information message", LoveProfiler.LOG_INFO) 86 | profiler:addMessage("Your warning message", LoveProfiler.LOG_WARN) 87 | profiler:addMessage("Your error message", LoveProfiler.LOG_ERROR) 88 | end 89 | ``` 90 | 91 | By default, logs are also saved in a file `loveprofiler.log`. You can change the logging destination by changing the configuration property `log_path = "your_path_to_log_file"`, see `defaults.lua`. Also if you set `log_path = nil`, output won't be saved into file at all. 92 | 93 | ### Disabling logging 94 | 95 | To completely disable logging set configuration property `log_enabled = false`. 96 | 97 | ## Using multiple profilers 98 | 99 | You can create as many LoveProfiler instances as you want with different configurations. 100 | 101 | ```lua 102 | local LoveProfiler = require("loveprofiler") 103 | 104 | function love.load() 105 | profiler1 = LoveProfiler:new() 106 | profiler2 = LoveProfiler:new{config = { 107 | driver = "console", 108 | font_size = 18, 109 | draw_x = 400, 110 | }} 111 | end 112 | 113 | function love.draw() 114 | profiler1:start() 115 | profiler2:start() 116 | end 117 | ``` 118 | 119 | ## Contribution 120 | 121 | Found a bug or implemented a new feature? Do not hesitate to make a pull request. 122 | 123 | ## License 124 | 125 | This software is distributed under [MIT license](https://opensource.org/licenses/MIT), so you are free to use it. 126 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- LoveProfiler for Love2D Framework 2 | -- Author: Dmitri Smirnov 3 | -- MIT License 4 | -- VERSION: 1.0.1 5 | local defaults = require("loveprofiler.defaults") 6 | 7 | ---@alias level 8 | ---| '"INFO"' 9 | ---| '"WARNING"' 10 | ---| '"ERROR"' 11 | 12 | ---@class LoveProfiler 13 | ---@field public config table 14 | ---@field private _out table 15 | ---@field private _log table 16 | ---@field private _driver table 17 | local LoveProfiler = { 18 | LOG_INFO = "INFO", ---[[@as level]] 19 | LOG_WARN = "WARNING", ---[[@as level]] 20 | LOG_ERROR = "ERROR", ---[[@as level]] 21 | } 22 | 23 | ---Creates new instance aka constructor 24 | ---@param t? table - Config can be passed here. 25 | ---@return table - New instance of LoveProfiler. 26 | function LoveProfiler:new(t) 27 | t = t or {} 28 | 29 | t._out = {} 30 | t._log = {} 31 | 32 | -- Merge configs. 33 | local cfg = {} 34 | for k, v in pairs(defaults) do 35 | cfg[k] = v 36 | end 37 | if t.config then 38 | for k, v in pairs(t.config) do 39 | cfg[k] = v 40 | end 41 | end 42 | t.config = cfg 43 | 44 | ---@private 45 | t._driver = require("loveprofiler.drivers." .. t.config.driver) 46 | 47 | setmetatable(t, self) 48 | self.__index = self 49 | return t 50 | end 51 | 52 | ---Adds message to log. 53 | ---@param msg string - Message to be logged. 54 | ---@param level? level level Level of the logged 55 | function LoveProfiler:addMessage(msg, level) 56 | level = level or LoveProfiler.LOG_INFO 57 | self._log[#self._log + 1] = { 58 | ["msg"] = msg, 59 | ["level"] = level, 60 | } 61 | if self.config.log_path then 62 | local time = os.date(self.config.log_date_format) 63 | local file = io.open(self.config.log_path, "a") 64 | if file ~= nil then 65 | file:write(string.format("%s %s %s\n", time, level, msg)) 66 | file:close() 67 | end 68 | end 69 | end 70 | 71 | --- Processed the log if log is enabled. 72 | function LoveProfiler:processLog() 73 | local start = #self._log > self.config.log_size 74 | and #self._log - self.config.log_size 75 | or 1 76 | local stop = #self._log 77 | for i = start, stop do 78 | local color 79 | if self._log[i].level == LoveProfiler.LOG_WARN then 80 | color = self.config.log_color_warn 81 | elseif self._log[i].level == LoveProfiler.LOG_ERROR then 82 | color = self.config.log_color_error 83 | else 84 | color = self.config.log_color_info 85 | end 86 | 87 | self:appendToOutput(self._log[i].msg, color) 88 | end 89 | end 90 | 91 | ---Adds line to output. 92 | ---@param msg string Message (line) to be added. 93 | ---@param color? table Color as table {r, g, b, a}. 94 | function LoveProfiler:appendToOutput(msg, color) 95 | color = color or self.config.color 96 | self._out[#self._out + 1] = { 97 | ["msg"] = msg .. "\n", 98 | ["color"] = color, 99 | } 100 | end 101 | 102 | ---Gets the name of OS. 103 | ---@return string 104 | function LoveProfiler:getOS() 105 | return love.system.getOS() 106 | end 107 | 108 | ---Gets FPS count. 109 | ---@return number 110 | function LoveProfiler:getFPS() 111 | return love.timer.getFPS() 112 | end 113 | 114 | ---Gets mouse coordinates. 115 | ---@return number, number 116 | function LoveProfiler:mouse() 117 | return love.mouse.getX(), love.mouse.getY() 118 | end 119 | 120 | ---Gets the time between 2 frames in milliseconds. 121 | ---@return number 122 | function LoveProfiler:getDelta() 123 | return love.timer.getDelta() * 1000 124 | end 125 | 126 | ---Gets the memory usage in bytes, textures + Lua + Love2d program. 127 | ---@return number 128 | function LoveProfiler:getMemoryUsage() 129 | local stats = love.graphics.getStats() 130 | return math.floor(collectgarbage("count") + stats.texturememory + 0.5) 131 | end 132 | 133 | ---Gets time duration 134 | ---@return number 135 | function LoveProfiler:getDuration() 136 | return love.timer.getTime() 137 | end 138 | 139 | ---@return string 140 | ---Formats duration 141 | function LoveProfiler:formatDuration() 142 | local f = "Duration: %02d:%02d:%02d.%03d" 143 | local t = LoveProfiler:getDuration() 144 | local h = t % (24 * 3600) / 3600 145 | 146 | t = t % 3600 147 | local m = t / 60 148 | 149 | t = t % 60 150 | local s = t 151 | 152 | local ms = (s - math.floor(s)) * 1000 153 | 154 | return string.format(f, h, m, s, ms) 155 | end 156 | 157 | ---Starts the profiler, should be used in love.draw() method. 158 | function LoveProfiler:start() 159 | local font = love.graphics.newFont(self.config.font_size) 160 | love.graphics.setFont(font) 161 | 162 | if self.config.show_os then 163 | self:appendToOutput(string.format("OS: %s", self:getOS())) 164 | end 165 | 166 | if self.config.show_fps then 167 | self:appendToOutput(string.format("FPS: %s", self:getFPS())) 168 | end 169 | 170 | if self.config.show_mem then 171 | self:appendToOutput( 172 | string.format("MEM: %s bytes", self:getMemoryUsage()) 173 | ) 174 | end 175 | 176 | if self.config.show_coords and self.config.driver == "canvas" then 177 | love.graphics.setColor(self.config.color) 178 | local x, y = self:mouse() 179 | local coords = string.format("%d, %d", x, y) 180 | x = x + self.config.x_coords_offset 181 | y = y + self.config.y_coords_offset 182 | love.graphics.print(coords, x, y) 183 | love.graphics.reset() 184 | end 185 | 186 | if self.config.show_delta then 187 | self:appendToOutput(string.format("Delta: %.1fms", self:getDelta())) 188 | end 189 | 190 | if self.config.show_duration then 191 | self:appendToOutput(LoveProfiler:formatDuration()) 192 | end 193 | 194 | if self.config.log_enabled then 195 | if #self._log > 0 then 196 | self:appendToOutput("---------- Log ----------") 197 | end 198 | self:processLog() 199 | end 200 | 201 | for i = 1, #self._out do 202 | self._driver.print(self, i) 203 | end 204 | 205 | self._driver.flush(self) 206 | end 207 | 208 | return LoveProfiler 209 | --------------------------------------------------------------------------------