写在前面
- 本文讨论的“Lua热重载”是基于他人现成工具和相关博文上展开的,所以这里并不会重复实现一遍工具,主要记录我的理解过程。
Lua热重载
探索
- 偶然在知乎上翻到一篇文章“使用ILRuntime遇到的一些问题”,文章最后提到Lua特有的加载机制(如下图),我第一个念头就是“怎么用Lua做了那么久的热更,我却没想到用Lua来做测试呢!”,如果能在不重新启动Unity的前提下重载Lua脚本,测试效率肯定会大大提升,而我最想提升的就是程序员在写好UI脚本后的测试效率,于是我开始在网上搜索有没有现成工具。
刚开始搜索到的博客们实现重点都差不多,代码如下图:
function reimport(name) local package = package package.loaded[name] = nil package.preload[name] = nil return require(name) end
于是我直接用这段代码拿工作项目里的图鉴UI做测试,结果没有成功。我没懂为啥不成功,于是我继续搜资料。以下是我主要参考的3篇资料。
【参考A】
【参考A】它是我找到的第一个示例,作者提供了两个G站链接,一个是作者自己的,另一个是作者参考的,而我先误点开的是参考链接,下载代码后发现运行时直接上while(true)的话Unity会卡死(尽管作者有写sleep),肇事代码如下图:
将肇事代码修好后开始测试,第一次测试,可以正常热重载,放到另一个项目的第二次测试,却不能正常热重载。因为这个没有Unity示例,代码一时半会看不懂,所以我暂时放弃……
后来我也打开看过作者的链接,不过那时我已经找到了【参考B】,所以只是粗略的翻了翻。
【参考B】
【参考B】有Unity示例,有代码讲解!是这3篇参考资料里读起来最轻松的(我在阅读的时候发现【参考A】作者链接的代码其实就是【参考B】的完整版,那里update_func()的内容会更加详细)。
它的Unity示例内容简单来说,就是在C#那边监听文件改动,如有改动,调用Lua那边的hotfix脚本。作者写了两个重载时机:管理者的Update和具体脚本的Reload。重载实现以package.loaded为前提。
以下是我在阅读、测试【参考B】代码时冒出的问题和解答(用“——>”符号标识):
- updated_tables感觉只是更新函数,和记录访问过的table,没看出哪里体现更新table ?——> 其实重点就是“更新函数”。
- 为什么会想到upvalue?——> 我最开始猜想是为了不破坏旧引用,后来测试证实,使用旧引用具体是为了维持其他地方的引用不变,所以不能直接拿新引用直接覆盖旧引用。(不过在测试中,正常重载后依然会显示老代码对应的行号,容易误导)
- 作者提供的代码只能在update函数里改,我想在任意函数里改,咋整?——> 写在作者提供的Reload函数中。
- 我在读作者博客时有一段没读懂(如下图),package.load[filename] 拿到的不是布尔值吗,类的类型哪里体现?filename即对应Lua脚本,不管多少次require,都会指向同一对象,怎么会不是类的实例呢?
——> 这里我的理解出错了。package.load[filename] 拿到的不是布尔值,是filename对应的Lua脚本具体返回值,如果没有写返回值,则require会记返回值是true。
“调用package.load[filename] 获得的是类的类型,并不是类的实例。”,首先,作者用的是云风写的Lua类,在class的代码里(如下图,我截图用的是云风版class,作者用的是自己修改后的BaseClass)能看到返回的是class_type(即“类-类型”),class_type用了new()才能拿到实例。
以作者的PlayerMove.lua为例,在PlayerMove.lua代码末尾,他return的是PlayerMove,所以在require("PlayerMove")时,返回的是PlayerMove类型。
如果作者在PlayerMove.lua代码末尾return的是PlayerMove.New(),则在require("PlayerMove")时,返回的是PlayerMove实例。
【参考C】
KSFramework:它的热重载实现写的很简单,完全没碰Lua那边,只是在C#这边把UI的老引用清了,换成新引用。
接入工作项目
我尝试将【参考B】这个现成的重载工具接入工作项目,过程依然问题多多,以下是遇到问题和解决方法记录:
-
最开始我在C#这新开了一个LuaState来负责监听Lua脚本变化工作,但不行,会出问题(Unity崩溃;工作项目的一些Lua脚本报错但看代码无问题),所以去掉了新开的LuaState。
- 工作项目中基本UI都是用dofile加载的,而不是require,所以在package.loaded[...]中找不到记录(前面提到的图鉴UI就是用dofile加载的,所以最开始测试失败),这里需要我另写函数来处理,如下:
- 虽然【参考B】用的是Lua脚本变化时自动触发重载,但因为工作项目是Unity2017版本,我在Unity2017上测试时发现自动重载时内容未发生变化,而此时Unity自己正在加载,卡住了一小会;如果是在Unity加载完后再触发重载,此时内容有变化,重载正常。因此我将自动触发改为了手动触发。
- 测试发现【参考B】脚本不让在运行时加新函数,会有断言报错。可以改成“在运行时加新函数”,但我不认为有改的必要。
因为UI在C#那边记录的引用比较多,再加上正常重载Lua脚本后还显示旧行号的问题,所以这个工具我用的不多,研究到此告一段落。