IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> unity lua热重载编辑器下检查lua文件的变化前端自动热更lua代码 -> 正文阅读

[游戏开发]unity lua热重载编辑器下检查lua文件的变化前端自动热更lua代码

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 以下是使用到的属性

IncludeSubdirectories

获取或设置一个值,该值指示是否应监视指定路径中的子目录。

Path

获取或设置要监视的目录的路径。

NotifyFilter

获取或设置要监视的更改的类型。

NotifyFilters.LastWrite也是c#系统类,代表上一次有任何内容写入到

Filter

获取或设置用于确定在目录中监视哪些文件的筛选器字符串。

Filter筛选lua文件

Changed

当更改指定?Path?中的文件和目录时发生。

watcher.Changed += handler;hander是回调函数
EnableRaisingEvents

获取或设置一个值,该值指示是否启用此组件。

InternalBufferSize

获取或设置内部缓冲区的大小(以字节为单位)。

其中回调函数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指向旧模块,这样就是数据保留,函数代码更新的热重载了

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-05-04 07:31:23  更:2022-05-04 07:32:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:59:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码