diff options
author | equa <equaa@protonmail.com> | 2021-04-18 14:45:55 -0500 |
---|---|---|
committer | equa <equaa@protonmail.com> | 2021-04-18 14:48:02 -0500 |
commit | 78530480d35be5dbb57f1a264147bec48d6cf800 (patch) | |
tree | 197350b11e46593db326b563e0917e9536329aa1 | |
parent | 0c8a8cf8d861bc4ef3162e45e8b58d2f0173d2f7 (diff) |
visual changes (zooming) and optimization
also profiling
-rw-r--r-- | lib/cells.fnl | 13 | ||||
-rw-r--r-- | lib/game.fnl | 85 | ||||
-rw-r--r-- | lib/main.fnl | 25 | ||||
-rw-r--r-- | lib/state.fnl | 1 | ||||
-rw-r--r-- | vendor/ProFi.lua | 457 |
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 |