最近沉迷lua脚本热更,想说这个可以提高多少菜鸡的调试效率,找了网上好多文章,但是都不行,尝试了很久,并且自己测试和学习,写了一遍,勉强能热更了。下面记录一下热更Lua的过程。
一、用来卸载表格的加载
最简单粗暴的热更新就是将package.loaded[modelname]的值置为nil,强制重新加载:
function
这样做虽然能完成热更,但问题是已经引用了该模块的地方不会得到更新, 因此我们需要将引用该模块的地方的值也做对应的更新。
function
二、我认为逻辑最清晰的,但是可能是我不会用吧!
源链接:链接
--region 利用_ENV环境,在加载的时候把数据加载到_ENV下,然后再通过对比的方式修改_G底下的值,从而实现热更新,函数 -- 失败
三、有点复杂,递归了debug.getregistry(),然后去替换旧的值
源链接:http://asqbtcupid.github.io/luahotupdate1-require/ 有介绍原理,讲得还蛮细的,学习了蛮多,但是这个递归真的是复杂,我注释掉了一些递归,能满足基本的需求。
local
我跟老大炫耀的时候,老大说,那你懂其中的原理吗,一下问懵我了,老大说,你要学习到其中的原理才能进步啊,不然就只是个会用工具的人。好有道理,搞得我羞愧难当,赶紧好好学习其中原理。
Upvalue
Upvalue 是指那些函数外被引用到的local变量,比如:
local
那么a就是这个foo的upvalue。
getupvalue (f, up)
此函数返回函数 f 的第 up 个上值的名字和值。 如果该函数没有那个上值,返回 nil 。
以 '(' (开括号)打头的变量名表示没有名字的变量 (去除了调试信息的代码块)。
setupvalue (f, up, value):
这个函数将 value 设为函数 f 的第 up 个上值。 如果函数没有那个上值,返回 nil 否则,返回该上值的名字。
_G和debug.getregistry这2张表
学习的时候,我一直以为只有把_G这张全局表的旧值替换掉就好了,然后真正实施的时候,还是会有种种问题,实在是很糟糕,看这段代码的时候一直不是很理解,看了debug.getregistry的定义:debug.getregistry():返回注册表表,这是一个预定义出来的表, 可以用来保存任何 C 代码想保存的 Lua 值。
还是半桶水,但是我有注意到_G这种表其实在debug.getregistry返回的这张注册表里有,所以最后就递归这张表其替换里面的旧值就好了。
getfenv(object):返回对象的环境变量。
setfenv(function,_ENV):设置一段代码的运行环境
io.popen("dir /S/B /A:A ""..RootPath.."""):获取一个File对象其下的所有文件和目录的绝对路径: 的所有文件(/S/B),不包括文件夹(/A:A),io.popen返回文件句柄file handle
对了,InitFileMap这个函数有个要注意的,luaPath在保存的时候是按文件夹路径保存的比如:game.modules.XXX.lua,所以可能要自己搞对这个路径;
最后总结一下流程:
1. 初始化需要热更的文件路径,用一张哈希表存下来;
2. 然后遍历这些路径,读取所有的代码,判断是否是新代码,新的话记录到ChangedFuncList列表里面;3. 新代码的话,就把加载的字符串放置在空环境了,防止报错setfenv(NewFunction, ReloadUtil.FakeENV);4. 代替旧代码,拿着ChangedFuncList列表到注册表(debug.getregistry())里面去找旧值,递归注册表的旧值替换成新的值。