├── README.md ├── middleclass.lua ├── Profiler ├── classy-classes-result.txt ├── classy-methods-result.txt ├── classy-alloc-result.txt ├── middleclass-classes-result.txt ├── middleclass-methods-result.txt ├── classy-inheritance-methods-result.txt ├── middleclass-alloc-result.txt ├── middleclass-inheritance-methods-result.txt ├── classy-inheritance-alloc-result.txt └── middleclass-inheritance-alloc-result.txt ├── LICENSE ├── profile.sh ├── Profiler.lua ├── profile.lua ├── footable.lua ├── classy.lua └── ProFi.lua /README.md: -------------------------------------------------------------------------------- 1 | # classy 2 | Yet another class implementation for Lua. 3 | -------------------------------------------------------------------------------- /middleclass.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForgeMistress/classy/HEAD/middleclass.lua -------------------------------------------------------------------------------- /Profiler/classy-classes-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: classes on library classy (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 0.0000000000 Seconds 5 | Starting Memory : 79.9404296875 Kb (0.0780668259 Mb) 6 | Stopping Memory : 89.3613281250 Kb (0.0872669220 Mb) 7 | Change in memory usage : 9.4208984375 Kb (0.0092000961 Mb) -------------------------------------------------------------------------------- /Profiler/classy-methods-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: methods on library classy (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 0.0050000000 Seconds 5 | Starting Memory : 89.4863281250 Kb (0.0873889923 Mb) 6 | Stopping Memory : 91.0292968750 Kb (0.0888957977 Mb) 7 | Change in memory usage : 1.5429687500 Kb (0.0015068054 Mb) -------------------------------------------------------------------------------- /Profiler/classy-alloc-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: alloc on library classy (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 0.6770000000 Seconds 5 | Starting Memory : 65626.6816406250 Kb (64.0885562897 Mb) 6 | Stopping Memory : 690605.7734375000 Kb (674.4197006226 Mb) 7 | Change in memory usage : 624979.0917968750 Kb (610.3311443329 Mb) -------------------------------------------------------------------------------- /Profiler/middleclass-classes-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: classes on library middleclass (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 0.0000000000 Seconds 5 | Starting Memory : 50.0869140625 Kb (0.0489130020 Mb) 6 | Stopping Memory : 53.5634765625 Kb (0.0523080826 Mb) 7 | Change in memory usage : 3.4765625000 Kb (0.0033950806 Mb) -------------------------------------------------------------------------------- /Profiler/middleclass-methods-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: methods on library middleclass (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 0.0040000000 Seconds 5 | Starting Memory : 53.6884765625 Kb (0.0524301529 Mb) 6 | Stopping Memory : 55.2314453125 Kb (0.0539369583 Mb) 7 | Change in memory usage : 1.5429687500 Kb (0.0015068054 Mb) -------------------------------------------------------------------------------- /Profiler/classy-inheritance-methods-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: inheritance-methods on library classy (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 0.0050000000 Seconds 5 | Starting Memory : 89.7207031250 Kb (0.0876178741 Mb) 6 | Stopping Memory : 91.9121093750 Kb (0.0897579193 Mb) 7 | Change in memory usage : 2.1914062500 Kb (0.0021400452 Mb) -------------------------------------------------------------------------------- /Profiler/middleclass-alloc-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: alloc on library middleclass (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 1.4980000000 Seconds 5 | Starting Memory : 65590.8212890625 Kb (64.0535364151 Mb) 6 | Stopping Memory : 690588.3212890625 Kb (674.4026575089 Mb) 7 | Change in memory usage : 624997.5000000000 Kb (610.3491210938 Mb) -------------------------------------------------------------------------------- /Profiler/middleclass-inheritance-methods-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: inheritance-methods on library middleclass (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 0.0050000000 Seconds 5 | Starting Memory : 54.3271484375 Kb (0.0530538559 Mb) 6 | Stopping Memory : 56.7646484375 Kb (0.0554342270 Mb) 7 | Change in memory usage : 2.4375000000 Kb (0.0023803711 Mb) -------------------------------------------------------------------------------- /Profiler/classy-inheritance-alloc-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: inheritance-alloc on library classy (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 1.1560000000 Seconds 5 | Starting Memory : 65626.7285156250 Kb (64.0886020660 Mb) 6 | Stopping Memory : 1315608.7900390625 Kb (1284.7742090225 Mb) 7 | Change in memory usage : 1249982.0615234375 Kb (1220.6856069565 Mb) -------------------------------------------------------------------------------- /Profiler/middleclass-inheritance-alloc-result.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | == Profiler: inheritance-alloc on library middleclass (5000000 iterations) 3 | ================================================================================ 4 | Elapsed time : 3.0300000000 Seconds 5 | Starting Memory : 65591.3505859375 Kb (64.0540533066 Mb) 6 | Stopping Memory : 1549968.8642578125 Kb (1513.6414690018 Mb) 7 | Change in memory usage : 1484377.5136718750 Kb (1449.5874156952 Mb) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 WildPyromancer 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 | -------------------------------------------------------------------------------- /profile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PLATFORM='unknown' 4 | unamestr=`uname` 5 | if [[ "$unamestr" == 'Linux' ]]; then 6 | PLATFORM='linux' 7 | elif [[ "$unamestr" == 'FreeBSD' ]]; then 8 | PLATFORM='freebsd' 9 | elif [[ "$unamestr" == 'Darwin' ]]; then 10 | PLATFORM='osx' 11 | elif [[ "$unamestr" == *"MINGW"* ]]; then 12 | PLATFORM='windows' 13 | fi 14 | 15 | arch=x86 16 | if [[ $HOSTTYPE == x86_64 ]]; then 17 | arch=x64 18 | fi 19 | 20 | # Set this to whatever you use to call a lua runtime on the command line. I've been using a locally built luajit 21 | # runtime for mine. I've also been using Love2D compiled against Luajit 2.1-beta2 as well as Luapower, but that's 22 | # neither here nor there. 23 | lua=luajit 24 | 25 | startdir=$PWD 26 | 27 | echo -- Arch = $arch 28 | echo -- Lua = $lua 29 | echo -- Starting in $startdir 30 | echo 31 | 32 | for file in Profiler/*-result.txt ; do 33 | rm $file 34 | done 35 | 36 | $lua profile.lua classy classes 37 | $lua profile.lua classy alloc 38 | $lua profile.lua classy methods 39 | $lua profile.lua classy inheritance-alloc 40 | $lua profile.lua classy inheritance-methods 41 | $lua profile.lua middleclass classes 42 | $lua profile.lua middleclass alloc 43 | $lua profile.lua middleclass methods 44 | $lua profile.lua middleclass inheritance-alloc 45 | $lua profile.lua middleclass inheritance-methods -------------------------------------------------------------------------------- /Profiler.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------------------------------------------------ 2 | -- 3 | -- Profiler.lua 4 | -- 5 | -- A profiler object. 6 | -- 7 | ------------------------------------------------------------------------------------------------------------------------ 8 | local Profiler = { 9 | Start = function(self) 10 | assert(not self.__m_running) 11 | self.__m_running = true 12 | self.__m_startTime = os.clock() 13 | self.__m_startMemory = collectgarbage("count") 14 | end; 15 | 16 | Stop = function(self) 17 | assert(self.__m_running) 18 | self.__m_running = false 19 | self.__m_stopTime = os.clock() 20 | self.__m_stopMemory = collectgarbage("count") 21 | end; 22 | 23 | GetElapsedTime = function(self) 24 | return self.__m_stopTime - self.__m_startTime 25 | end; 26 | 27 | GetMemoryUsage = function(self) 28 | return self.__m_stopMemory - self.__m_startMemory 29 | end; 30 | 31 | GetFormattedReport = function(self) 32 | local timeElapsedSeconds = self:GetElapsedTime() 33 | local memoryUsageKb = self:GetMemoryUsage() 34 | return 35 | ([[ 36 | ================================================================================ 37 | == Profiler: %s 38 | ================================================================================ 39 | Elapsed time : %.10f Seconds 40 | Starting Memory : %.10f Kb (%.10f Mb) 41 | Stopping Memory : %.10f Kb (%.10f Mb) 42 | Change in memory usage : %.10f Kb (%.10f Mb)]]) 43 | :format( 44 | self.__m_name, 45 | self:GetElapsedTime(), 46 | self.__m_startMemory, self.__m_startMemory/1024, 47 | self.__m_stopMemory, self.__m_stopMemory/1024, 48 | memoryUsageKb, memoryUsageKb/1024) 49 | end; 50 | 51 | __tostring = function(self) 52 | return ("profiler "..self.__m_name) 53 | end; 54 | 55 | __index = 0; 56 | } 57 | Profiler.__index = Profiler 58 | 59 | local __s_profilers = {} 60 | 61 | function Profiler.new(name) 62 | if __s_profilers[name] then 63 | return __s_profilers[name] 64 | end 65 | 66 | local instance = { 67 | __m_name = name; 68 | __m_running = false; 69 | __m_startTime = 0.0; 70 | __m_stopTime = 0.0; 71 | __m_startMemory = 0.0; 72 | __m_stopMemory = 0.0; 73 | } 74 | return setmetatable(instance, Profiler) 75 | end 76 | 77 | function Profiler.delete(name) 78 | __s_profilers[name] = nil 79 | end 80 | 81 | return Profiler -------------------------------------------------------------------------------- /profile.lua: -------------------------------------------------------------------------------- 1 | local classlib = arg[1] 2 | local test = arg[2] 3 | local iterations = 5000000 4 | 5 | local Profiler = require("Profiler").new(test.." on library "..classlib.." ("..iterations.." iterations)") 6 | 7 | local class 8 | if classlib == 'classy' then 9 | class = require("classy") 10 | 11 | elseif classlib == 'middleclass' then 12 | class = require("middleclass") 13 | 14 | end 15 | 16 | local note = classlib..' '..test.." ("..iterations.." iterations)" 17 | local file = 'Profiler/'..classlib..'-'..test..'-result.txt' 18 | 19 | print("Running test: "..note) 20 | 21 | local function WriteReport(profiler, filename) 22 | local f,err = io.open(filename, "w") 23 | assert(f, err) 24 | f:write(profiler:GetFormattedReport()) 25 | f:flush() 26 | f:close() 27 | end 28 | 29 | local BaseClass 30 | local Subclass 31 | local DoubleSubclass 32 | 33 | if classlib == 'classy' then 34 | if test == 'classes' then 35 | Profiler:Start() 36 | end 37 | BaseClass = class("BaseClass", { 38 | Bar = 0; 39 | Baz = 0; 40 | Foo = 0; 41 | }) 42 | function BaseClass:__init__(foo,bar,baz) 43 | self.Foo = foo 44 | self.Bar = bar 45 | self.Baz = baz 46 | end 47 | 48 | function BaseClass:func() 49 | self.Bar = self.Bar + 1 50 | end 51 | 52 | BaseClass = BaseClass:finalize() 53 | 54 | Subclass = BaseClass:subclass("Subclass", { 55 | NewTable = 0; 56 | NewBoolean = false; 57 | }) 58 | function Subclass:__init__(foo, bar, baz, bool) 59 | BaseClass.__init__(self, foo, bar, baz) 60 | self.NewTable = {"Test Table"} 61 | self.NewBoolean = bool 62 | end 63 | Subclass = Subclass:finalize() 64 | 65 | DoubleSubclass = Subclass:subclass("DoubleSubclass", { 66 | AnotherNewTable = 0; 67 | }) 68 | function DoubleSubclass:__init__(bar) 69 | Subclass.__init__(self, 2, bar, "String value!", false) 70 | self.AnotherNewTable = {} 71 | end 72 | DoubleSubclass = DoubleSubclass:finalize() 73 | 74 | if test == 'classes' then 75 | Profiler:Stop() 76 | WriteReport(Profiler, file) 77 | end 78 | elseif classlib == 'middleclass' then 79 | if test == 'classes' then 80 | Profiler:Start() 81 | end 82 | BaseClass = class("BaseClass") 83 | function BaseClass:initialize(foo,bar,baz) 84 | self.Foo = foo 85 | self.Bar = bar 86 | self.Baz = baz 87 | end 88 | 89 | function BaseClass:func() 90 | self.Bar = self.Bar + 1 91 | end 92 | 93 | Subclass = BaseClass:subclass("Subclass") 94 | function Subclass:initialize(foo, bar, baz, bool) 95 | BaseClass.initialize(self, foo, bar, baz) 96 | self.NewTable = {"Test Table"} 97 | self.NewBoolean = bool 98 | end 99 | 100 | DoubleSubclass = Subclass:subclass("DoubleSubclass") 101 | function DoubleSubclass:initialize(bar) 102 | Subclass.initialize(self, 2, bar, "String value!", false) 103 | self.AnotherNewTable = {} 104 | end 105 | if test == 'classes' then 106 | Profiler:Stop() 107 | WriteReport(Profiler, file) 108 | end 109 | end 110 | 111 | if test == 'alloc' then 112 | local t = {} 113 | for i=1, iterations do 114 | t[i] = true 115 | end 116 | 117 | Profiler:Start() 118 | for i=1, iterations do 119 | t[i] = BaseClass:new(2, 3, "String value!") 120 | end 121 | Profiler:Stop() 122 | WriteReport(Profiler, file) 123 | 124 | elseif test == 'methods' then 125 | local bar = 3 126 | local instance = BaseClass:new(2, bar, "String value!") 127 | 128 | Profiler:Start() 129 | for i=1, iterations do 130 | instance:func() 131 | end 132 | Profiler:Stop() 133 | WriteReport(Profiler, file) 134 | 135 | assert(instance.Bar == bar + iterations, "Result expected from function calls was wrong. Expected: "..bar + iterations.." Got: "..instance.Bar) 136 | 137 | elseif test == 'inheritance-alloc' then 138 | local t = {} 139 | for i=1, iterations do 140 | t[i] = true 141 | end 142 | 143 | local i 144 | 145 | Profiler:Start() 146 | for i=1, iterations do 147 | t[i] = DoubleSubclass:new(3) 148 | end 149 | Profiler:Stop() 150 | WriteReport(Profiler, file) 151 | 152 | elseif test == 'inheritance-methods' then 153 | local bar = 3 154 | local instance = DoubleSubclass:new(bar) 155 | 156 | Profiler:Start() 157 | for i=1, iterations do 158 | instance:func() 159 | end 160 | Profiler:Stop() 161 | WriteReport(Profiler, file) 162 | assert(instance.Bar == bar + iterations, "Result expected from function calls was wrong. Expected: "..bar + iterations.." Got: "..instance.Bar) 163 | end 164 | -------------------------------------------------------------------------------- /footable.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------------------------------------------------ 2 | -- 3 | -- footable.lua 4 | -- 5 | -- Couldn't think of a name for a library that makes tables using luajit's ffi based on definitions. So sue me! 6 | -- 7 | ------------------------------------------------------------------------------------------------------------------------ 8 | local __s_caches = setmetatable({}, {__mode='v'}) 9 | 10 | local footableMT = { 11 | defineffi = function(params) error("ffi is not available in this lua runner.", 2); end, 12 | allocateffi = function(params) error("ffi is not available in this lua runner.", 2); end, 13 | 14 | definelua = 0, 15 | allocatelua = 0, 16 | 17 | get = 0; 18 | isdefined = 0; 19 | } 20 | footableMT.__index = footableMT 21 | 22 | if jit and require("ffi") then 23 | local ffi = require("ffi") 24 | local FFI_STRING_TYPE = ffi.typeof("const char*") 25 | 26 | footableMT.defineffi = function(self, params) 27 | tblMT = tblMT or {} 28 | local nname = params.name:gsub("%.", "__") 29 | if not self.__definedTables[params.name] and not __s_ffiTypes[nname] then 30 | local tbldef = string.format([[ 31 | typedef struct %s 32 | %s 33 | %s; 34 | ]], nname, params.def, nname) 35 | ffi.cdef(tbldef) 36 | self.__definedTables[name] = ffi.metatype(ffi.typeof(nname), tblMT) 37 | else 38 | return false, "FFI-Based Table "..name.." is already defined (remember, ffi tables can only be globally ".. 39 | "defined)." 40 | end 41 | 42 | return self.__definedTables[params.name] ~= nil 43 | end 44 | 45 | local __footableFFIMT = { 46 | __index = function(self, key) 47 | if ffi.istype(FFI_STRING_TYPE, self.__m_cdata[key]) then 48 | return ffi.string(self.__m_cdata[key]) 49 | end 50 | return self.__m_cdata[key] 51 | end; 52 | } 53 | 54 | footableMT.allocateffi = function(self, name) 55 | assert(self.__definedTables[name], "Table definition '"..name.."' is undefined.") 56 | return setmetatable({ 57 | __m_cdata = self.__definedTables[name](params) 58 | }, footableFFIMT) 59 | end 60 | end 61 | 62 | 63 | --[[ PURE LUA ]]-- 64 | local private = {} 65 | local function __val_to_str ( v ) 66 | local vType = type( v ) 67 | if vType == 'string' then 68 | v = string.gsub( v, "\n", "\\n" ) 69 | if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then 70 | return "'" .. v .. "'" 71 | end 72 | return '"' .. string.gsub(v,'"', '\\"' ) .. '"' 73 | elseif vType == 'table' then 74 | return private.tbl_to_str( v ) 75 | else 76 | return tostring( v ) 77 | end 78 | end 79 | 80 | local function __key_to_str ( k, minify ) 81 | local kType = type( k ) 82 | if kType == 'string' and string.match( k, "^[_%a][_%a%d]*$" ) then 83 | return k 84 | else 85 | return "[" .. __val_to_str( k ) .. "]" 86 | end 87 | end 88 | 89 | local function tbl_to_str( tbl ) 90 | local ind = ind or 0 91 | local result, done = {}, {} 92 | for k, v in ipairs( tbl ) do 93 | table.insert( result, __val_to_str( v ) ) 94 | done[ k ] = true 95 | end 96 | for k, v in pairs( tbl ) do 97 | if not done[ k ] then 98 | table.insert( result, __key_to_str( k ) .. "=" .. __val_to_str( v ) ) 99 | end 100 | end 101 | return "{" .. table.concat( result, "," ) .. "}" 102 | end 103 | private.tbl_to_str = tbl_to_str 104 | 105 | footableMT.definelua = function(self, name, tbl) 106 | if name == nil then 107 | error("Name is nil.", 2) 108 | end 109 | assert(self.__definedTables[name] == nil, "Table definition '"..name.."' already exists.") 110 | local allocatorString = "return "..private.tbl_to_str(tbl) 111 | self.__definedTables[name] = assert(loadstring(allocatorString)) 112 | return true 113 | end 114 | 115 | footableMT.allocatelua = function(self, name) 116 | assert(self.__definedTables[name] ~= nil, "Table definition '"..name.."' is undefined.") 117 | return self.__definedTables[name]() 118 | end 119 | 120 | footableMT.get = function(self, name) 121 | return self.__definedTables[name] or nil 122 | end 123 | 124 | footableMT.isdefined = function(self, name) 125 | return self.__definedTables[name] ~= nil 126 | end 127 | 128 | local function _makeNewFootableCache(name) 129 | if __s_caches[name] then 130 | return __s_caches[name] 131 | end 132 | 133 | local ftCache = setmetatable({ 134 | __definedTables = {} 135 | }, footableMT) 136 | __s_caches[name] = ftCache 137 | return ftCache 138 | end 139 | return _makeNewFootableCache -------------------------------------------------------------------------------- /classy.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------------------------------------------------ 2 | -- 3 | -- classy.lua 4 | -- 5 | -- Class implementation for lua. Not as small as middleclass, but a whole hell of a lot faster. 6 | -- 7 | ------------------------------------------------------------------------------------------------------------------------ 8 | local __ftCache = require(((...):match("(.-)[^%.]+$"))..".footable")("classy") 9 | local __classtag = {} 10 | 11 | -- Merge the right table with the left and return the left. Asserts on duplicate keys. 12 | local function _mergeTables(className, left, right) 13 | if right == nil then return left end 14 | 15 | for k, v in pairs(right) do 16 | assert(left[k] == nil, "Duplicate key detected: Key = "..k.." Class = "..className) 17 | left[k] = v 18 | end 19 | return left 20 | end 21 | -- END UTILITIES -- 22 | 23 | -- Module. Functions are defined towards the bottom of the file. 24 | local classy = { 25 | class = 0; 26 | isfinalized = 0; 27 | isinstanceof = 0; 28 | isclass = 0; 29 | isinstance = 0; 30 | type = 0; 31 | } 32 | local function _makeAllocatorFunction(...) 33 | local arg = {...} 34 | local className = arg[1] 35 | local classTemplate = arg[2] 36 | local super = arg[3] 37 | 38 | local final 39 | if super then 40 | final = _mergeTables(className, classTemplate, __ftCache:allocatelua(super.__name__)) 41 | else 42 | final = classTemplate 43 | end 44 | return __ftCache:definelua(className, final) 45 | end 46 | 47 | local __maxClassID = 0 48 | local function _makeClassID(klass) 49 | local id = __maxClassID + 1 50 | __maxClassID = id 51 | return id 52 | end 53 | 54 | local function _instanceToString(instance) 55 | return ("instance of "..instance.__class__.__name__) 56 | end 57 | 58 | local function _classWrapIndex(klass) 59 | return function(instance, key) 60 | return klass.methods[key] or klass.methods.__index__(instance, key) 61 | end 62 | end 63 | local function _classFinalize(klass) 64 | if klass.__m_instMT then 65 | error("AssertNotFinalized: Class "..klass.__name__.." has already been finalized.", 2) 66 | end 67 | 68 | klass.methods.__class__ = klass 69 | 70 | if klass.mixins[1] then 71 | -- Change the mixin table from a set to an array for faster allocation. 72 | local newMixinTbl = {} 73 | for _, mixin in ipairs(klass.mixins) do 74 | if mixin.__onfinalize__ then 75 | mixin.__onfinalize__(klass) 76 | end 77 | end 78 | end 79 | 80 | if klass.__super__ then 81 | klass.methods.super = klass.__super__ 82 | klass.methods.__super__ = klass.__super__ 83 | setmetatable(klass.methods, { __index=klass.__super__.methods }) 84 | -- This might mess some things up. TODO: Test if this messes things up. 85 | setmetatable(klass.static, { __index=klass.__super__.static }) 86 | end 87 | 88 | -- Metatable support right there in the class definition. You're welcome. ;) 89 | klass.__m_instMT = { 90 | __index = ((klass.methods.__index__ and _classWrapIndex(klass)) or klass.methods); 91 | __tostring = (klass.methods.__tostring__ or _instanceToString); 92 | __eq = (klass.methods.__eq__ or nil); 93 | __lt = (klass.methods.__lt__ or nil); 94 | __gt = (klass.methods.__gt__ or nil); 95 | __call = (klass.methods.__call__ or nil); 96 | } 97 | 98 | return klass 99 | end 100 | 101 | local function _classNew(klass, ...) 102 | if not klass.__m_instMT then 103 | error("AssertFinalized: Class "..klass.__name__.." has not been finalized.", 2) 104 | end 105 | 106 | local instance = __ftCache:allocatelua(klass.__name__) 107 | 108 | instance = setmetatable(instance, klass.__m_instMT) 109 | 110 | if instance.__init__ then 111 | instance.__init__(instance, ...) 112 | end 113 | 114 | if klass.mixins[1] then 115 | for _, mixin in ipairs(klass.mixins) do 116 | if mixin.__init__ then 117 | mixin.__init__(instance) 118 | end 119 | end 120 | end 121 | 122 | return instance 123 | end 124 | 125 | local function _classSubclass(klass, name, subclassTemplate) 126 | if classy.type(klass) ~= 'class' then 127 | error("Subclass must be called as :subclass ("..classy.type(klass)..").") 128 | end 129 | if not klass.__m_instMT then 130 | error("AssertFinalized: Class "..klass.__name__.." has not been finalized.", 2) 131 | end 132 | 133 | local classImpl = classy.class(name, subclassTemplate, klass) 134 | if klass.__super__ then 135 | local superStatic = klass.__super__.static 136 | if superStatic.__onsubclassed__ then 137 | superStatic.__onsubclassed__(classImpl) 138 | end 139 | end 140 | return classImpl 141 | end 142 | 143 | local function _classInclude(klass, mixin) 144 | if not mixin then 145 | error("Mixin added to class "..klass.__name__.." was nil.", 2) 146 | end 147 | if klass.__m_instMT then 148 | error("AssertNotFinalized: Class "..klass.__name__.." has already been finalized.", 2) 149 | end 150 | for _, mix in ipairs(klass.mixins) do 151 | if mix == mixin then 152 | error("Mixin already included in class "..klass.__name__..".", 2) 153 | end 154 | end 155 | table.insert(klass.mixins, mixin) 156 | 157 | klass.methods = _mergeTables(klass.__name__, klass.methods, mixin.methods) 158 | klass.static = _mergeTables(klass.__name__, klass.static, mixin.static) 159 | 160 | if type(mixin.__oninclude__) == 'function' then 161 | mixin.__oninclude__(klass) 162 | end 163 | 164 | return klass 165 | end 166 | 167 | local function _classIsSubclassOf(klass, otherKlass) 168 | if klass.__id__ == otherKlass.__id__ then 169 | return true 170 | end 171 | 172 | if klass.__super__ then 173 | return _classIsSubclassOf(klass.__super__, otherKlass) 174 | end 175 | 176 | return false 177 | end 178 | 179 | local function _classNewIndex(klass, key, value) 180 | assert(not klass.__m_instMT, "AssertNotFinalized: Class "..klass.__name__.." has already been finalized.") 181 | 182 | if type(value) == 'function' then 183 | klass.methods[key] = value 184 | return 185 | end 186 | 187 | assert(not klass.static[key], "Class already has key "..key) 188 | klass.static[key] = value 189 | end 190 | 191 | local function _classEq(left, right) 192 | assert(classy.isclass(left) and classy.isclass(right)) 193 | return left.__id__ == right.__id__ 194 | end 195 | 196 | local function _classToString(klass) 197 | return ("class "..klass.__name__) 198 | end 199 | 200 | local function _checkKeys(name, template) 201 | for k, v in pairs(template) do 202 | assert(type(v) ~= 'function', string.format("Functions are not allowed in class templates. Class: %s, Key: %s", name, k)) 203 | assert(k ~= "__class__", "Can't use the key '__class__' in your definition.") 204 | assert(k ~= "__super__", "Can't use the key '__super__' in your definition.") 205 | end 206 | end 207 | 208 | -- Process for making a class: 209 | -- Call class() or Class:subclass() while passing it in a template table. 210 | function classy.class(name, classTemplate, superclass) 211 | classTemplate = classTemplate or {} 212 | _checkKeys(name, classTemplate) 213 | if _makeAllocatorFunction(name, classTemplate, superclass) then 214 | local classtbl = { 215 | __classtag__ = __classtag; 216 | __name__ = name; 217 | __super__ = superclass; 218 | __id__ = _makeClassID(); 219 | methods = {}; 220 | static = { 221 | IsSubclassOf = _classIsSubclassOf; 222 | fulfill = _classFulfill; 223 | }; 224 | 225 | mixins = {}; 226 | 227 | new = _classNew; 228 | subclass = _classSubclass; 229 | finalize = _classFinalize; 230 | include = _classInclude; 231 | 232 | __m_instMT = false; 233 | } 234 | 235 | setmetatable(classtbl.static, { 236 | __index = classtbl.methods 237 | }); 238 | 239 | classtbl = setmetatable(classtbl, { 240 | __index = classtbl.static; 241 | __tostring = _classToString; 242 | __newindex = _classNewIndex; 243 | __eq = _classEq; 244 | }) 245 | 246 | return classtbl 247 | end 248 | return nil, "Failed to define footable." 249 | end 250 | 251 | function classy.isfinalized(klass) 252 | return type(klass.__m_instMT) ~= 'boolean' 253 | end 254 | 255 | function classy.isclass(klass) 256 | if type(klass) ~= 'table' then return false end 257 | return klass.__classtag__ == __classtag 258 | end 259 | 260 | function classy.isinstance(input) 261 | if type(input) ~= 'table' then return false end 262 | return input.__class__ and input.__class__.__classtag__ == __classtag 263 | end 264 | 265 | function classy.isinstanceof(instance, klass) 266 | assert(classy.type(instance) == 'instance') 267 | return _classIsSubclassOf(instance.__class__, klass) 268 | end 269 | 270 | ---------------------------------------------------------------------------------------------------------------- 271 | -- Extension of the native lua type() function. Returns 'instance' if the type is an instance of any class, 272 | -- or 'class' if the item is a class definition. Otherwise it will return the result of type(). 273 | -- Returns one of the following: 274 | -- 'instance' - An instance of a classy class. 275 | -- 'class' - A classy class definition. 276 | -- 'callable' - A table with the __call metamethod defined. 277 | -- type(item) - If none of the above are true, then it will return the result of lua's type() function. 278 | function classy.type(item) 279 | if classy.isclass(item) then return 'class' end 280 | if classy.isinstance(item) then return 'instance' end 281 | 282 | local it = type(item) 283 | if it == 'table' and getmetatable(it).__call then return 'callable' end 284 | return it 285 | end 286 | 287 | return setmetatable(classy, { 288 | __call = function(_, ...) 289 | return classy.class(...) 290 | end 291 | }) 292 | -------------------------------------------------------------------------------- /ProFi.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | ProFi v1.3, by Luke Perkin 2012. MIT Licence http://www.opensource.org/licenses/mit-license.php. 3 | 4 | Example: 5 | ProFi = require 'ProFi' 6 | ProFi:start() 7 | some_function() 8 | another_function() 9 | coroutine.resume( some_coroutine ) 10 | ProFi:stop() 11 | ProFi:writeReport( 'MyProfilingReport.txt' ) 12 | 13 | API: 14 | *Arguments are specified as: type/name/default. 15 | ProFi:start( string/once/nil ) 16 | ProFi:stop() 17 | ProFi:checkMemory( number/interval/0, string/note/'' ) 18 | ProFi:writeReport( string/filename/'ProFi.txt' ) 19 | ProFi:reset() 20 | ProFi:setHookCount( number/hookCount/0 ) 21 | ProFi:setGetTimeMethod( function/getTimeMethod/os.clock ) 22 | ProFi:setInspect( string/methodName, number/levels/1 ) 23 | ]] 24 | 25 | ----------------------- 26 | -- Locals: 27 | ----------------------- 28 | 29 | local ProFi = {} 30 | local onDebugHook, sortByDurationDesc, sortByCallCount, getTime 31 | local DEFAULT_DEBUG_HOOK_COUNT = 0 32 | local FORMAT_HEADER_LINE = "| %-50s: %-40s: %-20s: %-12s: %-12s: %-12s|\n" 33 | local FORMAT_OUTPUT_LINE = "| %s: %-12s: %-12s: %-12s|\n" 34 | local FORMAT_INSPECTION_LINE = "> %s: %-12s\n" 35 | local FORMAT_TOTALTIME_LINE = "| TOTAL TIME = %f\n" 36 | local FORMAT_MEMORY_LINE = "| %-20s: %-16s: %-16s| %s\n" 37 | local FORMAT_HIGH_MEMORY_LINE = "H %-20s: %-16s: %-16sH %s\n" 38 | local FORMAT_LOW_MEMORY_LINE = "L %-20s: %-16s: %-16sL %s\n" 39 | local FORMAT_TITLE = "%-50.50s: %-40.40s: %-20s" 40 | local FORMAT_LINENUM = "%4i" 41 | local FORMAT_TIME = "%04.3f" 42 | local FORMAT_RELATIVE = "%03.2f%%" 43 | local FORMAT_COUNT = "%7i" 44 | local FORMAT_KBYTES = "%7i Kbytes" 45 | local FORMAT_MBYTES = "%7.1f Mbytes" 46 | local FORMAT_MEMORY_HEADER1 = "\n=== HIGH & LOW MEMORY USAGE ===============================\n" 47 | local FORMAT_MEMORY_HEADER2 = "=== MEMORY USAGE ==========================================\n" 48 | local FORMAT_BANNER = [[ 49 | ############################################################################################################### 50 | ##### ProFi, a lua profiler. This profile was generated on: %s 51 | ##### ProFi is created by Luke Perkin 2012 under the MIT Licence, www.locofilm.co.uk 52 | ##### Version 1.3. Get the most recent version at this gist: https://gist.github.com/2838755 53 | ############################################################################################################### 54 | 55 | ]] 56 | 57 | ----------------------- 58 | -- Public Methods: 59 | ----------------------- 60 | 61 | --[[ 62 | Starts profiling any method that is called between this and ProFi:stop(). 63 | Pass the parameter 'once' to so that this methodis only run once. 64 | Example: 65 | ProFi:start( 'once' ) 66 | ]] 67 | function ProFi:start( param ) 68 | if param == 'once' then 69 | if self:shouldReturn() then 70 | return 71 | else 72 | self.should_run_once = true 73 | end 74 | end 75 | self.has_started = true 76 | self.has_finished = false 77 | self:resetReports( self.reports ) 78 | self:startHooks() 79 | self.startTime = getTime() 80 | end 81 | 82 | --[[ 83 | Stops profiling. 84 | ]] 85 | function ProFi:stop() 86 | if self:shouldReturn() then 87 | return 88 | end 89 | self.stopTime = getTime() 90 | self:stopHooks() 91 | self.has_finished = true 92 | end 93 | 94 | function ProFi:checkMemory( interval, note ) 95 | local time = getTime() 96 | local interval = interval or 0 97 | if self.lastCheckMemoryTime and time < self.lastCheckMemoryTime + interval then 98 | return 99 | end 100 | self.lastCheckMemoryTime = time 101 | local memoryReport = { 102 | ['time'] = time; 103 | ['memory'] = collectgarbage('count'); 104 | ['note'] = note or ''; 105 | } 106 | table.insert( self.memoryReports, memoryReport ) 107 | self:setHighestMemoryReport( memoryReport ) 108 | self:setLowestMemoryReport( memoryReport ) 109 | end 110 | 111 | --[[ 112 | Writes the profile report to a file. 113 | Param: [filename:string:optional] defaults to 'ProFi.txt' if not specified. 114 | ]] 115 | function ProFi:writeReport( filename ) 116 | if #self.reports > 0 or #self.memoryReports > 0 then 117 | filename = filename or 'ProFi.txt' 118 | self:sortReportsWithSortMethod( self.reports, self.sortMethod ) 119 | self:writeReportsToFilename( filename ) 120 | print( string.format("[ProFi]\t Report written to %s", filename) ) 121 | end 122 | end 123 | 124 | --[[ 125 | Resets any profile information stored. 126 | ]] 127 | function ProFi:reset() 128 | self.reports = {} 129 | self.reportsByTitle = {} 130 | self.memoryReports = {} 131 | self.highestMemoryReport = nil 132 | self.lowestMemoryReport = nil 133 | self.has_started = false 134 | self.has_finished = false 135 | self.should_run_once = false 136 | self.lastCheckMemoryTime = nil 137 | self.hookCount = self.hookCount or DEFAULT_DEBUG_HOOK_COUNT 138 | self.sortMethod = self.sortMethod or sortByDurationDesc 139 | self.inspect = nil 140 | end 141 | 142 | --[[ 143 | Set how often a hook is called. 144 | See http://pgl.yoyo.org/luai/i/debug.sethook for information. 145 | Param: [hookCount:number] if 0 ProFi counts every time a function is called. 146 | if 2 ProFi counts every other 2 function calls. 147 | ]] 148 | function ProFi:setHookCount( hookCount ) 149 | self.hookCount = hookCount 150 | end 151 | 152 | --[[ 153 | Set how the report is sorted when written to file. 154 | Param: [sortType:string] either 'duration' or 'count'. 155 | 'duration' sorts by the time a method took to run. 156 | 'count' sorts by the number of times a method was called. 157 | ]] 158 | function ProFi:setSortMethod( sortType ) 159 | if sortType == 'duration' then 160 | self.sortMethod = sortByDurationDesc 161 | elseif sortType == 'count' then 162 | self.sortMethod = sortByCallCount 163 | end 164 | end 165 | 166 | --[[ 167 | By default the getTime method is os.clock (CPU time), 168 | If you wish to use other time methods pass it to this function. 169 | Param: [getTimeMethod:function] 170 | ]] 171 | function ProFi:setGetTimeMethod( getTimeMethod ) 172 | getTime = getTimeMethod 173 | end 174 | 175 | --[[ 176 | Allows you to inspect a specific method. 177 | Will write to the report a list of methods that 178 | call this method you're inspecting, you can optionally 179 | provide a levels parameter to traceback a number of levels. 180 | Params: [methodName:string] the name of the method you wish to inspect. 181 | [levels:number:optional] the amount of levels you wish to traceback, defaults to 1. 182 | ]] 183 | function ProFi:setInspect( methodName, levels ) 184 | if self.inspect then 185 | self.inspect.methodName = methodName 186 | self.inspect.levels = levels or 1 187 | else 188 | self.inspect = { 189 | ['methodName'] = methodName; 190 | ['levels'] = levels or 1; 191 | } 192 | end 193 | end 194 | 195 | ----------------------- 196 | -- Implementations methods: 197 | ----------------------- 198 | 199 | function ProFi:shouldReturn( ) 200 | return self.should_run_once and self.has_finished 201 | end 202 | 203 | function ProFi:getFuncReport( funcInfo ) 204 | local title = self:getTitleFromFuncInfo( funcInfo ) 205 | local funcReport = self.reportsByTitle[ title ] 206 | if not funcReport then 207 | funcReport = self:createFuncReport( funcInfo ) 208 | self.reportsByTitle[ title ] = funcReport 209 | table.insert( self.reports, funcReport ) 210 | end 211 | return funcReport 212 | end 213 | 214 | function ProFi:getTitleFromFuncInfo( funcInfo ) 215 | local name = funcInfo.name or 'anonymous' 216 | local source = funcInfo.short_src or 'C_FUNC' 217 | local linedefined = funcInfo.linedefined or 0 218 | linedefined = string.format( FORMAT_LINENUM, linedefined ) 219 | return string.format(FORMAT_TITLE, source, name, linedefined) 220 | end 221 | 222 | function ProFi:createFuncReport( funcInfo ) 223 | local name = funcInfo.name or 'anonymous' 224 | local source = funcInfo.source or 'C Func' 225 | local linedefined = funcInfo.linedefined or 0 226 | local funcReport = { 227 | ['title'] = self:getTitleFromFuncInfo( funcInfo ); 228 | ['count'] = 0; 229 | ['timer'] = 0; 230 | } 231 | return funcReport 232 | end 233 | 234 | function ProFi:startHooks() 235 | debug.sethook( onDebugHook, 'cr', self.hookCount ) 236 | end 237 | 238 | function ProFi:stopHooks() 239 | debug.sethook() 240 | end 241 | 242 | function ProFi:sortReportsWithSortMethod( reports, sortMethod ) 243 | if reports then 244 | table.sort( reports, sortMethod ) 245 | end 246 | end 247 | 248 | function ProFi:writeReportsToFilename( filename ) 249 | local file, err = io.open( filename, 'w' ) 250 | assert( file, err ) 251 | self:writeBannerToFile( file ) 252 | if #self.reports > 0 then 253 | self:writeProfilingReportsToFile( self.reports, file ) 254 | end 255 | if #self.memoryReports > 0 then 256 | self:writeMemoryReportsToFile( self.memoryReports, file ) 257 | end 258 | file:close() 259 | end 260 | 261 | function ProFi:writeProfilingReportsToFile( reports, file ) 262 | local totalTime = self.stopTime - self.startTime 263 | local totalTimeOutput = string.format(FORMAT_TOTALTIME_LINE, totalTime) 264 | file:write( totalTimeOutput ) 265 | local header = string.format( FORMAT_HEADER_LINE, "FILE", "FUNCTION", "LINE", "TIME", "RELATIVE", "CALLED" ) 266 | file:write( header ) 267 | for i, funcReport in ipairs( reports ) do 268 | local timer = string.format(FORMAT_TIME, funcReport.timer) 269 | local count = string.format(FORMAT_COUNT, funcReport.count) 270 | local relTime = string.format(FORMAT_RELATIVE, (funcReport.timer / totalTime) * 100 ) 271 | local outputLine = string.format(FORMAT_OUTPUT_LINE, funcReport.title, timer, relTime, count ) 272 | file:write( outputLine ) 273 | if funcReport.inspections then 274 | self:writeInpsectionsToFile( funcReport.inspections, file ) 275 | end 276 | end 277 | end 278 | 279 | function ProFi:writeMemoryReportsToFile( reports, file ) 280 | file:write( FORMAT_MEMORY_HEADER1 ) 281 | self:writeHighestMemoryReportToFile( file ) 282 | self:writeLowestMemoryReportToFile( file ) 283 | file:write( FORMAT_MEMORY_HEADER2 ) 284 | for i, memoryReport in ipairs( reports ) do 285 | local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_MEMORY_LINE ) 286 | file:write( outputLine ) 287 | end 288 | end 289 | 290 | function ProFi:writeHighestMemoryReportToFile( file ) 291 | local memoryReport = self.highestMemoryReport 292 | local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_HIGH_MEMORY_LINE ) 293 | file:write( outputLine ) 294 | end 295 | 296 | function ProFi:writeLowestMemoryReportToFile( file ) 297 | local memoryReport = self.lowestMemoryReport 298 | local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_LOW_MEMORY_LINE ) 299 | file:write( outputLine ) 300 | end 301 | 302 | function ProFi:formatMemoryReportWithFormatter( memoryReport, formatter ) 303 | local time = string.format(FORMAT_TIME, memoryReport.time) 304 | local kbytes = string.format(FORMAT_KBYTES, memoryReport.memory) 305 | local mbytes = string.format(FORMAT_MBYTES, memoryReport.memory/1024) 306 | local outputLine = string.format(formatter, time, kbytes, mbytes, memoryReport.note) 307 | return outputLine 308 | end 309 | 310 | function ProFi:writeBannerToFile( file ) 311 | local banner = string.format(FORMAT_BANNER, os.date()) 312 | file:write( banner ) 313 | end 314 | 315 | function ProFi:writeInpsectionsToFile( inspections, file ) 316 | local inspectionsList = self:sortInspectionsIntoList( inspections ) 317 | file:write('\n==^ INSPECT ^======================================================================================================== COUNT ===\n') 318 | for i, inspection in ipairs( inspectionsList ) do 319 | local line = string.format(FORMAT_LINENUM, inspection.line) 320 | local title = string.format(FORMAT_TITLE, inspection.source, inspection.name, line) 321 | local count = string.format(FORMAT_COUNT, inspection.count) 322 | local outputLine = string.format(FORMAT_INSPECTION_LINE, title, count ) 323 | file:write( outputLine ) 324 | end 325 | file:write('===============================================================================================================================\n\n') 326 | end 327 | 328 | function ProFi:sortInspectionsIntoList( inspections ) 329 | local inspectionsList = {} 330 | for k, inspection in pairs(inspections) do 331 | inspectionsList[#inspectionsList+1] = inspection 332 | end 333 | table.sort( inspectionsList, sortByCallCount ) 334 | return inspectionsList 335 | end 336 | 337 | function ProFi:resetReports( reports ) 338 | for i, report in ipairs( reports ) do 339 | report.timer = 0 340 | report.count = 0 341 | report.inspections = nil 342 | end 343 | end 344 | 345 | function ProFi:shouldInspect( funcInfo ) 346 | return self.inspect and self.inspect.methodName == funcInfo.name 347 | end 348 | 349 | function ProFi:getInspectionsFromReport( funcReport ) 350 | local inspections = funcReport.inspections 351 | if not inspections then 352 | inspections = {} 353 | funcReport.inspections = inspections 354 | end 355 | return inspections 356 | end 357 | 358 | function ProFi:getInspectionWithKeyFromInspections( key, inspections ) 359 | local inspection = inspections[key] 360 | if not inspection then 361 | inspection = { 362 | ['count'] = 0; 363 | } 364 | inspections[key] = inspection 365 | end 366 | return inspection 367 | end 368 | 369 | function ProFi:doInspection( inspect, funcReport ) 370 | local inspections = self:getInspectionsFromReport( funcReport ) 371 | local levels = 5 + inspect.levels 372 | local currentLevel = 5 373 | while currentLevel < levels do 374 | local funcInfo = debug.getinfo( currentLevel, 'nS' ) 375 | if funcInfo then 376 | local source = funcInfo.short_src or '[C]' 377 | local name = funcInfo.name or 'anonymous' 378 | local line = funcInfo.linedefined 379 | local key = source..name..line 380 | local inspection = self:getInspectionWithKeyFromInspections( key, inspections ) 381 | inspection.source = source 382 | inspection.name = name 383 | inspection.line = line 384 | inspection.count = inspection.count + 1 385 | currentLevel = currentLevel + 1 386 | else 387 | break 388 | end 389 | end 390 | end 391 | 392 | function ProFi:onFunctionCall( funcInfo ) 393 | local funcReport = ProFi:getFuncReport( funcInfo ) 394 | funcReport.callTime = getTime() 395 | funcReport.count = funcReport.count + 1 396 | if self:shouldInspect( funcInfo ) then 397 | self:doInspection( self.inspect, funcReport ) 398 | end 399 | end 400 | 401 | function ProFi:onFunctionReturn( funcInfo ) 402 | local funcReport = ProFi:getFuncReport( funcInfo ) 403 | if funcReport.callTime then 404 | funcReport.timer = funcReport.timer + (getTime() - funcReport.callTime) 405 | end 406 | end 407 | 408 | function ProFi:setHighestMemoryReport( memoryReport ) 409 | if not self.highestMemoryReport then 410 | self.highestMemoryReport = memoryReport 411 | else 412 | if memoryReport.memory > self.highestMemoryReport.memory then 413 | self.highestMemoryReport = memoryReport 414 | end 415 | end 416 | end 417 | 418 | function ProFi:setLowestMemoryReport( memoryReport ) 419 | if not self.lowestMemoryReport then 420 | self.lowestMemoryReport = memoryReport 421 | else 422 | if memoryReport.memory < self.lowestMemoryReport.memory then 423 | self.lowestMemoryReport = memoryReport 424 | end 425 | end 426 | end 427 | 428 | ----------------------- 429 | -- Local Functions: 430 | ----------------------- 431 | 432 | getTime = os.clock 433 | 434 | onDebugHook = function( hookType ) 435 | local funcInfo = debug.getinfo( 2, 'nS' ) 436 | if hookType == "call" then 437 | ProFi:onFunctionCall( funcInfo ) 438 | elseif hookType == "return" then 439 | ProFi:onFunctionReturn( funcInfo ) 440 | end 441 | end 442 | 443 | sortByDurationDesc = function( a, b ) 444 | return a.timer > b.timer 445 | end 446 | 447 | sortByCallCount = function( a, b ) 448 | return a.count > b.count 449 | end 450 | 451 | ----------------------- 452 | -- Return Module: 453 | ----------------------- 454 | 455 | ProFi:reset() 456 | return ProFi 457 | --------------------------------------------------------------------------------