我在互联网上听到过关于能够像在Java、Erlang、Lisp等中那样在Lua中热交换代码的传言。然而,30分钟的谷歌搜索却一无所获。有人读过这方面的实质性文章吗?有人有做这件事的经验吗?它是在LuaJIT中工作,还是只在引用VM中工作?
我更感兴趣的是将该技术作为开发/调试的快捷方式,而不是实时环境中的升级路径。
Lua和大多数脚本语言都不支持您定义的最通用的"热交换"形式。也就是说,您不能保证更改磁盘上的文件,并使其中的任何更改自行传播到执行程序中。
然而,Lua和大多数脚本语言完全能够进行控制的形式的热交换。全局函数是全局函数。模块只需加载全局函数(如果您以这种方式使用它们)。因此,如果一个模块加载了全局函数,如果模块发生了更改,您可以再次重新加载该模块,并且这些全局函数引用将更改为新加载的函数。
然而、Lua和大多数脚本语言对此没有任何保证。正在发生的只是全球状态数据的变化。如果有人将旧函数复制到本地变量中,他们仍然可以访问它。如果模块使用本地状态数据,则模块的新版本无法访问旧模块的状态。如果一个模块创建了某种具有成员函数的对象,除非这些成员是从全局获取的,否则这些对象将始终引用旧函数,而不是新函数。等等
此外,Lua不是线程安全的;您不能只是在某个时刻中断lua_State
并尝试再次加载模块。因此,您必须设置一些特定的时间点,以便它检出内容并重新加载更改的文件。
所以你可以这样做,但它并没有得到"支持",因为它只是可能发生的。你必须为它工作,你必须小心你如何写东西,以及你在本地和全局函数中放了什么。
正如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
元表,您可以使其非常透明,而不必到处调用丑陋的getter。这也意味着,如果任何第三方库需要,您可以提供"一行"回调,然后实际上只是通过注册表进行尾调用。