summary refs log tree commit diff
diff options
context:
space:
mode:
authorequa <equaa@protonmail.com>2021-04-18 14:45:55 -0500
committerequa <equaa@protonmail.com>2021-04-18 14:48:02 -0500
commit78530480d35be5dbb57f1a264147bec48d6cf800 (patch)
tree197350b11e46593db326b563e0917e9536329aa1
parent0c8a8cf8d861bc4ef3162e45e8b58d2f0173d2f7 (diff)
visual changes (zooming) and optimization
also profiling
-rw-r--r--lib/cells.fnl13
-rw-r--r--lib/game.fnl85
-rw-r--r--lib/main.fnl25
-rw-r--r--lib/state.fnl1
-rw-r--r--vendor/ProFi.lua457
5 files changed, 552 insertions, 29 deletions
diff --git a/lib/cells.fnl b/lib/cells.fnl
index 442c7c7..dde8372 100644
--- a/lib/cells.fnl
+++ b/lib/cells.fnl
@@ -12,8 +12,8 @@
 (fn neighbors> [f threshold]
   (var x 0)
   ;; nnn this could be faster maybe
-  (each [k v (ipairs neighbors)]
-    (when (> (cell.aliveness (f v)) threshold)
+  (for [k 1 8]
+    (when (> (cell.aliveness (f (. neighbors k))) threshold)
       (set x (+ x 1))))
   x)
 
@@ -28,10 +28,11 @@
          nil))
    cell.update
    (fn [self get]
-     (if (or (= (neighbors> get 0) 3)
-             (= (neighbors> get 0) 2))
+     (let [n (neighbors> get 0)]
+       (if (or (= n 3)
+               (= n 2))
          self
-         nil))
+         nil)))
    cell.aliveness
    #1
    cell.color
@@ -58,8 +59,6 @@
    cell.color
    #(if (= $.stage 0)
         [0.7 0.4 0.3]
-        (= $.stage 5)
-        [0.5 0.4 0.3]
         (= $.stage 1)
         [0.2 0.2 0.3])
    })
diff --git a/lib/game.fnl b/lib/game.fnl
index da96d9f..01f882d 100644
--- a/lib/game.fnl
+++ b/lib/game.fnl
@@ -6,6 +6,10 @@
 (fn lerp* [a b c d x]
   (+ c (* (/ (- x a) (- b a)) (- d c))))
 
+(fn vec-sub [a b]
+  {:x (- a.x b.x)
+   :y (- a.y b.y)})
+
 (fn vec-lerp* [a b c d x]
   {:x (lerp* a.x b.x c.x d.x x.x)
     :y (lerp* a.y b.y c.y d.y x.y)})
@@ -21,6 +25,7 @@
 (fn update [self]
   (set self.ship.x (+ self.ship.x 0.02))
   (set self.ship.y (+ self.ship.y 0.005))
+  (set self.radius (lerp* 0 1 self.radius self.target-radius 0.3))
   (when (= self.tick 0)
     (for [x 0 (- self.width 1)]
       (for [y 0 (- self.height 1)]
@@ -50,29 +55,46 @@
 
 (fn draw [self]
   (local (width height) (love.graphics.getDimensions))
-  (love.graphics.scale width height)
-  (local camera-size (math.max width height))
-  (let [camera-size (math.max width height)
-        camera-box {:x width :y height}
-        camera-box {:x 1 :y 1}
+  ;; (love.graphics.scale width height)
+  (let [camera-size (math.min width height)
         radius-x (* self.radius (/ width camera-size))
         radius-y (* self.radius (/ height camera-size))
-        camera-a {:x (- self.ship.x radius-x)
-                  :y (- self.ship.y radius-y)}
-        camera-b {:x (+ self.ship.x radius-x)
-                  :y (+ self.ship.y radius-y)}
+        clipped-x (math.min radius-x self.max-radius)
+        clipped-y (math.min radius-y self.max-radius)
+        display-a {:x (* width (- 1 (/ clipped-x radius-x)) 0.5)
+                   :y (* height (- 1 (/ clipped-y radius-y)) 0.5)}
+        display-b {:x (- width display-a.x) :y (- height display-a.y)}
+        display-size (vec-sub display-b display-a)
+        camera-a {:x (- self.ship.x clipped-x)
+                  :y (- self.ship.y clipped-y)}
+        camera-b {:x (+ self.ship.x clipped-x)
+                  :y (+ self.ship.y clipped-y)}
         cell-box (vec-lerp* {:x 0 :y 0}
-                            {:x (* 2 radius-x)
-                             :y (* 2 radius-y)}
+                            {:x (* 2 clipped-x)
+                             :y (* 2 clipped-y)}
+                            ;; TODO: this is wrong
                             {:x 0 :y 0}
-                            camera-box
+                            display-size
                             {:x 1 :y 1})]
+    (love.graphics.setScissor (- display-a.x 1) (- display-a.y 1)
+                              (- display-b.x display-a.x -2)
+                              (- display-b.y display-a.y -2))
+    (love.graphics.setColor 0.2 0.2 0.2)
+    (love.graphics.setLineWidth 4)
+    (love.graphics.rectangle :line
+                             display-a.x display-a.y
+                             (- display-b.x display-a.x)
+                             (- display-b.y display-a.y))
+    (love.graphics.setScissor display-a.x display-a.y
+                              (- display-b.x display-a.x)
+                              (- display-b.y display-a.y))
+    (love.graphics.clear)
     (for [x (math.floor camera-a.x) (math.floor camera-b.x)]
       (for [y (math.floor camera-a.y) (math.floor camera-b.y)]
         (let [vec {:x (% x self.width) :y (% y self.height)}
-              render-a (vec-lerp* camera-a camera-b {:x 0 :y 0} camera-box
+              render-a (vec-lerp* camera-a camera-b display-a display-b
                                   {: x : y})
-              render-b (vec-lerp* camera-a camera-b {:x 0 :y 0} camera-box
+              render-b (vec-lerp* camera-a camera-b display-a display-b
                                   {:x (+ x 1) :y (+ y 1)})
               the (. self.grid vec.x vec.y)
               color (and the (cell.color the))]
@@ -82,28 +104,51 @@
                                      (id render-a.x)
                                      (id render-a.y)
                                      (id cell-box.x)
-                                     (id cell-box.y))))))))
+                                     (id cell-box.y))))))
+    ;; draw other stuff
+    ))
     ;; (love.graphics.setLineWidth 0.1)
     ;; (love.graphics.line 0 0 0.3 0.3)
     ;; (love.graphics.polygon :line 0.3 0.3 0.6 0.3 0.4 0.6)
     ;; (love.graphics.line 0.4 0.8 0.4 0.8)
     ;; (love.graphics.print :Gaming))
 
+(fn keypressed [self key scancode repeat]
+  (when (= key "=")
+    (set self.target-radius
+         (math.max
+           self.min-radius
+           (math.min
+             self.max-radius
+             (- self.target-radius 4)))))
+  (when (= key "-")
+    (set self.target-radius
+         (math.max
+           self.min-radius
+           (math.min
+             self.max-radius
+             (+ self.target-radius 4))))))
+
 (fn init [self]
+  (local width 64)
+  (local height 64)
   (setmetatable
-    {:width 64
-     :height 64
+    {:width width
+     :height height
      :ship {:x 31 :y 31}
      :radius 32
+     :target-radius 32
+     :max-radius (/ (math.min height width) 2)
+     :min-radius 16
      :tick 0
      :rate 6
-     :grid (new-grid 64 64 #(if (= (math.random 6) 1)
+     :grid (new-grid width height #(if (= (math.random 6) 1)
                                 (if (< $1 52)
                                     (cell.init cells.life)
                                     (cell.init cells.brain))
                                 nil))
-     :grid-alt (new-grid 64 64 #nil)
+     :grid-alt (new-grid width height #nil)
      }
     self))
 
-{state.draw draw state.init init state.update update}
+{state.draw draw state.init init state.update update state.keypressed keypressed}
diff --git a/lib/main.fnl b/lib/main.fnl
index 4a9d26e..03383ad 100644
--- a/lib/main.fnl
+++ b/lib/main.fnl
@@ -2,6 +2,8 @@
 (local proto (require :lib.proto))
 (local state (require :lib.state))
 (local game (require :lib.game))
+(local profi (require :vendor.ProFi))
+(local profi? false)
 
 ;; i am thinking we could actually do a really hacky thing (modules add themselves
 ;; to this list) with this later but
@@ -16,6 +18,10 @@
 ;; oh thats why it doesnt work lmao
 
 (fn love.load []
+  (when profi?
+    (profi:start))
+  (set love.frame 0)
+  (love.keyboard.setKeyRepeat true)
   (global the-state (state.init game))
   (global messages {})
   (print "a"))
@@ -42,6 +48,13 @@
 
 ;; TODO: we need a better way to display errors at runtime for updates too
 (fn love.update []
+  (when profi?
+    (profi:startHooks)
+    (set love.frame (+ love.frame 1))
+    (when (= (% love.frame 100) 0)
+      (profi:stop)
+      (profi:writeReport)
+      (os.exit)))
   ;; TODO: make state changes actually possible
   (match (pcall #(state.update the-state))
     (true x) nil
@@ -49,10 +62,18 @@
                 (print (.. "update: \n" x))
                 (table.insert messages
                               {:ticks 1
-                               :msg (.. "update: \n" x)}))))
+                               :msg (.. "update: \n" x)})))
+  (when profi?
+    (profi:stopHooks)))
 
 (fn love.keypressed [key scancode repeat]
-  ;; (print key scancode repeat)
+  (match (pcall #(state.keypressed the-state key scancode repeat))
+    (true x) nil
+    (false x) (do
+                (print (.. "keypressed: \n" x))
+                (table.insert messages
+                              {:ticks 5
+                               :msg (.. "keypressed: \n" x)})))
   (when (= key "r")
     (each [k v (lume.ripairs messages)]
       (when (= v.type :reload-error)
diff --git a/lib/state.fnl b/lib/state.fnl
index d98b94e..41dc312 100644
--- a/lib/state.fnl
+++ b/lib/state.fnl
@@ -16,4 +16,5 @@
  ;; all of the next functions are just. regular love functions, exactly the same
  ;; i hope
  :draw (proto.meta-method-opt :state.draw)
+ :keypressed (proto.meta-method-opt :state.keypressed)
  }
diff --git a/vendor/ProFi.lua b/vendor/ProFi.lua
new file mode 100644
index 0000000..989985f
--- /dev/null
+++ b/vendor/ProFi.lua
@@ -0,0 +1,457 @@
+--[[
+ProFi v1.3, by Luke Perkin 2012. MIT Licence http://www.opensource.org/licenses/mit-license.php.
+Updated to v1.4 by Robert Machmer 2017
+
+    Example:
+        ProFi = require 'ProFi'
+        ProFi:start()
+        some_function()
+        another_function()
+        coroutine.resume( some_coroutine )
+        ProFi:stop()
+        ProFi:writeReport( 'MyProfilingReport.txt' )
+
+    API:
+    *Arguments are specified as: type/name/default.
+        ProFi:start( string/once/nil )
+        ProFi:stop()
+        ProFi:checkMemory( number/interval/0, string/note/'' )
+        ProFi:writeReport( string/filename/'ProFi.txt' )
+        ProFi:reset()
+        ProFi:setHookCount( number/hookCount/0 )
+        ProFi:setGetTimeMethod( function/getTimeMethod/os.clock )
+        ProFi:setInspect( string/methodName, number/levels/1 )
+]]
+
+-----------------------
+-- Locals:
+-----------------------
+
+local ProFi = {}
+local onDebugHook, sortByDurationDesc, sortByCallCount, getTime
+local DEFAULT_DEBUG_HOOK_COUNT = 0
+local FORMAT_HEADER_LINE       = "| %-50s: %-40s: %-20s: %-12s: %-12s: %-12s|\n"
+local FORMAT_OUTPUT_LINE       = "| %s: %-12s: %-12s: %-12s|\n"
+local FORMAT_INSPECTION_LINE   = "> %s: %-12s\n"
+local FORMAT_TOTALTIME_LINE    = "| TOTAL TIME = %f\n"
+local FORMAT_MEMORY_LINE       = "| %-20s: %-16s: %-16s| %s\n"
+local FORMAT_HIGH_MEMORY_LINE  = "H %-20s: %-16s: %-16sH %s\n"
+local FORMAT_LOW_MEMORY_LINE   = "L %-20s: %-16s: %-16sL %s\n"
+local FORMAT_TITLE             = "%-50.50s: %-40.40s: %-20s"
+local FORMAT_LINENUM           = "%4i"
+local FORMAT_TIME              = "%04.3f"
+local FORMAT_RELATIVE          = "%03.2f%%"
+local FORMAT_COUNT             = "%7i"
+local FORMAT_KBYTES            = "%7i Kbytes"
+local FORMAT_MBYTES            = "%7.1f Mbytes"
+local FORMAT_MEMORY_HEADER1    = "\n=== HIGH & LOW MEMORY USAGE ===============================\n"
+local FORMAT_MEMORY_HEADER2    = "=== MEMORY USAGE ==========================================\n"
+local FORMAT_BANNER            = [[
+###############################################################################################################
+#####  ProFi, a lua profiler. This profile was generated on: %s
+#####  ProFi is created by Luke Perkin 2012 under the MIT Licence, www.locofilm.co.uk
+#####  Version 1.3 Original gist can be found at https://gist.github.com/perky/2838755
+#####  Version 1.4 Cleaned up code to work with luacheck https://gist.github.com/rm-code/383c98a6af04652ed9f39b7ae536bcc5
+###############################################################################################################
+
+
+]]
+
+-----------------------
+-- Public Methods:
+-----------------------
+
+--[[
+Starts profiling any method that is called between this and ProFi:stop().
+Pass the parameter 'once' to so that this methodis only run once.
+Example:
+ProFi:start( 'once' )
+]]
+function ProFi:start( param )
+    if param == 'once' then
+        if self:shouldReturn() then
+            return
+        else
+            self.should_run_once = true
+        end
+    end
+    self.has_started  = true
+    self.has_finished = false
+    self:resetReports( self.reports )
+    self:startHooks()
+    self.startTime = getTime()
+end
+
+--[[
+Stops profiling.
+]]
+function ProFi:stop()
+    if self:shouldReturn() then
+        return
+    end
+    self.stopTime = getTime()
+    self:stopHooks()
+    self.has_finished = true
+end
+
+function ProFi:checkMemory( ninterval, note )
+    local time = getTime()
+    local interval = ninterval or 0
+    if self.lastCheckMemoryTime and time < self.lastCheckMemoryTime + interval then
+        return
+    end
+    self.lastCheckMemoryTime = time
+    local memoryReport = {
+        ['time']   = time;
+        ['memory'] = collectgarbage('count');
+        ['note']   = note or '';
+    }
+    table.insert( self.memoryReports, memoryReport )
+    self:setHighestMemoryReport( memoryReport )
+    self:setLowestMemoryReport( memoryReport )
+end
+
+--[[
+Writes the profile report to a file.
+Param: [filename:string:optional] defaults to 'ProFi.txt' if not specified.
+]]
+function ProFi:writeReport( filename )
+    if #self.reports > 0 or #self.memoryReports > 0 then
+        filename = filename or 'ProFi.txt'
+        self:sortReportsWithSortMethod( self.reports, self.sortMethod )
+        self:writeReportsToFilename( filename )
+        print( string.format("[ProFi]\t Report written to %s", filename) )
+        return true;
+    end
+end
+
+--[[
+Resets any profile information stored.
+]]
+function ProFi:reset()
+    self.reports = {}
+    self.reportsByTitle = {}
+    self.memoryReports  = {}
+    self.highestMemoryReport = nil
+    self.lowestMemoryReport  = nil
+    self.has_started  = false
+    self.has_finished = false
+    self.should_run_once = false
+    self.lastCheckMemoryTime = nil
+    self.hookCount = self.hookCount or DEFAULT_DEBUG_HOOK_COUNT
+    self.sortMethod = self.sortMethod or sortByDurationDesc
+    self.inspect = nil
+end
+
+--[[
+Set how often a hook is called.
+See http://pgl.yoyo.org/luai/i/debug.sethook for information.
+Param: [hookCount:number] if 0 ProFi counts every time a function is called.
+if 2 ProFi counts every other 2 function calls.
+]]
+function ProFi:setHookCount( hookCount )
+    self.hookCount = hookCount
+end
+
+--[[
+Set how the report is sorted when written to file.
+Param: [sortType:string] either 'duration' or 'count'.
+'duration' sorts by the time a method took to run.
+'count' sorts by the number of times a method was called.
+]]
+function ProFi:setSortMethod( sortType )
+    if sortType == 'duration' then
+        self.sortMethod = sortByDurationDesc
+    elseif sortType == 'count' then
+        self.sortMethod = sortByCallCount
+    end
+end
+
+--[[
+By default the getTime method is os.clock (CPU time),
+If you wish to use other time methods pass it to this function.
+Param: [getTimeMethod:function]
+]]
+function ProFi:setGetTimeMethod( getTimeMethod )
+    getTime = getTimeMethod
+end
+
+--[[
+Allows you to inspect a specific method.
+Will write to the report a list of methods that
+call this method you're inspecting, you can optionally
+provide a levels parameter to traceback a number of levels.
+Params: [methodName:string] the name of the method you wish to inspect.
+[levels:number:optional] the amount of levels you wish to traceback, defaults to 1.
+]]
+function ProFi:setInspect( methodName, levels )
+    if self.inspect then
+        self.inspect.methodName = methodName
+        self.inspect.levels = levels or 1
+    else
+        self.inspect = {
+            ['methodName'] = methodName;
+            ['levels'] = levels or 1;
+        }
+    end
+end
+
+-----------------------
+-- Implementations methods:
+-----------------------
+
+function ProFi:shouldReturn( )
+    return self.should_run_once and self.has_finished
+end
+
+function ProFi:getFuncReport( funcInfo )
+    local title = self:getTitleFromFuncInfo( funcInfo )
+    local funcReport = self.reportsByTitle[ title ]
+    if not funcReport then
+        funcReport = self:createFuncReport( funcInfo )
+        self.reportsByTitle[ title ] = funcReport
+        table.insert( self.reports, funcReport )
+    end
+    return funcReport
+end
+
+function ProFi:getTitleFromFuncInfo( funcInfo )
+    local name        = funcInfo.name or 'anonymous'
+    local source      = funcInfo.short_src or 'C_FUNC'
+    local linedefined = funcInfo.linedefined or 0
+    linedefined = string.format( FORMAT_LINENUM, linedefined )
+    return string.format(FORMAT_TITLE, source, name, linedefined)
+end
+
+function ProFi:createFuncReport( funcInfo )
+    local funcReport = {
+        ['title']         = self:getTitleFromFuncInfo( funcInfo );
+        ['count'] = 0;
+        ['timer']         = 0;
+    }
+    return funcReport
+end
+
+function ProFi:startHooks()
+    debug.sethook( onDebugHook, 'cr', self.hookCount )
+end
+
+function ProFi:stopHooks()
+    debug.sethook()
+end
+
+function ProFi:sortReportsWithSortMethod( reports, sortMethod )
+    if reports then
+        table.sort( reports, sortMethod )
+    end
+end
+
+function ProFi:writeReportsToFilename( filename )
+    local file, err = io.open( filename, 'w' )
+    assert( file, err )
+    self:writeBannerToFile( file )
+    if #self.reports > 0 then
+        self:writeProfilingReportsToFile( self.reports, file )
+    end
+    if #self.memoryReports > 0 then
+        self:writeMemoryReportsToFile( self.memoryReports, file )
+    end
+    file:close()
+end
+
+function ProFi:writeProfilingReportsToFile( reports, file )
+    local totalTime = self.stopTime - self.startTime
+    local totalTimeOutput =  string.format(FORMAT_TOTALTIME_LINE, totalTime)
+    file:write( totalTimeOutput )
+    local header = string.format( FORMAT_HEADER_LINE, "FILE", "FUNCTION", "LINE", "TIME", "RELATIVE", "CALLED" )
+    file:write( header )
+    for _, funcReport in ipairs( reports ) do
+        local timer         = string.format(FORMAT_TIME, funcReport.timer)
+        local count         = string.format(FORMAT_COUNT, funcReport.count)
+        local relTime         = string.format(FORMAT_RELATIVE, (funcReport.timer / totalTime) * 100 )
+        local outputLine    = string.format(FORMAT_OUTPUT_LINE, funcReport.title, timer, relTime, count )
+        file:write( outputLine )
+        if funcReport.inspections then
+            self:writeInpsectionsToFile( funcReport.inspections, file )
+        end
+    end
+end
+
+function ProFi:writeMemoryReportsToFile( reports, file )
+    file:write( FORMAT_MEMORY_HEADER1 )
+    self:writeHighestMemoryReportToFile( file )
+    self:writeLowestMemoryReportToFile( file )
+    file:write( FORMAT_MEMORY_HEADER2 )
+    for _, memoryReport in ipairs( reports ) do
+        local outputLine = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_MEMORY_LINE )
+        file:write( outputLine )
+    end
+end
+
+function ProFi:writeHighestMemoryReportToFile( file )
+    local memoryReport = self.highestMemoryReport
+    local outputLine   = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_HIGH_MEMORY_LINE )
+    file:write( outputLine )
+end
+
+function ProFi:writeLowestMemoryReportToFile( file )
+    local memoryReport = self.lowestMemoryReport
+    local outputLine   = self:formatMemoryReportWithFormatter( memoryReport, FORMAT_LOW_MEMORY_LINE )
+    file:write( outputLine )
+end
+
+function ProFi:formatMemoryReportWithFormatter( memoryReport, formatter )
+    local time       = string.format(FORMAT_TIME, memoryReport.time)
+    local kbytes     = string.format(FORMAT_KBYTES, memoryReport.memory)
+    local mbytes     = string.format(FORMAT_MBYTES, memoryReport.memory/1024)
+    local outputLine = string.format(formatter, time, kbytes, mbytes, memoryReport.note)
+    return outputLine
+end
+
+function ProFi:writeBannerToFile( file )
+    local banner = string.format(FORMAT_BANNER, os.date())
+    file:write( banner )
+end
+
+function ProFi:writeInpsectionsToFile( inspections, file )
+    local inspectionsList = self:sortInspectionsIntoList( inspections )
+    file:write('\n==^ INSPECT ^======================================================================================================== COUNT ===\n')
+    for _, inspection in ipairs( inspectionsList ) do
+        local line             = string.format(FORMAT_LINENUM, inspection.line)
+        local title         = string.format(FORMAT_TITLE, inspection.source, inspection.name, line)
+        local count         = string.format(FORMAT_COUNT, inspection.count)
+        local outputLine    = string.format(FORMAT_INSPECTION_LINE, title, count )
+        file:write( outputLine )
+    end
+    file:write('===============================================================================================================================\n\n')
+end
+
+function ProFi:sortInspectionsIntoList( inspections )
+    local inspectionsList = {}
+    for _, inspection in pairs(inspections) do
+        inspectionsList[#inspectionsList+1] = inspection
+    end
+    table.sort( inspectionsList, sortByCallCount )
+    return inspectionsList
+end
+
+function ProFi:resetReports( reports )
+    for _, report in ipairs( reports ) do
+        report.timer = 0
+        report.count = 0
+        report.inspections = nil
+    end
+end
+
+function ProFi:shouldInspect( funcInfo )
+    return self.inspect and self.inspect.methodName == funcInfo.name
+end
+
+function ProFi:getInspectionsFromReport( funcReport )
+    local inspections = funcReport.inspections
+    if not inspections then
+        inspections = {}
+        funcReport.inspections = inspections
+    end
+    return inspections
+end
+
+function ProFi:getInspectionWithKeyFromInspections( key, inspections )
+    local inspection = inspections[key]
+    if not inspection then
+        inspection = {
+            ['count']  = 0;
+        }
+        inspections[key] = inspection
+    end
+    return inspection
+end
+
+function ProFi:doInspection( inspect, funcReport )
+    local inspections = self:getInspectionsFromReport( funcReport )
+    local levels = 5 + inspect.levels
+    local currentLevel = 5
+    while currentLevel < levels do
+        local funcInfo = debug.getinfo( currentLevel, 'nS' )
+        if funcInfo then
+            local source = funcInfo.short_src or '[C]'
+            local name = funcInfo.name or 'anonymous'
+            local line = funcInfo.linedefined
+            local key = source..name..line
+            local inspection = self:getInspectionWithKeyFromInspections( key, inspections )
+            inspection.source = source
+            inspection.name = name
+            inspection.line = line
+            inspection.count = inspection.count + 1
+            currentLevel = currentLevel + 1
+        else
+            break
+        end
+    end
+end
+
+function ProFi:onFunctionCall( funcInfo )
+    local funcReport = ProFi:getFuncReport( funcInfo )
+    funcReport.callTime = getTime()
+    funcReport.count = funcReport.count + 1
+    if self:shouldInspect( funcInfo ) then
+        self:doInspection( self.inspect, funcReport )
+    end
+end
+
+function ProFi:onFunctionReturn( funcInfo )
+    local funcReport = ProFi:getFuncReport( funcInfo )
+    if funcReport.callTime then
+        funcReport.timer = funcReport.timer + (getTime() - funcReport.callTime)
+    end
+end
+
+function ProFi:setHighestMemoryReport( memoryReport )
+    if not self.highestMemoryReport then
+        self.highestMemoryReport = memoryReport
+    else
+        if memoryReport.memory > self.highestMemoryReport.memory then
+            self.highestMemoryReport = memoryReport
+        end
+    end
+end
+
+function ProFi:setLowestMemoryReport( memoryReport )
+    if not self.lowestMemoryReport then
+        self.lowestMemoryReport = memoryReport
+    else
+        if memoryReport.memory < self.lowestMemoryReport.memory then
+            self.lowestMemoryReport = memoryReport
+        end
+    end
+end
+
+-----------------------
+-- Local Functions:
+-----------------------
+
+getTime = os.clock
+
+onDebugHook = function( hookType )
+    local funcInfo = debug.getinfo( 2, 'nS' )
+    if hookType == "call" then
+        ProFi:onFunctionCall( funcInfo )
+    elseif hookType == "return" then
+        ProFi:onFunctionReturn( funcInfo )
+    end
+end
+
+sortByDurationDesc = function( a, b )
+    return a.timer > b.timer
+end
+
+sortByCallCount = function( a, b )
+    return a.count > b.count
+end
+
+-----------------------
+-- Return Module:
+-----------------------
+
+ProFi:reset()
+return ProFi