FileSystemWatcher这里主要用到的一个c#系统类
https://docs.microsoft.com/zh-cn/dotnet/api/system.io.filesystemwatcher?view=net-6.0
watcher = new FileSystemWatcher();
watcher.IncludeSubdirectories = true;
watcher.Path = dirPath;
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Filter = "*.lua";
watcher.Changed += new FileSystemEventHandler(LuaFileOnChanged);
watcher.EnableRaisingEvents = true;
watcher.InternalBufferSize = 10240;
用于?FileSystemWatcher?监视指定目录中的更改。 可以监视指定目录的文件和子目录中的更改。 可以创建组件来监视本地计算机、网络驱动器或远程计算机上的文件。
FileSystemWatcher 以下是使用到的属性
NotifyFilters.LastWrite也是c#系统类,代表上一次有任何内容写入到
Filter | 获取或设置用于确定在目录中监视哪些文件的筛选器字符串。 |
Filter筛选lua文件
watcher.Changed += handler;hander是回调函数
其中回调函数LuaFileOnChanged如下
private static void LuaFileOnChanged(object obj, FileSystemEventArgs args)
{
string fullPath = args.FullPath;
string luaFolderName = "Lua";
string requirePath = fullPath.Replace(".lua", "");
int luaScriptIndex = requirePath.IndexOf(luaFolderName) + luaFolderName.Length + 1;
requirePath = requirePath.Substring(luaScriptIndex);
requirePath = requirePath.Replace('\\', '.');
_changeFiles.Add(requirePath);
}
FileSystemEventHandler 委托 (System.IO) | Microsoft DocsFileSystemEventHandler 委托
public delegate void FileSystemEventHandler(object sender, FileSystemEventArgs e);
?带两个参数,
参数
Object
事件源。
e
FileSystemEventArgs
包含事件数据的?FileSystemEventArgs。
?FileSystemEventArgs里面有着文件的完全路径
将改动的文件记录下来,并在主线程中对这些文件进行重载?为什么需要记录,原因是因为FileSystemWatcher是多线程的。每新建一个FileSystemWatcher都相当于开了一个新线程。?如果不拿一个列表记录,直接在多线程下重载lua模块,极其容易导致unity崩溃!!!?
使用EditorApplication.update重载,使用这个需要注意,这个在不是运行模式下也会执行,所以需要跳出if (EditorApplication.isPlaying == false) ? ? ? ? ? ? return;
EditorApplication.update -= Reload;
EditorApplication.update += Reload;
private static LuaFunction ReloadFunction;
luaState.Require("Base.Main");
ReloadFunction = luaState.GetFunction("hotfix");
private static void Reload()
{
if (EditorApplication.isPlaying == false)
return;
if (_changeFiles.Count == 0)
return;
foreach (var item in _changeFiles)
{
ReloadFunction.Call(item);
}
_changeFiles.Clear();
}
重载的话调用lua中的的Main.lua中的hotfix方法
function hotfix(fileName)
if BATTLE_EDITOR then
return
end
if not package.loaded[fileName] then
return
end
print("hotfix call. file:", fileName)
--置空之前加载的
local oldModule = package.loaded[fileName]
package.loaded[fileName] = nil
--重新加载
local ok, err = pcall(require, fileName)
if not ok then
package.loaded[fileName] = oldModule
warning("重新加载失败,再尝试一下. error: %s", err)
return
end
local newModule = package.loaded[fileName]
if type(newModule) ~= "table" or type(oldModule) ~= "table" then
warning("hotfix fail. 热更失败. %s还是旧文件", fileName)
return
end
--更新旧module
if newModule.__classid then --require 时,luaObject会生成一个新的id.
newModule.__classid = oldModule.__classid
end
local showingPanel = false
local isUI = not not (newModule.InternalShow and newModule.OnCreate)
if isUI then --如果是UI,检测一下参数变化..
showingPanel = OnHotfixUI(newModule, oldModule)
end
if showingPanel then
UIManager:Instance():HidePanel(oldModule)
end
local updateTables = {}
HotfixStackNum = 0
UpdateTable(newModule, oldModule, updateTables, HotfixStackNum)
if oldModule.OnReload then
oldModule:OnReload()
end
package.loaded[fileName] = oldModule
if isUI then --重新加载得了..不然OnCreate改不到..
package.loaded[fileName] = nil
end
print("relaod Finish, 更新成功了")
if showingPanel then
UIManager:Instance():ShowPanel(require(fileName), safeunpack(showingPanel._cacheParams or {}))
--UI直接重新require,不管那些旧函数了..
end
end
package.loaded记录了lua内部哪些模块被?require,我们这里先--置空之前加载的旧模块 ?? ?local oldModule = package.loaded[fileName] ?? ?package.loaded[fileName] = nil?
然后用pcall的方式加载更改的当前文件? ?local ok, err = pcall(require, fileName) ?? ?if not ok then? ?? ??? ?package.loaded[fileName] = oldModule ?? ??? ?warning("重新加载失败,再尝试一下. error: %s", err) ?? ??? ?return ?? ?end
local newModule = package.loaded[fileName] ?? ?if type(newModule) ~= "table" or type(oldModule) ~= "table" then? ?? ??? ?warning("hotfix fail. 热更失败. %s还是旧文件", fileName) ?? ??? ?return? ?? ?end
一般如果只是重载,到这步就完了,
但是还有个问题就是lua文件会缓存数据,我们就模块的缓存数据不能丢失,所以还需要对旧模块进行处理,这里使用的是UpdateTable函数,里面是把旧模块的新函数和局部数据进行替换
表里的三种元素,数据,不做处理,
函数,UpdateFunc新的函数
表,嵌套UpdateTable处理
原表,嵌套UpdateTable处理
local function UpdateTable(newModule, oldModule, updateTables)
if type(newModule) ~= "table" or type(oldModule) ~= "table" then
return
end
HotfixStackNum = HotfixStackNum + 1
if HotfixStackNum > 10000 then
print(debug.traceback(">>>>>>>>>>>>>>>>>>"))
warning("stack overflow ????")
return
end
for k, v in pairs(newModule) do
local valueType = type(v)
local oldValue = oldModule[k]
if valueType == "function" then --替换func,以及其中的upvalue
UpdateFunc(v, oldValue, k)
oldModule[k] = v
elseif valueType == "table" then
if not updateTables[v] then --防止互相引用引起死循环
-- print(k, v, oldValue)
updateTables[v] = true
UpdateTable(v, oldValue, updateTables)
end
else
oldModule[k] = v
end
end
local oldMeta = debug.getmetatable(oldModule)
local newMeta = debug.getmetatable(newModule)
if type(oldMeta) == "table" and type(newMeta) == "table" then
if not updateTables[newMeta] then
updateTables[newMeta] = true
UpdateTable(newMeta, oldMeta, updateTables)
end
end
end
--更新旧模块的函数方法
local function UpdateFunc(newFunc, oldFunc, funcName)
if type(oldFunc) ~= "function" then
return
end
local newUpvalueMap = {}
for i = 1, 10000 do
local name, value = debug.getupvalue(newFunc, i)
if not name then
break
end
newUpvalueMap[name] = value
end
for i = 1, 10000 do
local name, value = debug.getupvalue(oldFunc, i)
if not name then
break
end
local newValue = newUpvalueMap[name]
if newValue then
local newTypeName = type(newValue)
local oldTypeName = type(value)
if newTypeName ~= oldTypeName and #name > 0 then --不知道为什么,函数里注释掉upvalue后,会产生一个name为空,value为false的字段..
warning("注意, upvalue类型不一致。 函数名:%s, 变量名:%s, 之前类型为:%s, 当前类型为:%s", funcName, name, oldTypeName, newTypeName)
end
debug.setupvalue(oldFunc, i, newValue)
end
end
end
最后再吧package.loaded[fileName] = oldModule指向旧模块,这样就是数据保留,函数代码更新的热重载了
|