You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
5.6 KiB
Lua

--- Updater to update loaded module.
-- Updating a table is to update metatable and sub-table of the old table,
-- and to update functions of the old table, keeping value fields.
-- Updating a function is to copy upvalues of old function to new function.
-- Functions will be replaced later after updating.
local M = {}
local update_table
local update_func
-- Updated signature set to prevent self-reference dead loop.
local updated_sig = {}
-- Map old function to new functions.
local updated_func_map = {}
-- Do not update and replace protected objects.
-- Set to hotfix.protected.
local protected = {}
-- Set to hotfix.log_debug.
function M.log_debug(msg_str)
end
-- Check if function or table has been updated. Return true if updated.
local function check_updated(new_obj, old_obj, name, deep)
local signature = string.format("new(%s) old(%s)", tostring(new_obj), tostring(old_obj))
M.log_debug(string.format("%sUpdate %s: %s", deep, name, signature))
if new_obj == old_obj then
M.log_debug(deep .. " Same")
return true
end
if updated_sig[signature] then
M.log_debug(deep .. " Already updated")
return true
end
updated_sig[signature] = true
return false
end
-- Update new function with upvalues of old function.
-- Parameter name and deep are only for log.
function update_func(new_func, old_func, name, deep)
assert("function" == type(new_func))
assert("function" == type(old_func))
if protected[old_func] then
return
end
if check_updated(new_func, old_func, name, deep) then
return
end
deep = deep .. " "
updated_func_map[old_func] = new_func
-- Get upvalues of old function.
local old_upvalue_map = {}
for i = 1, math.huge do
local name, value = debug.getupvalue(old_func, i)
if not name then
break
end
old_upvalue_map[name] = value
end
local function log_dbg(name, from, to)
M.log_debug(string.format("%ssetupvalue %s: (%s) -> (%s)", deep, name, tostring(from), tostring(to)))
end
-- Update new upvalues with old.
for i = 1, math.huge do
local name, value = debug.getupvalue(new_func, i)
if not name then
break
end
local old_value = old_upvalue_map[name]
if old_value then
local type_old_value = type(old_value)
if type_old_value ~= type(value) then
debug.setupvalue(new_func, i, old_value)
log_dbg(name, value, old_value)
elseif type_old_value == "function" then
update_func(value, old_value, name, deep)
elseif type_old_value == "table" then
update_table(value, old_value, name, deep)
debug.setupvalue(new_func, i, old_value)
else
debug.setupvalue(new_func, i, old_value)
log_dbg(name, value, old_value)
end
end -- if old_value
end -- for i
end -- update_func()
-- Compare 2 tables and update the old table. Keep the old data.
function update_table(new_table, old_table, name, deep)
assert("table" == type(new_table))
assert("table" == type(old_table))
if protected[old_table] then
return
end
if check_updated(new_table, old_table, name, deep) then
return
end
deep = deep .. " "
-- Compare 2 tables, and update old table.
for key, value in pairs(new_table) do
local old_value = old_table[key]
local type_value = type(value)
if type_value ~= type(old_value) then
old_table[key] = value
M.log_debug(
string.format("%sUpdate field %s: (%s) -> (%s)", deep, key, tostring(old_value), tostring(value))
)
elseif type_value == "function" then
update_func(value, old_value, key, deep)
elseif type_value == "table" then
update_table(value, old_value, key, deep)
end
end -- for
-- Update metatable.
local old_meta = debug.getmetatable(old_table)
local new_meta = debug.getmetatable(new_table)
if type(old_meta) == "table" and type(new_meta) == "table" then
update_table(new_meta, old_meta, name .. "'s Meta", deep)
end
end -- update_table()
-- Update new loaded object with package.loaded[module_name].
local function update_loaded_module2(module_name, new_obj)
assert(nil ~= new_obj)
assert("string" == type(module_name))
local old_obj = package.loaded[module_name]
local new_type = type(new_obj)
local old_type = type(old_obj)
if new_type == old_type then
if "table" == new_type then
update_table(new_obj, old_obj, module_name, "")
return
end
if "function" == new_type then
update_func(new_obj, old_obj, module_name, "")
return
end
end -- if new_type == old_type
M.log_debug(string.format("Directly replace module: old(%s) -> new(%s)", tostring(old_obj), tostring(new_obj)))
package.loaded[module_name] = new_obj
end -- update_loaded_module2()
-- Update new loaded object with package.loaded[module_name].
-- Return an updated function map (updated_func_map).
-- new_module_obj is the newly loaded module object.
function M.update_loaded_module(module_name, protected_objects, new_module_obj)
assert(type(module_name) == "string")
assert(type(protected_objects) == "table")
protected = protected_objects
updated_func_map = {}
updated_sig = {}
update_loaded_module2(module_name, new_module_obj)
updated_sig = {}
return updated_func_map
end -- update_loaded_module()
return M