├── .gitignore ├── LICENSE.md ├── README.md ├── example ├── assets │ ├── example.atlas │ └── images │ │ └── icon_72.png ├── example.collection ├── example.script └── thing.go ├── game.project ├── input └── game.input_binding └── metrics ├── fps.go ├── fps.lua ├── fps.script ├── mem.go ├── mem.lua └── mem.script /.gitignore: -------------------------------------------------------------------------------- 1 | /.internal 2 | /build 3 | .externalToolBuilders 4 | .DS_Store 5 | Thumbs.db 6 | .lock-wscript 7 | *.pyc 8 | .project 9 | .cproject 10 | builtins -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Björn Ritzl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Defold Metrics 2 | Calculate and display performance metrics in Defold games. 3 | 4 | 5 | # Installation 6 | You can use Defold Metrics in your own project by adding this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your game.project file and in the dependencies field under project add: 7 | 8 | https://github.com/britzl/defold-metrics/archive/master.zip 9 | 10 | Or point to the ZIP file of a [specific release](https://github.com/britzl/defold-metrics/releases). 11 | 12 | 13 | # Usage 14 | * In your game.project settings, go to Dependencies,and add https://github.com/britzl/defold-metrics/archive/master.zip. 15 | * In the outline in your scene, right-click and "Add Game Object File" and choose "/metrics/fps.go" 16 | * Change the position on the game object to ex 10, 20, 0 to make it visible. 17 | * If you also want to use the Lua memory counter, add the "/metrics/mem.go" in the same way, but on position 10, 40, 0. 18 | 19 | Demo: https://britzl.github.io/Metrics/ 20 | 21 | 22 | ## FPS 23 | The FPS counter uses socket.gettime() to get an accurate timestamp and calculates an average FPS based on a sequence of samples. The FPS counter can be used in several ways: 24 | 25 | 1. Using fps.go() - Draw FPS counter at game object world position using draw text 26 | 2. Using fps.script - Draw FPS counter at game object world position using draw text 27 | 3. Using fps.lua - Draw FPS counter at specified position or get current FPS 28 | 29 | 30 | ### fps.create(samples, format, position, color) 31 | Create an instance of the FPS counter 32 | 33 | **PARAMETERS** 34 | * `samples` (number) - Optional sample count. This is the number of samples required before the module will calculate FPS. Defaults to 60. 35 | * `format` (string) - Optional format to draw FPS in. Defaults to "FPS %.2f" 36 | * `position` (string) - Optional position to draw FPS at. Defaults to v3(10, 20, 0) 37 | * `color` (string) - Optional color to use when drawing FPS text. Defaults to v4(0,0,1,1) 38 | 39 | **RETURNS** 40 | * `instance` (table) - An FPS counter instance 41 | 42 | 43 | ### fps.update() 44 | Call this once per frame. Once enough samples have been collected it is possible to call fps() to get the current FPS. 45 | 46 | 47 | ### fps.fps() 48 | Get the current FPS, based on collected samples. 49 | 50 | **RETURNS** 51 | * `fps` (number) - The calculated FPS 52 | 53 | 54 | ### fps.draw() 55 | Draw fps count text using `draw_debug_text`. 56 | 57 | 58 | 59 | ## Memory 60 | The memory counter uses `collectgarbage("count")` to get the amount of Lua memory used. The memory counter can be used in several ways: 61 | 62 | 1. Using mem.go() - Draw memory usage at game object world position using draw text 63 | 2. Using mem.script - Draw memory usage at game object world position using draw text 64 | 3. Using mem.lua - Draw memory usage at specified position or get current memory usage 65 | 66 | 67 | ### mem.create(format, position, color) 68 | Create an instance of the memory counter 69 | 70 | **PARAMETERS** 71 | * `format` (string) - Optional format to draw memory usage in. Defaults to "MEM %dkb" 72 | * `position` (string) - Optional position to draw memory usage at. Defaults to v3(10, 20, 0) 73 | * `color` (string) - Optional color to use when drawing memory usage text. Defaults to v4(0,0,1,1) 74 | 75 | **RETURNS** 76 | * `instance` (table) - A memory counter counter instance 77 | 78 | 79 | ### mem.update() 80 | Call this to get a new memory usage reading. 81 | 82 | 83 | ### mem.mem() 84 | Get the current memory usage, in kilobytes. 85 | 86 | **RETURNS** 87 | * `mem` (number) - The Lua memory usage 88 | 89 | 90 | ### mem.draw() 91 | Draw memory usage text using `draw_debug_text`. 92 | -------------------------------------------------------------------------------- /example/assets/example.atlas: -------------------------------------------------------------------------------- 1 | images { 2 | image: "/example/assets/images/icon_72.png" 3 | } 4 | margin: 0 5 | extrude_borders: 1 6 | inner_padding: 0 7 | -------------------------------------------------------------------------------- /example/assets/images/icon_72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-metrics/a8b31bf7a680e65e0f5a9d4a2b654d849b5ffd3c/example/assets/images/icon_72.png -------------------------------------------------------------------------------- /example/example.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | instances { 3 | id: "fps" 4 | prototype: "/metrics/fps.go" 5 | position { 6 | x: 10.0 7 | y: 20.0 8 | z: 0.0 9 | } 10 | rotation { 11 | x: 0.0 12 | y: 0.0 13 | z: 0.0 14 | w: 1.0 15 | } 16 | component_properties { 17 | id: "fps" 18 | properties { 19 | id: "color" 20 | value: "1.0, 1.0, 1.0, 1.0" 21 | type: PROPERTY_TYPE_VECTOR4 22 | } 23 | } 24 | scale3 { 25 | x: 1.0 26 | y: 1.0 27 | z: 1.0 28 | } 29 | } 30 | instances { 31 | id: "mem" 32 | prototype: "/metrics/mem.go" 33 | position { 34 | x: 90.0 35 | y: 20.0 36 | z: 0.0 37 | } 38 | rotation { 39 | x: 0.0 40 | y: 0.0 41 | z: 0.0 42 | w: 1.0 43 | } 44 | component_properties { 45 | id: "mem" 46 | properties { 47 | id: "color" 48 | value: "1.0, 1.0, 1.0, 1.0" 49 | type: PROPERTY_TYPE_VECTOR4 50 | } 51 | } 52 | scale3 { 53 | x: 1.0 54 | y: 1.0 55 | z: 1.0 56 | } 57 | } 58 | scale_along_z: 0 59 | embedded_instances { 60 | id: "go" 61 | data: "components {\n" 62 | " id: \"example\"\n" 63 | " component: \"/example/example.script\"\n" 64 | " position {\n" 65 | " x: 0.0\n" 66 | " y: 0.0\n" 67 | " z: 0.0\n" 68 | " }\n" 69 | " rotation {\n" 70 | " x: 0.0\n" 71 | " y: 0.0\n" 72 | " z: 0.0\n" 73 | " w: 1.0\n" 74 | " }\n" 75 | "}\n" 76 | "embedded_components {\n" 77 | " id: \"factory\"\n" 78 | " type: \"factory\"\n" 79 | " data: \"prototype: \\\"/example/thing.go\\\"\\n" 80 | "load_dynamically: false\\n" 81 | "\"\n" 82 | " position {\n" 83 | " x: 0.0\n" 84 | " y: 0.0\n" 85 | " z: 0.0\n" 86 | " }\n" 87 | " rotation {\n" 88 | " x: 0.0\n" 89 | " y: 0.0\n" 90 | " z: 0.0\n" 91 | " w: 1.0\n" 92 | " }\n" 93 | "}\n" 94 | "" 95 | position { 96 | x: 0.0 97 | y: 0.0 98 | z: 0.0 99 | } 100 | rotation { 101 | x: 0.0 102 | y: 0.0 103 | z: 0.0 104 | w: 1.0 105 | } 106 | scale3 { 107 | x: 1.0 108 | y: 1.0 109 | z: 1.0 110 | } 111 | } 112 | embedded_instances { 113 | id: "walls" 114 | data: "embedded_components {\n" 115 | " id: \"collisionobject\"\n" 116 | " type: \"collisionobject\"\n" 117 | " data: \"collision_shape: \\\"\\\"\\n" 118 | "type: COLLISION_OBJECT_TYPE_STATIC\\n" 119 | "mass: 0.0\\n" 120 | "friction: 0.1\\n" 121 | "restitution: 0.5\\n" 122 | "group: \\\"wall\\\"\\n" 123 | "mask: \\\"thing\\\"\\n" 124 | "embedded_collision_shape {\\n" 125 | " shapes {\\n" 126 | " shape_type: TYPE_BOX\\n" 127 | " position {\\n" 128 | " x: 320.0\\n" 129 | " y: 20.0\\n" 130 | " z: 0.0\\n" 131 | " }\\n" 132 | " rotation {\\n" 133 | " x: 0.0\\n" 134 | " y: 0.0\\n" 135 | " z: 0.0\\n" 136 | " w: 1.0\\n" 137 | " }\\n" 138 | " index: 0\\n" 139 | " count: 3\\n" 140 | " }\\n" 141 | " shapes {\\n" 142 | " shape_type: TYPE_BOX\\n" 143 | " position {\\n" 144 | " x: 320.0\\n" 145 | " y: 1136.0\\n" 146 | " z: 0.0\\n" 147 | " }\\n" 148 | " rotation {\\n" 149 | " x: 0.0\\n" 150 | " y: 0.0\\n" 151 | " z: 0.0\\n" 152 | " w: 1.0\\n" 153 | " }\\n" 154 | " index: 3\\n" 155 | " count: 3\\n" 156 | " }\\n" 157 | " shapes {\\n" 158 | " shape_type: TYPE_BOX\\n" 159 | " position {\\n" 160 | " x: 0.0\\n" 161 | " y: 568.0\\n" 162 | " z: 0.0\\n" 163 | " }\\n" 164 | " rotation {\\n" 165 | " x: 0.0\\n" 166 | " y: 0.0\\n" 167 | " z: 0.0\\n" 168 | " w: 1.0\\n" 169 | " }\\n" 170 | " index: 6\\n" 171 | " count: 3\\n" 172 | " }\\n" 173 | " shapes {\\n" 174 | " shape_type: TYPE_BOX\\n" 175 | " position {\\n" 176 | " x: 640.0\\n" 177 | " y: 568.0\\n" 178 | " z: 0.0\\n" 179 | " }\\n" 180 | " rotation {\\n" 181 | " x: 0.0\\n" 182 | " y: 0.0\\n" 183 | " z: 0.0\\n" 184 | " w: 1.0\\n" 185 | " }\\n" 186 | " index: 9\\n" 187 | " count: 3\\n" 188 | " }\\n" 189 | " data: 320.0\\n" 190 | " data: 10.0\\n" 191 | " data: 10.0\\n" 192 | " data: 320.0\\n" 193 | " data: 10.0\\n" 194 | " data: 10.0\\n" 195 | " data: 10.0\\n" 196 | " data: 568.0\\n" 197 | " data: 10.0\\n" 198 | " data: 10.0\\n" 199 | " data: 568.0\\n" 200 | " data: 10.0\\n" 201 | "}\\n" 202 | "linear_damping: 0.0\\n" 203 | "angular_damping: 0.0\\n" 204 | "locked_rotation: false\\n" 205 | "\"\n" 206 | " position {\n" 207 | " x: 0.0\n" 208 | " y: 0.0\n" 209 | " z: 0.0\n" 210 | " }\n" 211 | " rotation {\n" 212 | " x: 0.0\n" 213 | " y: 0.0\n" 214 | " z: 0.0\n" 215 | " w: 1.0\n" 216 | " }\n" 217 | "}\n" 218 | "" 219 | position { 220 | x: 0.0 221 | y: 0.0 222 | z: 0.0 223 | } 224 | rotation { 225 | x: 0.0 226 | y: 0.0 227 | z: 0.0 228 | w: 1.0 229 | } 230 | scale3 { 231 | x: 1.0 232 | y: 1.0 233 | z: 1.0 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /example/example.script: -------------------------------------------------------------------------------- 1 | function init(self) 2 | msg.post("@render:", "clear_color", { color = vmath.vector4(0.2, 0.4, 1.0, 1.0) }) 3 | for i=1,1000 do 4 | local p = vmath.vector3(math.random(40,620), math.random(40, 1090), 0) 5 | local s = vmath.vector3(0.4) 6 | local id = factory.create("#factory", p, nil, nil, s) 7 | 8 | local f = vmath.vector3(math.random(-10000, 10000), math.random(-10000, 10000), 0) 9 | msg.post(id, "apply_force", {force = f, position = go.get_world_position(id) }) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /example/thing.go: -------------------------------------------------------------------------------- 1 | embedded_components { 2 | id: "sprite" 3 | type: "sprite" 4 | data: "tile_set: \"/example/assets/example.atlas\"\n" 5 | "default_animation: \"icon_72\"\n" 6 | "material: \"/builtins/materials/sprite.material\"\n" 7 | "blend_mode: BLEND_MODE_ALPHA\n" 8 | "" 9 | position { 10 | x: 0.0 11 | y: 0.0 12 | z: 0.0 13 | } 14 | rotation { 15 | x: 0.0 16 | y: 0.0 17 | z: 0.0 18 | w: 1.0 19 | } 20 | } 21 | embedded_components { 22 | id: "collisionobject" 23 | type: "collisionobject" 24 | data: "collision_shape: \"\"\n" 25 | "type: COLLISION_OBJECT_TYPE_DYNAMIC\n" 26 | "mass: 1.0\n" 27 | "friction: 0.0\n" 28 | "restitution: 1.0\n" 29 | "group: \"thing\"\n" 30 | "mask: \"thing\"\n" 31 | "mask: \"wall\"\n" 32 | "embedded_collision_shape {\n" 33 | " shapes {\n" 34 | " shape_type: TYPE_BOX\n" 35 | " position {\n" 36 | " x: 0.0\n" 37 | " y: 0.0\n" 38 | " z: 0.0\n" 39 | " }\n" 40 | " rotation {\n" 41 | " x: 0.0\n" 42 | " y: 0.0\n" 43 | " z: 0.38268343\n" 44 | " w: 0.9238795\n" 45 | " }\n" 46 | " index: 0\n" 47 | " count: 3\n" 48 | " }\n" 49 | " data: 25.0\n" 50 | " data: 25.0\n" 51 | " data: 10.0\n" 52 | "}\n" 53 | "linear_damping: 0.0\n" 54 | "angular_damping: 0.0\n" 55 | "locked_rotation: false\n" 56 | "" 57 | position { 58 | x: 0.0 59 | y: 0.0 60 | z: 0.0 61 | } 62 | rotation { 63 | x: 0.0 64 | y: 0.0 65 | z: 0.0 66 | w: 1.0 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [project] 2 | title = Metrics 3 | version = 0.2 4 | 5 | [script] 6 | shared_state = 1 7 | 8 | [library] 9 | include_dirs = metrics 10 | 11 | [display] 12 | width = 640 13 | height = 1136 14 | 15 | [bootstrap] 16 | main_collection = /example/example.collectionc 17 | 18 | [physics] 19 | gravity_y = 0.0 20 | max_collisions = 2000 21 | max_contacts = 2000 22 | 23 | [ios] 24 | bundle_identifier = com.britzl.defold.metrics 25 | 26 | [android] 27 | package = com.britzl.defold.metrics 28 | 29 | [osx] 30 | bundle_identifier = com.britzl.defold.metrics 31 | 32 | [sprite] 33 | max_count = 1100 34 | 35 | -------------------------------------------------------------------------------- /input/game.input_binding: -------------------------------------------------------------------------------- 1 | mouse_trigger { 2 | input: MOUSE_BUTTON_1 3 | action: "touch" 4 | } 5 | -------------------------------------------------------------------------------- /metrics/fps.go: -------------------------------------------------------------------------------- 1 | components { 2 | id: "fps" 3 | component: "/metrics/fps.script" 4 | position { 5 | x: 0.0 6 | y: 0.0 7 | z: 0.0 8 | } 9 | rotation { 10 | x: 0.0 11 | y: 0.0 12 | z: 0.0 13 | w: 1.0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /metrics/fps.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.POSITION = vmath.vector3(10, 20, 0) 4 | M.FORMAT = "FPS %.2f" 5 | M.COLOR = vmath.vector4(0, 0, 1, 1) 6 | 7 | function M.create(samples, format, position, color) 8 | samples = samples or 60 9 | format = format or M.FORMAT 10 | position = position or M.POSITION 11 | color = color or M.COLOR 12 | 13 | local message = { text = "", position = position, color = color } 14 | 15 | local instance = {} 16 | 17 | local frames = {} 18 | 19 | local fps = 0 20 | 21 | function instance.update() 22 | table.insert(frames, socket.gettime()) 23 | if #frames == samples + 1 then 24 | table.remove(frames, 1) 25 | fps = 1 / ((frames[#frames] - frames[1]) / (#frames - 1)) 26 | end 27 | end 28 | 29 | function instance.fps() 30 | return fps 31 | end 32 | 33 | function instance.draw() 34 | message.text = format:format(fps) 35 | msg.post("@render:", "draw_debug_text", message) 36 | end 37 | 38 | return instance 39 | end 40 | 41 | 42 | local singleton = M.create() 43 | 44 | 45 | function M.update() 46 | singleton.update() 47 | end 48 | 49 | 50 | function M.fps() 51 | return singleton.fps() 52 | end 53 | 54 | 55 | function M.draw() 56 | singleton.draw() 57 | end 58 | 59 | 60 | 61 | return M -------------------------------------------------------------------------------- /metrics/fps.script: -------------------------------------------------------------------------------- 1 | go.property("show", true) 2 | go.property("color", vmath.vector4(0, 0, 1, 1)) 3 | go.property("samples", 60) 4 | 5 | local fps = require "metrics.fps" 6 | 7 | function init(self) 8 | self.instance = fps.create(self.samples, nil, go.get_world_position(), self.color) 9 | end 10 | 11 | function update(self, dt) 12 | self.instance.update() 13 | if self.show then 14 | self.instance.draw() 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /metrics/mem.go: -------------------------------------------------------------------------------- 1 | components { 2 | id: "mem" 3 | component: "/metrics/mem.script" 4 | position { 5 | x: 0.0 6 | y: 0.0 7 | z: 0.0 8 | } 9 | rotation { 10 | x: 0.0 11 | y: 0.0 12 | z: 0.0 13 | w: 1.0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /metrics/mem.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.POSITION = vmath.vector3(10, 20, 0) 4 | M.FORMAT = "MEM %dkb" 5 | M.COLOR = vmath.vector4(0, 0, 1, 1) 6 | 7 | function M.create(format, position, color) 8 | format = format or M.FORMAT 9 | position = position or M.POSITION 10 | color = color or M.COLOR 11 | 12 | local instance = {} 13 | 14 | local mem = collectgarbage("count") 15 | 16 | local message = { text = format:format(mem), position = position, color = color } 17 | 18 | function instance.update() 19 | mem = collectgarbage("count") 20 | end 21 | 22 | function instance.mem() 23 | return mem 24 | end 25 | 26 | function instance.draw() 27 | message.text = format:format(mem) 28 | msg.post("@render:", "draw_debug_text", message) 29 | end 30 | 31 | return instance 32 | end 33 | 34 | local singleton = M.create() 35 | 36 | function M.update() 37 | singleton.update() 38 | end 39 | 40 | function M.mem() 41 | return singleton.mem() 42 | end 43 | 44 | function M.draw() 45 | singleton.draw() 46 | end 47 | 48 | return M -------------------------------------------------------------------------------- /metrics/mem.script: -------------------------------------------------------------------------------- 1 | go.property("show", true) 2 | go.property("color", vmath.vector4(0, 0, 1, 1)) 3 | 4 | local mem = require "metrics.mem" 5 | 6 | function init(self) 7 | self.instance = mem.create(nil, go.get_world_position(), self.color) 8 | end 9 | 10 | function update(self, dt) 11 | self.instance.update() 12 | if self.show then 13 | self.instance.draw() 14 | end 15 | end 16 | --------------------------------------------------------------------------------