Wrap mode handlers in xpcall+error-mode.

When an error occurs in a mode handler, change to error-mode. Within
error-mode, display the traceback and allow the user to exit, (which
the default love error handler does) but also allows returning back to
the previous mode. The assumption is that this would be done after
using the repl to ,reload the module containing the bug which caused
the error.
This commit is contained in:
Phil Hagelberg 2021-09-11 19:33:52 -07:00
parent 0b04501302
commit c257139d17
2 changed files with 45 additions and 8 deletions

30
error-mode.fnl Normal file
View file

@ -0,0 +1,30 @@
;; This mode has two purposes:
;; * display the stack trace that caused the error
;; * allow the user to decide whether to retry after reloading or quit
;; Since we can't know which module needs to be reloaded, we rely on the user
;; doing a ,reload foo in the repl.
(var state nil)
(local explanation "Press escape to quit.
Press space to return to the previous mode after reloading in the repl.")
(fn draw []
(love.graphics.clear 0.34 0.61 0.86)
(love.graphics.setColor 0.9 0.9 0.9)
(love.graphics.print state.msg 10 10)
(love.graphics.print explanation 15 25)
(love.graphics.print state.traceback 15 50))
(fn keypressed [key set-mode]
(match key
:escape (love.event.quit)
:space (set-mode state.old-mode)))
(fn activate [old-mode msg traceback]
(print msg)
(print traceback)
(set state {: old-mode : msg : traceback}))
{: draw : keypressed : activate}

View file

@ -1,37 +1,44 @@
(local repl (require "lib.stdio"))
(local fennel (require :lib.fennel))
(local repl (require :lib.stdio))
(local canvas (let [(w h) (love.window.getMode)]
(love.graphics.newCanvas w h)))
(var scale 1)
;; set the first mode
(var mode (require "mode-intro"))
(var (mode mode-name) nil)
(fn set-mode [mode-name ...]
(set mode (require mode-name))
(fn set-mode [new-mode-name ...]
(set (mode mode-name) (values (require new-mode-name) new-mode-name))
(when mode.activate
(mode.activate ...)))
(match (pcall mode.activate ...)
(false msg) (print mode-name "activate error" msg))))
(fn love.load []
(set-mode :mode-intro)
(canvas:setFilter "nearest" "nearest")
(repl.start))
(fn safely [f]
(xpcall f #(set-mode :error-mode mode-name $ (fennel.traceback))))
(fn love.draw []
;; the canvas allows you to get sharp pixel-art style scaling; if you
;; don't want that, just skip that and call mode.draw directly.
(love.graphics.setCanvas canvas)
(love.graphics.clear)
(love.graphics.setColor 1 1 1)
(mode.draw)
(safely mode.draw)
(love.graphics.setCanvas)
(love.graphics.setColor 1 1 1)
(love.graphics.draw canvas 0 0 0 scale scale))
(fn love.update [dt]
(mode.update dt set-mode))
(when mode.update
(safely #(mode.update dt set-mode))))
(fn love.keypressed [key]
(if (and (love.keyboard.isDown "lctrl" "rctrl" "capslock") (= key "q"))
(love.event.quit)
;; add what each keypress should do in each mode
(mode.keypressed key set-mode)))
(safely #(mode.keypressed key set-mode))))