递归遍历嵌套表(装备的强化树、行会的组织树)最大的风险是循环引用:A 引用 B、B 引用 A,普通递归永远出不来,脚本栈溢出直接崩。递归护栏给递归上两道闸:层数计数器(超过上限如 32 层强制返回,防止数据异常的超深嵌套)与已访问集合(visited 表记录进入过的表,再次进入即判定为循环引用并剪枝)。两道闸的成本各是一次计数与一次哈希查询,换来的是"任何输入都出得来"的保证。
递归护栏遍历器:层数上限、visited 剪枝、降级摘要三件套。示例代码如下:
local function deepWalk(tbl, visit, depth, maxDepth, visited)
depth = depth or 0
visited = visited or {}
if depth > (maxDepth or 32) then
return visit("层数超限", nil, depth)
end
if type(tbl) ~= "table" then
return visit(nil, tbl, depth)
end
if visited[tbl] then
return visit("循环引用", nil, depth)
end
visited[tbl] = true
for k, v in pairs(tbl) do
if type(v) == "table" then
deepWalk(v, visit, depth + 1, maxDepth, visited)
else
visit(k, v, depth)
end
end
end
业务接入:装备树遍历,叶子节点打印,异常节点降级为标记。示例代码如下:
local function dumpGear(actor, gearTree)
actor = getplayerbyname(actor)
deepWalk(gearTree, function(k, v, depth)
local pad = string.rep(" ", depth)
if k == "循环引用" or k == "层数超限" then
sendmsg(actor, 1, pad .. "[" .. k .. "]")
else
sendmsg(actor, 1, pad .. tostring(k) .. " = " .. tostring(v))
end
end)
end
防护成本对照:500 节点的装备树(无循环)遍历,裸递归 0.6ms,防护版 0.9ms(多出 50% 的检查开销)——防护是保险不是免费。循环引用场景:裸递归栈溢出脚本中断;防护版在第 33 层返回"层数超限"、在循环引用处返回剪枝标记,脚本继续运行。visited 表内存与节点数成正比,500 节点约 40KB,遍历结束即释放。
数据来源可控(自己构建的配置树、保证无环)时,visited 剪枝可以省略只留层数上限;处理外部输入或玩家构建的数据(任何人都可能塞进自引用),两道闸缺一不可。层数上限的取值按业务真实层级的两倍到三倍定(装备树实际最多 6 层,上限 32 已很宽裕)——上限设得越大,异常数据浪费的遍历越多。另外,visited 会把"同一张表被合法引用两次"也剪掉,共享子树的遍历要改用计数访问。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
设计初衷 安全区村庄只是传送与摆摊的功能集散地:NPC 站桩、玩家路过、两不相干。村庄共生设计:让村庄与玩家互相需要——玩家…
设计初衷 高战玩家的经验只在小圈子里口口相传,新手想学连门路都没有;师徒系统绑定太重,轻量的武学传授没有载体。玩家武馆设计:…
设计初衷 服务器的日常是可预期的循环:刷怪、活动、攻城——缺乏全员共同面对的异常时刻;既有的世界事件又只是打怪换皮。天灾事件…
底层原理 给地图据点分阵营、要求相邻据点不同阵营——这是图的染色问题。二分染色是最简单的情形:从任一点出发做 BFS 逐层染…
底层原理 常规表一键一值;"一个玩家持有多件装备""一个行会包含多名成员"是一对多关系——多重映射(multimap):每个…
业务场景 沙巴克战后残血只能灌药慢慢回;NPC 医师的一次性治疗要么免费无成本、要么收费高到劝退。帮会医师封装:帮会驻地常驻…