123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- --
- -- lurker
- --
- -- Copyright (c) 2018 rxi
- --
- -- This library is free software; you can redistribute it and/or modify it
- -- under the terms of the MIT license. See LICENSE for details.
- --
- -- Assumes lume is in the same directory as this file if it does not exist
- -- as a global
- local lume = rawget(_G, "lume") or require((...):gsub("[^/.\\]+$", "lume"))
- local lurker = { _version = "1.0.1" }
- local dir = love.filesystem.enumerate or love.filesystem.getDirectoryItems
- local time = love.timer.getTime or os.time
- local function isdir(path)
- local info = love.filesystem.getInfo(path)
- return info.type == "directory"
- end
- local function lastmodified(path)
- local info = love.filesystem.getInfo(path, "file")
- return info.modtime
- end
- local lovecallbacknames = {
- "update",
- "load",
- "draw",
- "mousepressed",
- "mousereleased",
- "keypressed",
- "keyreleased",
- "focus",
- "quit",
- }
- function lurker.init()
- lurker.print("Initing lurker")
- lurker.path = "."
- lurker.preswap = function() end
- lurker.postswap = function() end
- lurker.interval = .5
- lurker.protected = true
- lurker.quiet = false
- lurker.lastscan = 0
- lurker.lasterrorfile = nil
- lurker.files = {}
- lurker.funcwrappers = {}
- lurker.lovefuncs = {}
- lurker.state = "init"
- lume.each(lurker.getchanged(), lurker.resetfile)
- return lurker
- end
- function lurker.print(...)
- print("[lurker] " .. lume.format(...))
- end
- function lurker.listdir(path, recursive, skipdotfiles)
- path = (path == ".") and "" or path
- local function fullpath(x) return path .. "/" .. x end
- local t = {}
- for _, f in pairs(lume.map(dir(path), fullpath)) do
- if not skipdotfiles or not f:match("/%.[^/]*$") then
- if recursive and isdir(f) then
- t = lume.concat(t, lurker.listdir(f, true, true))
- else
- table.insert(t, lume.trim(f, "/"))
- end
- end
- end
- return t
- end
- function lurker.initwrappers()
- for _, v in pairs(lovecallbacknames) do
- lurker.funcwrappers[v] = function(...)
- local 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, nostacktrace)
- lurker.print("An error occurred; switching to error state")
- lurker.state = "error"
- -- Release mouse
- local setgrab = love.mouse.setGrab or love.mouse.setGrabbed
- setgrab(false)
- -- Set up callbacks
- for _, v in pairs(lovecallbacknames) do
- love[v] = function() end
- end
- love.update = lurker.update
- love.keypressed = function(k)
- if k == "escape" then
- lurker.print("Exiting...")
- love.event.quit()
- end
- end
- local stacktrace = nostacktrace and "" or
- lume.trim((debug.traceback("", 2):gsub("\t", "")))
- local msg = lume.format("{1}\n\n{2}", {e, stacktrace})
- local colors = {
- { lume.color("#1e1e2c", 256) },
- { lume.color("#f0a3a3", 256) },
- { lume.color("#92b5b0", 256) },
- { lume.color("#66666a", 256) },
- { lume.color("#cdcdcd", 256) },
- }
- 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(color1) end
- love.graphics.rectangle("fill", pad, pos, width - pad*2, 1)
- if color2 then love.graphics.setColor(color2) end
- love.graphics.rectangle("fill", animpos, pos, 8, 1)
- end
- local function drawtext(str, x, y, color, limit)
- love.graphics.setColor(color)
- love.graphics[limit and "printf" or "print"](str, x, y, limit)
- end
- love.graphics.setBackgroundColor(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], width - pad * 2)
- love.graphics.reset()
- end
- end
- function lurker.exitinitstate()
- lurker.state = "normal"
- if lurker.protected then
- lurker.initwrappers()
- end
- end
- function lurker.exiterrorstate()
- lurker.state = "normal"
- for _, v in pairs(lovecallbacknames) do
- love[v] = lurker.funcwrappers[v]
- end
- end
- function lurker.update()
- if lurker.state == "init" then
- lurker.exitinitstate()
- end
- local diff = time() - lurker.lastscan
- if diff > lurker.interval then
- lurker.lastscan = lurker.lastscan + diff
- local changed = lurker.scan()
- if #changed > 0 and lurker.lasterrorfile then
- local f = lurker.lasterrorfile
- lurker.lasterrorfile = nil
- lurker.hotswapfile(f)
- end
- end
- end
- function lurker.getchanged()
- local function fn(f)
- return f:match("%.lua$") and lurker.files[f] ~= lastmodified(f)
- end
- return lume.filter(lurker.listdir(lurker.path, true, true), fn)
- end
- function lurker.modname(f)
- return (f:gsub("%.lua$", ""):gsub("[/\\]", "."))
- end
- function lurker.resetfile(f)
- lurker.files[f] = lastmodified(f)
- end
- function lurker.hotswapfile(f)
- lurker.print("Hotswapping '{1}'...", {f})
- if lurker.state == "error" then
- lurker.exiterrorstate()
- end
- if lurker.preswap(f) then
- lurker.print("Hotswap of '{1}' aborted by preswap", {f})
- lurker.resetfile(f)
- return
- end
- local modname = lurker.modname(f)
- local t, ok, err = lume.time(lume.hotswap, modname)
- if ok then
- lurker.print("Swapped '{1}' in {2} secs", {f, t})
- else
- lurker.print("Failed to swap '{1}' : {2}", {f, err})
- if not lurker.quiet and lurker.protected then
- lurker.lasterrorfile = f
- lurker.onerror(err, true)
- lurker.resetfile(f)
- return
- end
- end
- lurker.resetfile(f)
- lurker.postswap(f)
- if lurker.protected then
- lurker.updatewrappers()
- end
- end
- function lurker.scan()
- if lurker.state == "init" then
- lurker.exitinitstate()
- end
- local changed = lurker.getchanged()
- lume.each(changed, lurker.hotswapfile)
- return changed
- end
- return lurker.init()
|