【语法算法】
一、一行代码的魔法
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 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 一、一次裸眼抓哑巴的潜行 烟幕弹玩法首版测试,玩家丢完烟幕就站在烟里发呆,怪在烟边上转圈也不进来——表现对了,…
【游戏功能】 一、一个震了个寂寞的大招 跺地震波类技能首版,特效华丽、震屏到位,测试却打回:"这大招怎么只炸了一下就没了?"…
【游戏功能】 一、一个卡进墙里的箱子 推箱谜题移植首版,测试发现一个无解局面:箱子被推到墙角,四个方向都推不动,谜题卡死只能…
【游戏功能】 一、一个永远差一口气的接线 星轨接电类旋转解谜首版,测试卡在第三关:四段线路怎么转都差一口气,明明视觉上头尾相…
【游戏功能】 一、一个被风卷走的判空 龙卷风聚怪技能首测,最灵异的 bug:怪被吸到风眼附近后集体"抽搐"——坐标每帧在风眼…
【游戏功能】 一、一个只闪不中的斩击 斩钢闪类突进斩首版被吐槽"人过去了刀没过去"——突进的位移做了,斩击的刀痕却只在终点画…