forked from hswaw/hscloud
270 lines
6.4 KiB
Lua
270 lines
6.4 KiB
Lua
--
|
|
-- 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()
|