diff --git a/README.md b/README.md index 01944e4..e2528c1 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,14 @@ hotswap occurs and is passed the name of the file which was swapped. lurker.postswap = function(f) print("File " .. f .. " was swapped") end ``` +### lurker.protected +Dictates whether lurker should run in protected mode; this is `true` by +default. If protected mode is disabled then LÖVE's usual error screen is used +when an error occurs in a LÖVE callback function; if it is enabled then +lurker's error state (which continues watching for file changes and can resume +execution) is used. Changes to this variable should be made before any calls to +lurker.update() are made. + ### lurker.interval The interval in seconds for how often the scan of the directory is performed. This is `.5` by default. diff --git a/lurker.lua b/lurker.lua index 930b6f2..fe08fa5 100644 --- a/lurker.lua +++ b/lurker.lua @@ -17,6 +17,10 @@ local isdir = love.filesystem.isDirectory local time = love.timer.getTime or os.time local lastmodified = love.filesystem.getLastModified +local lovecallbacknames = { "update", "load", "draw", "mousepressed", + "mousereleased", "keypressed", "keyreleased", + "focus", "quit" } + function lurker.init() lurker.print("Initing lurker") @@ -24,8 +28,12 @@ function lurker.init() lurker.preswap = function() end lurker.postswap = function() end lurker.interval = .5 + lurker.protected = true lurker.last = 0 lurker.files = {} + lurker.funcwrappers = {} + lurker.lovefuncs = {} + lurker.state = "init" lume.each(lurker.getchanged(), lurker.resetfile) return lurker end @@ -53,7 +61,85 @@ function lurker.listdir(path, recursive, skipdotfiles) end +function lurker.initwrappers() + for _, v in pairs(lovecallbacknames) do + lurker.funcwrappers[v] = function(...) + args = {...} + xpcall(function() + return lurker.lovefuncs[v] and lurker.lovefuncs[v](unpack(args)) + end, lurker.onerror) + end + lurker.lovefuncs[v] = love[v] + end + lurker.updatewrappers() +end + + +function lurker.updatewrappers() + for _, v in pairs(lovecallbacknames) do + if love[v] ~= lurker.funcwrappers[v] then + lurker.lovefuncs[v] = love[v] + love[v] = lurker.funcwrappers[v] + end + end +end + + +function lurker.onerror(e) + lurker.print("An error occurred; switching to error state") + lurker.state = "error" + for _, v in pairs(lovecallbacknames) do + love[v] = function() end + end + love.update = lurker.update + + local stacktrace = debug.traceback():gsub("\t", "") + local msg = lume.format("{1}\n\n{2}", {e, stacktrace}) + local colors = { 0xFF1E1E2C, 0xFFF0A3A3, 0xFF92B5B0, 0xFF66666A, 0xFFCDCDCD } + love.graphics.reset() + love.graphics.setFont(love.graphics.newFont(12)) + + love.draw = function() + local pad = 25 + local width = love.graphics.getWidth() + local function drawhr(pos, color1, color2) + local animpos = lume.smooth(pad, width - pad - 8, lume.pingpong(time())) + if color1 then love.graphics.setColor(lume.rgba(color1)) end + love.graphics.rectangle("fill", pad, pos, width - pad*2, 1) + if color2 then love.graphics.setColor(lume.rgba(color2)) end + love.graphics.rectangle("fill", animpos, pos, 8, 1) + end + local function drawtext(str, x, y, color) + love.graphics.setColor(lume.rgba(color)) + love.graphics.print(str, x, y) + end + love.graphics.setBackgroundColor(lume.rgba(colors[1])) + love.graphics.clear() + drawtext("An error has occurred", pad, pad, colors[2]) + drawtext("lurker", width - love.graphics.getFont():getWidth("lurker") - + pad, pad, colors[4]) + drawhr(pad + 32, colors[4], colors[5]) + drawtext("If you fix the problem and update the file the program will " .. + "resume", pad, pad + 46, colors[3]) + drawhr(pad + 72, colors[4], colors[5]) + drawtext(msg, pad, pad + 90, colors[5]) + love.graphics.reset() + end +end + + +function lurker.onfirstframe() + if lurker.protected then + lurker.initwrappers() + end +end + + function lurker.update() + if lurker.state == "init" then + lurker.onfirstframe() + lurker.state = "normal" + end local diff = time() - lurker.last if diff > lurker.interval then lurker.last = lurker.last + diff @@ -80,9 +166,20 @@ function lurker.resetfile(f) end +function lurker.exiterrorstate() + lurker.state = "normal" + for _, v in pairs(lovecallbacknames) do + love[v] = lurker.funcwrappers[v] + end +end + + function lurker.scan() for _, f in pairs(lurker.getchanged()) do lurker.print("Hotswapping '{f}'...", {f = f}) + if lurker.state == "error" then + lurker.exiterrorstate() + end lurker.preswap(f) local modname = lurker.modname(f) local t, ok, err = lume.time(lume.hotswap, modname) @@ -93,6 +190,9 @@ function lurker.scan() end lurker.resetfile(f) lurker.postswap(f) + if lurker.protected then + lurker.updatewrappers() + end end end