【语法算法】
一、一行代码的魔法
package.loaded["game/mediator/bag"] = nil——把这行打进调试控制台,再敲一次 require,磁盘上的新文件就生效了,游戏不用重启。很多人把这当成玄学,其实 require 的全部行为只有两步:查缓存,miss 才读文件。package.loaded 就是一张以模块名字符串为键的哈希表,值缓存着模块的返回值;把键置 nil 等于驱逐缓存,下一次 require 必然重新 loadfile、重新执行模块体、返回一张崭新的模块表。热更新的"热",全部来自这一行。
二、底层原理:require 的查找链与真正的难点
require 的查找顺序是 package.loaded 命中即返回、package.preload 次之、最后才按 package.path 找文件读入。也就是说模块只被"执行"一次,此后所有 require 拿到的是同一个表引用——Lua 模块系统天然的单例。驱逐之后难点不在加载新代码,在旧代码的交接:已经拿到旧模块表的地方,引用不会自动换成新表。
flowchart TD
A["require('bag')"] --> B{"package.loaded<br/>命中?"}
B -- "命中 --> 返回旧表引用<br/>(零成本单例)" --> C["调用方继续用旧逻辑"]
B -- "未命中(被驱逐) --> loadfile<br/>执行新模块体" --> D["新模块表<br/>写入 package.loaded"]
D --> E["Onloaded 重挂:<br/>重新注册通知/命令"]
E --> F["注意: 调用方若还攥着<br/>旧表引用 --> 新旧并存"]
F --> G["OnUnloaded 反注册:<br/>旧闭包全部撤下分发器"]
G --> A
三、正确写法:对称的卸载与重挂
local function reloadMediator(files)
for _, v in pairs(files) do
if package.loaded[v] then
package.loaded[v].OnUnloaded() -- 退场钩子: 反注册通知
package.loaded[v] = nil -- 驱逐缓存
end
local mediator = require(v)
mediator.Onloaded() -- 进场钩子: 重新注册
print("Reload File:" .. v .. ".lua")
end
end
local function reloadCommand(files)
for k, v in pairs(files) do
if package.loaded[v] then
global.Facade:removeCommand(k) -- 命令必须先反注册再重挂
package.loaded[v] = nil
end
global.Facade:registerCommand(k, require(v))
end
end
四、幽灵回调:热重载的头号事故
不写 OnUnloaded 直接驱逐会怎样?旧模块创建的回调闭包还挂在通知分发器上——闭包由分发器持有,GC 看不见"模块已驱逐"这件事。于是新模块的回调也在,旧的也在,一条通知触发两份逻辑:技能放一次扣两 CD、面板开一次注册两遍监听。这类 bug 的诡异之处在于它只在线上热更后出现,重启就消失,人称幽灵回调。解法就是上文的对钩子:驱逐前把旧模块注册进全局的一切(通知、命令、定时器)显式撤干净。
五、边界与收口
三件事驱逐不掉:upvalue 里捕获的旧模块引用、全局变量里存着的旧表、已创建对象身上挂着的旧 class 元表。所以热重载的适用边界是"行为逻辑层"——Mediator、Command、工具函数;持有大量长生命周期实例的管理层不要热更,重启成本反而更低。调试期把重载清单外置成 localReload.lua,个人清单不进版本库,reload 一次全组共享同一个心智模型:驱逐、交接、验证,三步缺一不可。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【语法算法】 闭包工厂的本质就这一行: 外层函数接收配置,返回一个闭包——闭包捕获配置,后续调用使用捕获的配置——闭包工厂的…
【语法算法】 闭包工厂的本质就这一行: 外层函数接收配置,返回一个闭包——闭包捕获配置,后续调用使用捕获的配置——闭包工厂的…
【语法算法】 泛型 for 的四种形态就这两行: 泛型 for 的四种形态覆盖了 Lua 所有的遍历需求——从无序遍历到有序…
【语法算法】 string.find 的起始偏移就这一行: 第三个参数 init 是搜索的起始偏移——从字符串的第 init…
【语法算法】 CPU 时间和墙钟时间的分界就这两行: os.clock 返回 CPU 时间——程序实际占用处理器的秒数——o…
【语法算法】 元方法 __unm 的触发就这一行: 对带 __unm 的表做一元负号操作 -t 时,Lua 调用 __unm…