我在互联网上听到过关于能够在 Lua 中热交换代码的传言,类似于在 Java、Erlang、Lisp 等中的做法。然而,30 分钟的谷歌搜索却一无所获。有没有人读过关于这个的任何实质性内容?任何人都有这样做的经验吗?它是在 LuaJIT 中工作还是仅在参考 VM 中工作?
我对该技术作为开发/调试的捷径比在实时环境中的升级路径更感兴趣。
Lua 和大多数脚本语言不支持您定义的最通用的“热交换”形式。也就是说,您不能保证更改磁盘上的文件并将其中的任何更改传播到正在执行的程序中。
然而,Lua 和大多数脚本语言完全能够控制热交换的形式。全局函数是全局函数。模块只是加载全局函数(如果你以这种方式使用它们)。所以如果一个模块加载了全局函数,如果模块改变了,你可以重新加载模块,这些全局函数引用将更改为新加载的函数。
然而,Lua 和大多数脚本语言对此不做任何保证。正在发生的只是全局状态数据的变化。如果有人将旧函数复制到局部变量中,他们仍然可以访问它。如果您的模块使用本地状态数据,则新版本的模块无法访问旧模块的状态。如果一个模块创建了某种具有成员函数的对象,除非这些成员是从全局变量中获取的,否则这些对象将始终引用旧函数,而不是新函数。等等。
此外,Lua 不是线程安全的。您不能只是lua_State
在某个时候中断 a 并尝试再次加载模块。因此,您必须设置一些特定的时间点才能检查内容并重新加载更改的文件。
所以你可以做到,但它不是“支持”的,因为它可能会发生。你必须为此而努力,你必须小心你如何写东西,以及你在本地和全局函数中放了什么。
正如 Nicol所说,语言本身并不适合您。
但是,如果您想自己实现类似的东西,那并不难,唯一“阻止”您的是任何“剩余”引用(仍将指向旧代码),并且事实require
将其返回值缓存在package.loaded
.
我这样做的方法是将您的代码分成3个模块:
main.lua
)data.lua
)payload.lua
),确保您不保留对它的任何引用(例如,当您必须对某个库进行回调时,这有时是不可能的;见下文)。-- main.lua:
local PL = require("payload")
local D = require("data")
function reload(module)
package.loaded[module]=nil -- this makes `require` forget about its cache
return require(module)
end
PL.setX(5)
PL.setY(10)
PL.printX()
PL.printY()
-- .... somehow detect you want to reload:
print "reloading"
PL = reload("payload") -- make sure you don't keep references to PL elsewhere, e.g. as a function upvalue!
PL.printX()
PL.printY()
-- data.lua:
return {} -- this is a pretty dumb module, it's literally just a table stored in `package.loaded.data` to make sure everyone gets the same instance when requiring it.
-- payload.lua:
local D = require("data")
local y = 0
return {
setX = function(nx) D.x = nx end, -- using the data module is preserved
setY = function(ny) y = ny end, -- using a local is reset upon reload
printX = function() print("x:",D.x) end,
printY = function() print("y:", y) end
}
输出:
x: 5
y: 10
reloading
x: 5
y: 0
您可以通过拥有一个“注册表模块”来更好地充实该逻辑,该模块为您跟踪所有需要/重新加载并抽象出对模块的任何访问(从而允许您替换引用),并且使用__index
元表该注册表可以使它变得非常透明,而不必到处调用丑陋的吸气剂。这也意味着您可以提供“单线”回调,然后实际上只是通过注册表进行尾调用,如果任何第 3 方库需要的话。