我们知道,lua通过package模块来管理模块的搜索和加载,当使用require首次成功加载一个模块后,模块(Table)会被存储在package.loaded中,之后的require行为会直接获取到已加载的模块缓存。
如何在程序执行时,将对模块文件的修改热更新到程序中,同时确保运行时状态的正常。这是项目开发中常见的机制需求,这里给出一个遍历模块键值替换更新的方案:将新文件使用loadfile加载进内存,遍历原Table,根据key匹配value进行替换更新。
方案需要支持对运行时状态数据的继承。
模块在内存中以Table类型存在,我们把更新前的模块称为mod,在内存中的Table称为old_t,把新加载进内存的Table称为new_t。old_t被package管理:
registry.package = {
loaded = {
mod = old_t
-- ...
}
}
将修改后的模块文件使用loadfile加载进内存(没有内置的缓存机制,每次编译),遍历将old_t的键值替换为new_t,实现模块的更新:
-- load module file
local new_t
if package.loaded[mod] then
local filename = package.searchpath(mod, package.path)
local f, err = loadfile(filename)
if not f then
assert(false, string.format("loadfile err=%s", err))
end
new_t = f()
end
-- release old value
local keys = table.allkeys(old_t)
for _, k in ipairs(keys) do
old_t[k] = nil
end
-- update new value
for k, v in pairs(new_t) do
old_t[k] = v
end
运行时状态数据的处理
我们可以约定:
-
需要继承的数据定义在模块的域内;
-
模块提供release方法用于处理并收集原Table中需要继承的内存数据;
-
模块提供onload方法用于将原Table的运行时数据继承到新的模块内存中
local context, inherts
local old_t = package.loaded[mod]
if old_t and new_t then
if old_t._release then
context, inherts = old_t._release(old_t)
end
end
-- inhert old_t runtime
if context and inherts then
for _, key in ipairs(inherts) do
new_t[key] = old_t[key]
end
end
给出一个符合上述热更新规范的模块设计demo:
local context = {} -- TODO logic agent context
local logic = {
_name = "logic",
_inherit = { "_runtime" },
_release = function(self)
return context, self._inherit
end,
_onload = function(self, _context)
print(string.format("run reload on mod %s", self._name))
self._runtime._RELOAD_VERSION = self._runtime._RELOAD_VERSION + 1
end,
_runtime = {
_RELOAD_VERSION = 1
},
_hotfixver = function(self)
print("reload version:", self._runtime._RELOAD_VERSION)
end
}
function logic.callfunc()
print("run callfunc. [logic]")
end
return logic
当我修改本地模块文件将callfunc函数定义为:
function logic.callfunc()
print("run callfunc. [logic_v2]")
end
执行:
local logic = require "logic"
local reload = require "reload"
-- old_t
logic.callfunc()
reload("logic")
-- new_t
logic.callfunc()
logic:_hotfixver()
从输出结果可以看出,callfunc被正确更新为修改后的函数,切runtime数据被正确继承;
linxx@linxx-MacBookAir hotfix % lua tsreload.lua
run callfunc. [logic]
run reload on mod logic.
run callfunc. [logic_v2]
reload version: 2
btw,根据我们做项目的经验,简单地做一次遍历替换操作风险是低的(只需要保证更新操作过程不会让出执行权)。热更新时复杂的往往是处理中间业务数据(数据的状态和引用关系是复杂的)。
在热更新要求高的项目中,我们约定,模块的设计应该偏好无状态。即模块逻辑与数据是分离的(比如以上下文的形式传入模块),热更新操作只需要处理模块的逻辑(新老Table的键值替换),来降低热更新操作的复杂度和风险。
1.本站内容仅供参考,不作为任何法律依据。用户在使用本站内容时,应自行判断其真实性、准确性和完整性,并承担相应风险。
2.本站部分内容来源于互联网,仅用于交流学习研究知识,若侵犯了您的合法权益,请及时邮件或站内私信与本站联系,我们将尽快予以处理。
3.本文采用知识共享 署名4.0国际许可协议 [BY-NC-SA] 进行授权
4.根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
5.本站是非经营性个人站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途
暂无评论内容