在Lua中热插拔代码。

我在网络上听到过一些关于能够类似于 Java、Erlang、Lisp 等语言中那样在 Lua 中进行热插拔编码的议论。然而,我花了 30 分钟在 Google 上搜索,却没有发现任何有意义的信息。有人读到过相关的实质性内容吗?有人有这方面的经验吗?这个技术只在 LuaJIT 上可行还是只在参考虚拟机中可行?

我更感兴趣的是这个技巧在开发/调试中的快捷方式,而不是在实际环境中的升级路径。

原文链接 https://stackoverflow.com/questions/9369318

点赞
stackoverflow用户734069
stackoverflow用户734069

Lua,以及大多数脚本语言,不支持您定义的最通用的“热交换”形式。也就是说,您不能保证更改磁盘上的文件,并使其中的任何更改传播到正在执行的程序中。

但是,Lua以及大多数脚本语言都可以进行可控制的热交换形式。全局函数是全局函数。如果您以这种方式使用它们,模块只需加载全局函数即可。因此,如果模块加载全局函数,则可以再次重新加载模块(如果它已更改),并且这些全局函数引用将更改为新加载的函数。

但是,Lua以及大多数脚本语言对此没有任何保证。所有的都是全局状态数据的更改。如果有人将旧的函数复制到了本地变量中,则仍然可以访问它。如果您的模块使用本地状态数据,新版本的模块将无法访问旧模块的状态。如果模块创建某种具有成员函数的对象,除非从全局获取这些成员,否则这些对象将始终引用旧函数,而不是新函数。等等。

此外,Lua不是线程安全的;您不能只中断某个点上的`lua_State'并尝试重新加载模块。因此,您必须设置某些时间点来检查并重新加载更改的文件。

因此,您可以这样做,但在某种意义上它并不“得到支持”。您必须为此付出努力,并小心编写内容以及全局和本地函数的放置。

2012-02-20 23:07:53
stackoverflow用户668125
stackoverflow用户668125

正如 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 --这让 `require` 忘记了它的缓存
  return require(module)
end

PL.setX(5)
PL.setY(10)

PL.printX()
PL.printY()

-- .... 某种方式检测到你想要重载:
print "reloading"
PL = reload("payload") -- 确保你不会在其他地方保留对 PL 的引用,例如函数 upvalue!

PL.printX()
PL.printY()
-- data.lua:
return {} -- 这是一个相当愚蠢的模块,它只是一个在 `package.loaded.data` 中存储的表,确保每个人在需要时都能获得相同的实例。
-- payload.lua:
local D = require("data")
local y = 0
return {
  setX = function(nx) D.x = nx end, -- 使用 data 模块被保留
  setY = function(ny) y = ny end, -- 使用本地将在重载时重置
  printX = function() print("x:",D.x) end,
  printY = function() print("y:", y) end
}

输出:

x:5
y:10
reloading
x:5
y:0

你可以更好地完善那个逻辑,通过一个 "注册表模块" 为你跟踪所有需要/重载的模块,并将模块的所有访问抽象出来(从而使你可以替换引用),并且使用该注册表上的 __index 元表,你可以使其几乎透明,而无需在各个地方都调用丑陋的 getter。这也意味着,如果第三方库需要,你可以提供“一行代码的”回调,然后实际上只是通过注册表进行尾调用。

2019-08-15 11:46:28