local HU = {}
|
|
|
|
function HU.FailNotify(...)
|
|
if HU.NotifyFunc then HU.NotifyFunc(...) end
|
|
end
|
|
function HU.DebugNofity(...)
|
|
if HU.DebugNofityFunc then HU.DebugNofityFunc(...) end
|
|
end
|
|
|
|
local function GetWorkingDir()
|
|
if HU.WorkingDir == nil then
|
|
local p = io.popen("echo %cd%")
|
|
if p then
|
|
HU.WorkingDir = p:read("*l").."\\"
|
|
p:close()
|
|
end
|
|
end
|
|
return HU.WorkingDir
|
|
end
|
|
|
|
local function Normalize(path)
|
|
path = path:gsub("/","\\")
|
|
if path:find(":") == nil then
|
|
path = GetWorkingDir()..path
|
|
end
|
|
local pathLen = #path
|
|
if path:sub(pathLen, pathLen) == "\\" then
|
|
path = path:sub(1, pathLen - 1)
|
|
end
|
|
|
|
local parts = { }
|
|
for w in path:gmatch("[^\\]+") do
|
|
if w == ".." and #parts ~=0 then table.remove(parts)
|
|
elseif w ~= "." then table.insert(parts, w)
|
|
end
|
|
end
|
|
return table.concat(parts, "\\")
|
|
end
|
|
|
|
function HU.InitFileMap(RootPath)
|
|
for _, rootpath in pairs(RootPath) do
|
|
rootpath = Normalize(rootpath)
|
|
local file = io.popen("dir /S/B /A:A "..rootpath)
|
|
io.input(file)
|
|
for line in io.lines() do
|
|
local FileName = string.match(line,".*\\(.*)%.lua")
|
|
if FileName ~= nil then
|
|
if HU.FileMap[FileName] == nil then
|
|
HU.FileMap[FileName] = {}
|
|
end
|
|
local luapath = string.sub(line, #rootpath+2, #line-4)
|
|
luapath = string.gsub(luapath, "\\", ".")
|
|
HU.LuaPathToSysPath[luapath] = SysPath
|
|
table.insert(HU.FileMap[FileName], {SysPath = line, LuaPath = luapath})
|
|
end
|
|
end
|
|
file:close()
|
|
end
|
|
end
|
|
|
|
function HU.InitFakeTable()
|
|
local meta = {}
|
|
HU.Meta = meta
|
|
local function FakeT() return setmetatable({}, meta) end
|
|
local function EmptyFunc() end
|
|
local function pairs() return EmptyFunc end
|
|
local function setmetatable(t, metaT)
|
|
HU.MetaMap[t] = metaT
|
|
return t
|
|
end
|
|
local function getmetatable(t, metaT)
|
|
return setmetatable({}, t)
|
|
end
|
|
local function require(LuaPath)
|
|
if not HU.RequireMap[LuaPath] then
|
|
local FakeTable = FakeT()
|
|
HU.RequireMap[LuaPath] = FakeTable
|
|
end
|
|
return HU.RequireMap[LuaPath]
|
|
end
|
|
function meta.__index(t, k)
|
|
if k == "setmetatable" then
|
|
return setmetatable
|
|
elseif k == "pairs" or k == "ipairs" then
|
|
return pairs
|
|
elseif k == "next" then
|
|
return EmptyFunc
|
|
elseif k == "require" then
|
|
return require
|
|
else
|
|
local FakeTable = FakeT()
|
|
rawset(t, k, FakeTable)
|
|
return FakeTable
|
|
end
|
|
end
|
|
function meta.__newindex(t, k, v) rawset(t, k, v) end
|
|
function meta.__call() return FakeT(), FakeT(), FakeT() end
|
|
function meta.__add() return meta.__call() end
|
|
function meta.__sub() return meta.__call() end
|
|
function meta.__mul() return meta.__call() end
|
|
function meta.__div() return meta.__call() end
|
|
function meta.__mod() return meta.__call() end
|
|
function meta.__pow() return meta.__call() end
|
|
function meta.__unm() return meta.__call() end
|
|
function meta.__concat() return meta.__call() end
|
|
function meta.__eq() return meta.__call() end
|
|
function meta.__lt() return meta.__call() end
|
|
function meta.__le() return meta.__call() end
|
|
function meta.__len() return meta.__call() end
|
|
return FakeT
|
|
end
|
|
|
|
function HU.InitProtection()
|
|
HU.Protection = {}
|
|
HU.Protection[setmetatable] = true
|
|
HU.Protection[pairs] = true
|
|
HU.Protection[ipairs] = true
|
|
HU.Protection[next] = true
|
|
HU.Protection[require] = true
|
|
HU.Protection[HU] = true
|
|
HU.Protection[HU.Meta] = true
|
|
HU.Protection[math] = true
|
|
HU.Protection[string] = true
|
|
HU.Protection[table] = true
|
|
end
|
|
|
|
function HU.AddFileFromHUList()
|
|
package.loaded[HU.UpdateListFile] = nil
|
|
local FileList = _G.originalRequire (HU.UpdateListFile)
|
|
HU.ALL = false
|
|
HU.HUMap = {}
|
|
for _, file in pairs(FileList) do
|
|
if file == "_ALL_" then
|
|
HU.ALL = true
|
|
for k, v in pairs(HU.FileMap) do
|
|
for _, path in pairs(v) do
|
|
HU.HUMap[path.LuaPath] = path.SysPath
|
|
end
|
|
end
|
|
return
|
|
end
|
|
if HU.FileMap[file] then
|
|
for _, path in pairs(HU.FileMap[file]) do
|
|
HU.HUMap[path.LuaPath] = path.SysPath
|
|
end
|
|
elseif string.find(file, "$") then
|
|
local search_str = string.sub(file, 2)
|
|
for k, v in pairs(HU.FileMap) do
|
|
for _, path in pairs(v) do
|
|
local file_parts = Split(path.SysPath, "\\")
|
|
if file_parts and #file_parts > 0 then
|
|
local file_name = file_parts[#file_parts]
|
|
local isFind = string.find(file_name, search_str)
|
|
if isFind then
|
|
HU.HUMap[path.LuaPath] = path.SysPath
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
HU.FailNotify("HotUpdate can't not find "..file)
|
|
end
|
|
end
|
|
end
|
|
|
|
function HU.ErrorHandle(e)
|
|
HU.FailNotify("HotUpdate Error\n"..tostring(e))
|
|
HU.ErrorHappen = true
|
|
end
|
|
|
|
function HU.BuildNewCode(SysPath, LuaPath)
|
|
io.input(SysPath)
|
|
local NewCode = io.read("*all")
|
|
if HU.ALL and HU.OldCode[SysPath] == nil then
|
|
HU.OldCode[SysPath] = NewCode
|
|
return
|
|
end
|
|
if HU.OldCode[SysPath] == NewCode then
|
|
io.input():close()
|
|
return false
|
|
end
|
|
HU.DebugNofity(SysPath)
|
|
io.input(SysPath)
|
|
local chunk = "--[["..LuaPath.."]] "
|
|
chunk = chunk..NewCode
|
|
io.input():close()
|
|
local NewFunction = loadstring(chunk)
|
|
if not NewFunction then
|
|
HU.FailNotify(SysPath.." has syntax error.")
|
|
collectgarbage("collect")
|
|
return false
|
|
else
|
|
HU.FakeENV = HU.FakeT()
|
|
HU.MetaMap = {}
|
|
HU.RequireMap = {}
|
|
setfenv(NewFunction, HU.FakeENV)
|
|
local NewObject
|
|
HU.ErrorHappen = false
|
|
xpcall(function () NewObject = NewFunction() end, HU.ErrorHandle)
|
|
if not HU.ErrorHappen then
|
|
HU.OldCode[SysPath] = NewCode
|
|
return true, NewObject
|
|
else
|
|
collectgarbage("collect")
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
function HU.Travel_G()
|
|
local visited = {}
|
|
visited[HU] = true
|
|
local function f(t)
|
|
if (type(t) ~= "function" and type(t) ~= "table") or visited[t] or HU.Protection[t] then return end
|
|
visited[t] = true
|
|
if type(t) == "function" then
|
|
for i = 1, math.huge do
|
|
local name, value = debug.getupvalue(t, i)
|
|
if not name then break end
|
|
if type(value) == "function" then
|
|
for _, funcs in ipairs(HU.ChangedFuncList) do
|
|
if value == funcs[1] then
|
|
debug.setupvalue(t, i, funcs[2])
|
|
end
|
|
end
|
|
end
|
|
f(value)
|
|
end
|
|
elseif type(t) == "table" then
|
|
f(debug.getmetatable(t))
|
|
local changeIndexs = {}
|
|
for k,v in pairs(t) do
|
|
f(k); f(v);
|
|
if type(v) == "function" then
|
|
for _, funcs in ipairs(HU.ChangedFuncList) do
|
|
if v == funcs[1] then t[k] = funcs[2] end
|
|
end
|
|
end
|
|
if type(k) == "function" then
|
|
for index, funcs in ipairs(HU.ChangedFuncList) do
|
|
if k == funcs[1] then changeIndexs[#changeIndexs+1] = index end
|
|
end
|
|
end
|
|
end
|
|
for _, index in ipairs(changeIndexs) do
|
|
local funcs = HU.ChangedFuncList[index]
|
|
t[funcs[2]] = t[funcs[1]]
|
|
t[funcs[1]] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
f(_G)
|
|
local registryTable = debug.getregistry()
|
|
f(registryTable)
|
|
|
|
for _, funcs in ipairs(HU.ChangedFuncList) do
|
|
if funcs[3] == "HUDebug" then funcs[4]:HUDebug() end
|
|
end
|
|
end
|
|
|
|
function HU.ReplaceOld(OldObject, NewObject, LuaPath, From, Deepth)
|
|
if type(OldObject) == type(NewObject) then
|
|
if type(NewObject) == "table" then
|
|
HU.UpdateAllFunction(OldObject, NewObject, LuaPath, From, "")
|
|
elseif type(NewObject) == "function" then
|
|
HU.UpdateOneFunction(OldObject, NewObject, LuaPath, nil, From, "")
|
|
end
|
|
end
|
|
end
|
|
|
|
function HU.HotUpdateCode(LuaPath, SysPath)
|
|
local OldObject = package.loaded[LuaPath]
|
|
print("Cat:luahotupdate [264] OldObject: ",OldObject,LuaPath)
|
|
if OldObject ~= nil then
|
|
HU.VisitedSig = {}
|
|
HU.ChangedFuncList = {}
|
|
local Success, NewObject = HU.BuildNewCode(SysPath, LuaPath)
|
|
if Success then
|
|
HU.ReplaceOld(OldObject, NewObject, LuaPath, "Main", "")
|
|
for LuaPath, NewObject in pairs(HU.RequireMap) do
|
|
local OldObject = package.loaded[LuaPath]
|
|
HU.ReplaceOld(OldObject, NewObject, LuaPath, "Main_require", "")
|
|
end
|
|
setmetatable(HU.FakeENV, nil)
|
|
HU.UpdateAllFunction(HU.ENV, HU.FakeENV, " ENV ", "Main", "")
|
|
if #HU.ChangedFuncList > 0 then
|
|
HU.Travel_G()
|
|
end
|
|
collectgarbage("collect")
|
|
end
|
|
elseif HU.OldCode[SysPath] == nil then
|
|
io.input(SysPath)
|
|
HU.OldCode[SysPath] = io.read("*all")
|
|
io.input():close()
|
|
end
|
|
end
|
|
|
|
function HU.ResetENV(object, name, From, Deepth)
|
|
local visited = {}
|
|
local function f(object, name)
|
|
if not object or visited[object] then return end
|
|
visited[object] = true
|
|
if type(object) == "function" then
|
|
HU.DebugNofity(Deepth.."HU.ResetENV", name, " from:"..From)
|
|
xpcall(function () setfenv(object, HU.ENV) end, HU.FailNotify)
|
|
elseif type(object) == "table" then
|
|
HU.DebugNofity(Deepth.."HU.ResetENV", name, " from:"..From)
|
|
for k, v in pairs(object) do
|
|
f(k, tostring(k).."__key", " HU.ResetENV ", Deepth.." " )
|
|
f(v, tostring(k), " HU.ResetENV ", Deepth.." ")
|
|
end
|
|
end
|
|
end
|
|
f(object, name)
|
|
end
|
|
|
|
function HU.UpdateUpvalue(OldFunction, NewFunction, Name, From, Deepth)
|
|
HU.DebugNofity(Deepth.."HU.UpdateUpvalue", Name, " from:"..From)
|
|
local OldUpvalueMap = {}
|
|
local OldExistName = {}
|
|
for i = 1, math.huge do
|
|
local name, value = debug.getupvalue(OldFunction, i)
|
|
if not name then break end
|
|
OldUpvalueMap[name] = value
|
|
OldExistName[name] = true
|
|
end
|
|
for i = 1, math.huge do
|
|
local name, value = debug.getupvalue(NewFunction, i)
|
|
if not name then break end
|
|
if OldExistName[name] then
|
|
local OldValue = OldUpvalueMap[name]
|
|
if type(OldValue) ~= type(value) then
|
|
debug.setupvalue(NewFunction, i, OldValue)
|
|
elseif type(OldValue) == "function" then
|
|
HU.UpdateOneFunction(OldValue, value, name, nil, "HU.UpdateUpvalue", Deepth.." ")
|
|
elseif type(OldValue) == "table" then
|
|
HU.UpdateAllFunction(OldValue, value, name, "HU.UpdateUpvalue", Deepth.." ")
|
|
debug.setupvalue(NewFunction, i, OldValue)
|
|
else
|
|
debug.setupvalue(NewFunction, i, OldValue)
|
|
end
|
|
else
|
|
HU.ResetENV(value, name, "HU.UpdateUpvalue", Deepth.." ")
|
|
end
|
|
end
|
|
end
|
|
|
|
function HU.UpdateOneFunction(OldObject, NewObject, FuncName, OldTable, From, Deepth)
|
|
if HU.Protection[OldObject] or HU.Protection[NewObject] then return end
|
|
if OldObject == NewObject then return end
|
|
local signature = tostring(OldObject)..tostring(NewObject)
|
|
if HU.VisitedSig[signature] then return end
|
|
HU.VisitedSig[signature] = true
|
|
HU.DebugNofity(Deepth.."HU.UpdateOneFunction "..FuncName.." from:"..From)
|
|
if pcall(debug.setfenv, NewObject, getfenv(OldObject)) then
|
|
HU.UpdateUpvalue(OldObject, NewObject, FuncName, "HU.UpdateOneFunction", Deepth.." ")
|
|
HU.ChangedFuncList[#HU.ChangedFuncList + 1] = {OldObject, NewObject, FuncName, OldTable}
|
|
end
|
|
end
|
|
|
|
function HU.UpdateAllFunction(OldTable, NewTable, Name, From, Deepth)
|
|
if HU.Protection[OldTable] or HU.Protection[NewTable] then return end
|
|
if OldTable == NewTable then return end
|
|
|
|
local function call_back( OldTable,NewTable )
|
|
return tostring(OldTable or "")..tostring(NewTable or "")
|
|
end
|
|
local have_error,signature = pcall(call_back,OldTable,NewTable)
|
|
if not have_error then
|
|
--直接跳出
|
|
print('----LZR luahotupdate.lua 你要重新点下热更')
|
|
return
|
|
end
|
|
|
|
if HU.VisitedSig[signature] then return end
|
|
HU.VisitedSig[signature] = true
|
|
HU.DebugNofity(Deepth.."HU.UpdateAllFunction "..Name.." from:"..From)
|
|
for ElementName, Element in pairs(NewTable) do
|
|
local OldElement = OldTable[ElementName]
|
|
if type(Element) == type(OldElement) then
|
|
if type(Element) == "function" then
|
|
HU.UpdateOneFunction(OldElement, Element, ElementName, OldTable, "HU.UpdateAllFunction", Deepth.." ")
|
|
elseif type(Element) == "table" then
|
|
HU.UpdateAllFunction(OldElement, Element, ElementName, "HU.UpdateAllFunction", Deepth.." ")
|
|
end
|
|
elseif OldElement == nil and type(Element) == "function" then
|
|
if pcall(setfenv, Element, HU.ENV) then
|
|
OldTable[ElementName] = Element
|
|
end
|
|
end
|
|
end
|
|
local OldMeta = debug.getmetatable(OldTable)
|
|
local NewMeta = HU.MetaMap[NewTable]
|
|
if type(OldMeta) == "table" and type(NewMeta) == "table" then
|
|
HU.UpdateAllFunction(OldMeta, NewMeta, Name.."'s Meta", "HU.UpdateAllFunction", Deepth.." ")
|
|
end
|
|
end
|
|
|
|
function HU.Init(UpdateListFile, RootPath, FailNotify, ENV)
|
|
HU.UpdateListFile = UpdateListFile
|
|
HU.HUMap = {}
|
|
HU.FileMap = {}
|
|
HU.NotifyFunc = FailNotify
|
|
HU.OldCode = {}
|
|
HU.ChangedFuncList = {}
|
|
HU.VisitedSig = {}
|
|
HU.FakeENV = nil
|
|
HU.ENV = ENV or _G
|
|
HU.LuaPathToSysPath = {}
|
|
HU.InitFileMap(RootPath)
|
|
HU.FakeT = HU.InitFakeTable()
|
|
HU.InitProtection()
|
|
HU.ALL = false
|
|
end
|
|
|
|
function HU.Update()
|
|
HU.AddFileFromHUList()
|
|
for LuaPath, SysPath in pairs(HU.HUMap) do
|
|
HU.HotUpdateCode(LuaPath, SysPath)
|
|
end
|
|
end
|
|
|
|
return HU
|