Reputation: 550
I've written a script to hot-reload already require
ed modules. It work s only partially however...
My approach to this task is quite simple. I changed Lua's require
function so that it remembers modules that it loaded together with a timestamp and its file path. Then I use a shell script to observe the modification time of those files and re-require them if they changed. I simply dofile()
and if no errors happen, I take the return value and (re-)assign it at package.loaded[<module>]
. So far so good.
All of this works perfect when I use global variables, e.g. foo = require "foobar"
, but when I use local assignments, like local foo = require "foobar"
, my hotswapper failes (partially)!
It seems that the package gets swapped out like intended, however the local variable (from the assignment above) still holds an old reference or the old value that it got when require was called the first time.
My idea was to use Lua's debug.getlocal
and debug.setlocal
functions to find all local variables (upvalues in stack) and update their values/references.
BUT I get an error that the upvalue I want to change is "out of range"... Could somebody help me please? What should I do or how could I work around this?
The complete code is over at Gist, the important/relevant snippets however are...
local_upvalues()
at line 27, which collects all available upvalueslocal function local_upvalues()
local upvalues = {}
local failures = 0
local thread = 0
while true do
thread = thread + 1
local index = 0
while true do
index = index + 1
local success, name, value = pcall(debug.getlocal, thread, index)
if success and name ~= nil then
table.insert(upvalues, {
name = name,
value = value,
thread = thread,
index = index
})
else
if index == 1 then failures = failures + 1 end
break
end
end
if failures > 1 then break end
end
return upvalues
end
debug.setlocal()
at line 89, which tries to update the upvalue that holds the absolete module reference -- update module references of local upvalues
for count, upvalue in ipairs(local_upvalues()) do
if upvalue.value == package.loaded[resource] then
-- print(upvalue.name, "updated from", upvalue.value, "to", message)
table.foreach(debug.getinfo(1), print)
print(upvalue.name, upvalue.thread, upvalue.index)
debug.setlocal(upvalue.thread, upvalue.index, message)
end
end
package.loaded[resource] = message -- update the absolete module
Upvotes: 3
Views: 5635
Reputation: 550
I accepted @Nifim answer. However that will only work for tables as far as I can tell. But require
can also return any type of value. - Nevertheless, it's a nice solution that can work with some tweaking...
However, just for reference - I got my approach working as well! First, I removed the wrapping pcall()
from debug.getlocal()
because this introduced another stack level and thus returned wrong thread and index values that didn't work with debug.setlocal()
. Finally, I moved the debug.setlocal
call into the same function (=same scope) so that I check and re-assign in one step!
See my rereference(absolete, new)
function code below.
local thread = 1
while debug.getinfo(thread) ~= nil do
local index, name, value = 0, nil, nil
repeat
index = index + 1
name, value = debug.getlocal(thread, index)
if name ~= nil
and name ~= "absolete"
and name ~= "new"
then
if value == absolete then
if debug.setlocal(thread, index, new) == name then
print(string.format(
"%s local upvalue '%s' has been re-referenced",
os.date("%d.%m.%Y %H:%M:%S"),
name
))
end
end
end
until name == nil
thread = thread + 1
end
Upvotes: 0
Reputation: 5031
You can use a metatable with __index
. Rather then returning package.loaded[resource]
or _require(resource)
return:
_require(resource)
return setmetatable({}, --create a dummy table
{
__index = function(_, k)
return package.loaded[resource][k] -- pass all index requests to the real resource.
end
})
And
package.loaded[resource] = message -- update the absolete module
print(string.format("%s %s hot-swap of module '%s'",
os.date("%d.%m.%Y %H:%M:%S"),
stateless and "stateless" or "stateful",
hotswap.registry[resource].url
))
return setmetatable({},
{
__index = function(_, k)
return package.loaded[resource][k]
end
})
Doing this you shouldn't need to look up the upvalues at all, as this will force any local
require results to always reference the up-to-date resource.
There are likely cases where this will not work well, or otherwise break a module, but with some tweaking it can.
Upvotes: 0