diff --git a/lualib/snax.lua b/lualib/snax.lua index 6af7fbd6..fd476528 100644 --- a/lualib/snax.lua +++ b/lualib/snax.lua @@ -101,9 +101,9 @@ local function test_result(ok, ...) end end -function snax.hotfix(obj, ...) +function snax.hotfix(obj, source, ...) local t = snax.interface(obj.type) - test_result(skynet_call(obj.handle, "lua", t.system.hotfix, obj.type, ...)) + return test_result(skynet_call(obj.handle, "lua", t.system.hotfix, source, ...)) end return snax diff --git a/lualib/snax_hotfix.lua b/lualib/snax_hotfix.lua index bbe5c214..f42ab0c6 100644 --- a/lualib/snax_hotfix.lua +++ b/lualib/snax_hotfix.lua @@ -3,105 +3,92 @@ local io = io local hotfix = {} -local function loader(filename, ...) - local f = io.open(filename, "rb") - if not f then - return false, string.format("Can't open %s", filename) - end - local source = f:read "*a" - f:close() - - return load(source, "@"..filename, ...) -end - -local function check_funcs(f1,f2) - for k,a in pairs(f1) do - local b = f2[k] - assert(a[1] == b[1] and a[2] == b[2] and a[3] == b[3] , - string.format("%s.%s %s.%s , function mismatch", a[2], a[3] , b[2], b[3])) - end -end - -local function get_upvalues(f, global) - local uv = {} +local function collect_uv(f , uv) local i = 1 while true do - local name = debug.getupvalue(f, i) - if name then - local uid = debug.upvalueid(f, i) - uv[name] = i - if global[name] and global[name][1] ~= uid then - error(string.format("ambiguity upvalue %s", name)) - end - global[name] = { uid , f , i } - else + local name, value = debug.getupvalue(f, i) + if name == nil then break end - i = i + 1 - end - return uv -end + local id = debug.upvalueid(f, i) -local function join_upvalues(f, new_f, uv) - local i = 1 - while true do - local name = debug.getupvalue(new_f, i) - if name then - if uv[name] then - debug.upvaluejoin(new_f, i, f, uv[name]) - end + if uv[name] then + assert(uv[name].id == id, string.format("ambiguity local value %s", name)) else - break + uv[name] = { func = f, index = i, id = id } + + if type(value) == "function" then + collect_uv(value, uv) + end end + i = i + 1 end end -local function join_global_upvalues(f, global) - local i = 1 - while true do - local name = debug.getupvalue(f, i) - if name then - local uv = global[name] - if uv then - debug.upvaluejoin(f, i, uv[2], uv[3]) - end - else - break - end - i = i + 1 - end -end - -local function update_upvalues(funcs, newfuncs) +local function collect_all_uv(funcs) local global = {} - for id,v in pairs(funcs) do - local f = v[4] - local new_f = newfuncs[id][4] - if f and new_f then - local uv = get_upvalues(f, global) - v[4] = new_f - join_upvalues(f, new_f, uv) - newfuncs[id] = nil + for _, v in pairs(funcs) do + if v[4] then + collect_uv(v[4], global) end end - for id,v in pairs(newfuncs) do - if v[2] == "system" then - funcs[id] = v - local f = v[4] - join_global_upvalues(f, global) - else - error(string.format("Detect unkown function %s.%s", v[2], v[3])) + + return global +end + +local function loader(source) + return function (filename, ...) + return load(source, "=patch", ...) + end +end + +local function find_func(funcs, group , name) + for _, desc in pairs(funcs) do + local _, g, n = table.unpack(desc) + if group == g and name == n then + return desc end end end -local function inject(funcs, new) - check_funcs(funcs, new) - update_upvalues(funcs, new) +local dummy_env = {} + +local function patch_func(funcs, global, group, name, f) + local desc = assert(find_func(funcs, group, name) , string.format("Patch mismatch %s.%s", group, name)) + local i = 1 + while true do + local name, value = debug.getupvalue(f, i) + if name == nil then + break + elseif value == nil or value == dummy_env then + local old_uv = global[name] + if old_uv then + debug.upvaluejoin(f, i, old_uv.func, old_uv.index) + end + end + i = i + 1 + end + desc[4] = f end -return function (funcs, modname) - local new_funcs = si(modname, _ENV, loader) - return pcall(inject, funcs, new_funcs) +local function inject(funcs, source, ...) + local patch = si("patch", dummy_env, loader(source)) + local global = collect_all_uv(funcs) + + for _, v in pairs(patch) do + local _, group, name, f = table.unpack(v) + if f then + patch_func(funcs, global, group, name, f) + end + end + + local hf = find_func(patch, "system", "hotfix") + if hf and hf[4] then + return hf[4](...) + end +end + +return function (funcs, source, ...) + return pcall(inject, funcs, source, ...) end diff --git a/service/snaxd.lua b/service/snaxd.lua index 2b62ddee..519c45e4 100644 --- a/service/snaxd.lua +++ b/service/snaxd.lua @@ -11,13 +11,7 @@ skynet.start(function() local command = method[3] if command == "hotfix" then local hotfix = require "snax_hotfix" - local ok, msg = hotfix(func, ...) - if ok then - local hf = func[id][4] - skynet.ret(skynet.pack(pcall(hf, select(2,...)))) - else - skynet.ret(skynet.pack(ok, msg)) - end + skynet.ret(skynet.pack(hotfix(func, ...))) elseif command == "init" then assert(not init, "Already init") local initfunc = method[4] or function() end diff --git a/test/testping.lua b/test/testping.lua index 512cbe31..7952f20b 100644 --- a/test/testping.lua +++ b/test/testping.lua @@ -6,7 +6,23 @@ skynet.start(function() print(ps.pub.hello()) print(ps.req.ping("foobar")) print(pcall(ps.req.error)) - snax.hotfix(ps) + print("Hotfix (i) :", snax.hotfix(ps, [[ + +local i +local hello + +function subscribe.hello() + i = i + 1 + print ("fix", i, hello) +end + +function hotfix(...) + local temp = i + i = 100 + return temp +end + + ]])) print(ps.pub.hello()) print(snax.kill(ps,"exit")) skynet.exit()