Skip to content

Instantly share code, notes, and snippets.

@tkokof
Created April 9, 2019 08:23
Show Gist options
  • Save tkokof/ed2e69b309a42c37607ea03cdbdb3327 to your computer and use it in GitHub Desktop.
Save tkokof/ed2e69b309a42c37607ea03cdbdb3327 to your computer and use it in GitHub Desktop.
a simple reload module implementation of Lua
local function try_get_upvalue(func, name)
local upvalue_index = 1
while true do
local u_name, u_value = debug.getupvalue(func, upvalue_index)
if not u_name then
break
elseif u_name == name then
return u_name, u_value, upvalue_index
end
upvalue_index = upvalue_index + 1
end
end
local function merge_upvalues_recur(old_func, new_func)
local upvalue_index = 1
while true do
local new_name, new_value = debug.getupvalue(new_func, upvalue_index)
if not new_name then
break
elseif new_name ~= "_ENV" then -- skip "_ENV" now ...
local name, value, index = try_get_upvalue(old_func, new_name)
if not name then
print("can not resolve upvalue(reload could be incorrect) : " .. tostring(new_name))
else
if type(value) == "function" then
return merge_upvalues_recur(value, new_value)
else
debug.upvaluejoin(new_func, upvalue_index, old_func, index)
end
end
end
upvalue_index = upvalue_index + 1
end
return true
end
-- limitation reload module
function reload_module(name, path)
if name and path then
local global_module = _G[name]
local package_module = package.loaded[path]
-- when package_module is true, check global_module and package_module
if package_module ~= true then
if global_module and global_module ~= package_module then
return false, "incorrect module internal status : " .. tostring(name)
end
end
-- clear references
_G[name] = nil
package.loaded[path] = nil
-- require new module
-- NOTE module should not have side effects when require
local status, error = pcall(require, path)
if not status then
-- rollback for error handling
_G[name] = global_module
package.loaded[path] = package_module
return false, error
end
local new_global_module = _G[name]
local new_package_module = package.loaded[path]
-- rollback first for error handling
_G[name] = global_module
package.loaded[path] = package_module
local old_module = type(package_module) == "table" and package_module or global_module
local new_module = type(new_package_module) == "table" and new_package_module or new_global_module
if type(old_module) ~= "table" or type(new_module) ~= "table" then
return false, "error to get old and new module data : " .. tostring(name)
end
for k, v in pairs(old_module) do
if type(v) ~= "function" then
-- simple copy non-function stuff from old to new
-- TODO improve ?
new_module[k] = v
else
-- merge function upvalues
-- TODO improve ?
local old_func = v
local new_func = new_module[k]
if type(new_func) ~= "function" then
return false, "only support same function layout module reload now : " .. tostring(name) .. "." .. tostring(k)
else
local ret = merge_upvalues_recur(old_func, new_func)
if not ret then
return false, "error to resolve upvalues : " .. tostring(name) .. "." .. tostring(k)
end
end
end
end
-- extra clear stuff
if type(old_module.release) == "function" then
old_module.release()
end
if type(new_module.init) == "function" then
new_module.init()
end
-- set to new module
_G[name] = new_global_module
package.loaded[path] = new_package_module
return true
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment