CHUAN2 DEV ENGINE
996 正版授权研发中心 · 360 授权合作教学中心 · 抖音传奇直播合作授权 · 快手推广运营商授权
OFFICIAL LICENSED ACADEMY 查验官方授权证书 →
// 威海旷世互娱教学基地 · 技术文章
进阶实战游戏功能996引擎

Lua懒删除:先做记号的延迟清扫

2026-10-02 01:41 作者:996 技术组 996引擎Lua教程传奇脚本进阶实战游戏功能996引擎

【语法算法】

先抛一个坑:删掉的一条数据,去哪了?听上去是句废话——删了就是没了。可账本一多就知道没那么简单:遍历到一半把成员从表里抠掉,下一项直接跳过去被漏掉;每删一条把后面的全部前移,删十次搬一千次。懒删除的答案是:删,先不做——盖一个"没了"的记号,查找时跳过它,攒到记号过半,一口气压实清扫。这篇把整套写法拆开:配置表、记号删除、跳过遍历、压实清扫、定时器、调试按钮,一段一段照抄能跑。

一、效果演示:先做记号的延迟清扫

演示场十格账本。点「删除一条」:随机一条数据当场打上黄叉——格子还在、数据还在,只是多了个记号,"记号"的小字挂在格子角上。连删几条,黄叉越攒越多,存活数往下掉。点「压实」:活的数据哗啦一声挤到前排,黄叉连着空格一起清掉——真删发生在这一拍——记号期的账全部结清,账本恢复到满员也干净的两头都硬。演示里试两笔账:记号期的一次遍历跳过黄叉照常走完,和立刻删除时遍历到一半表长变了的错乱——懒的那一拍,正是遍历最安全的那一拍。懒删除教的是延迟:不是不删,是把删挑个不疼的时候——遍历中的安全,就是这一延买来的。

mermaid
flowchart TD
A[删除请求] --> B[盖记号 不搬移]
B --> C[遍历时跳过记号]
C --> D{记号过半了吗}
D -- 否 --> C
D -- 是 --> E[压实 活数据前移 记号清空]
E --> C
demo
 fx-lazydel

二、底层原理:一次记号删除加一次压实清扫

模块的机关是一对搭档。记号删除:删除请求只改一个布尔——数据原地不动,遍历的表长不变,正在进行的循环不会被删穿。压实清扫:记号攒到过半的那一拍把活数据整体前移——搬移一次付清,记号全部清空,账本恢复满员的干净。和立刻删除的分野在"懒删除把搬移的账攒着付":立刻删是每删一次搬一次尾,删得勤搬得也勤;懒删是删十次搬一次,把搬移的开销从频率里拿出来,攒成一次性付清。

压实为什么等过半才做?删一条压一次的版本压实变成常态搬移,懒字白写;全满才压的版本记号堆成山,遍历净跳空格白耗——过半是一条"跳的成本快追上搬的成本"的平衡线,到了就收账——收早了懒名不副实,收晚了账本里全是记号。

三、核心代码:完整模块(上·骨架)

lua
-- @file LazyDelete.lua
-- 懒删除 —— 先做记号的延迟清扫

local LazyDelete = {}

local CONST = {
    SLOT_CAP      = 64,
    COMPACT_HALF  = true,

    AUTOINC_BASE  = 1145000,
}

local _slots     = {}
local _count     = 0
local _tombN     = 0

local function ShowTip(msg)
    if msg and msg ~= "" then SL:ShowSystemTips(msg) end
end

function LazyDelete.Count()
    return _count - _tombN
end

function LazyDelete.TombCount()
    return _tombN
end

function LazyDelete.Add(value)
    if _count >= CONST.SLOT_CAP then
        LazyDelete.Compact()
        if _count >= CONST.SLOT_CAP then
            ShowTip("账本真满了——压实也救不回")
            return nil
        end
    end
    _count = _count + 1
    _slots[_count] = { value = value, tomb = false }
    return _count
end

四、核心代码:完整模块(下·记号删除与压实清扫)

lua
-- 记号删除:只盖记号不搬移
function LazyDelete.Remove(idx)
    local slot = _slots[idx]
    if not slot or slot.tomb then return false end
    slot.tomb = true
    _tombN = _tombN + 1
    if CONST.COMPACT_HALF
        and _tombN * 2 >= LazyDelete.Count() then
        LazyDelete.Compact()
    end
    return true
end

-- 遍历:跳过记号,遍历中删除安全
function LazyDelete.Each(func)
    for i = 1, _count do
        local slot = _slots[i]
        if not slot.tomb then
            if func(slot.value, i) == false then
                break
            end
        end
    end
end

-- 压实清扫:活数据前移,记号连格清空
function LazyDelete.Compact()
    local write = 0
    for read = 1, _count do
        local slot = _slots[read]
        if not slot.tomb then
            write = write + 1
            if write ~= read then
                _slots[write] = slot
                _slots[read] = nil
            end
        else
            _slots[read] = nil
        end
    end
    _count = write
    _tombN = 0
    ShowTip("压实了——真删发生在这一拍")
end

SL:ScheduleOnce(function()
    SL:BindDebugButton("演示增删", function()
        LazyDelete.Add("账" .. math.random(100, 999))
    end)
    ShowTip("技能已加载: 懒删除 (调试按钮触发)")
end, 1.0)

function LazyDelete.Unload()
    _slots = {}
    _count = 0
    _tombN = 0
end

return LazyDelete

五、机制问答

问:遍历中调删除真的安全吗?
答:安全——记号删除不动表长不搬位,遍历的游标照常走,删的只是内容的心跳。

问:查找时谁被跳过?
答:盖记号的——遍历的口子里记号成员直接略过,查找的逻辑不用自己判,账本替它判了。

问:压实挑什么时候做?
答:过半即压是默认——也可挂定时压或停服压,原则只有一条:压实的搬移别赶上战斗的峰值。

问:压实期间新加的成员会丢吗?
答:不会——新增走尾部追加,压实的游标从头部往尾部写,写完尾部的新成员原地待着,两头互不打扰。

问:记号和删除标记有分别吗?
答:没分别——记号就是删除标记的口头叫法,重点不在叫什么,在"删了但没搬"这四个字——搬不搬,是懒和勤的分界线。

六、调参与实战怎么用

第四笔是压实的时机:过半即压是跳表成本和搬移成本的平衡线——全满才压的版本遍历净跳空格,删一压一的版本搬移成了常态,过半刚好卡在两头都不疼的位置。第三笔是遍历的豁口:遍历的跳过是记号的消费端——业务遍历一律走遍历口子不裸拿下标,裸拿下标的代码会一头撞上记号格。第二笔是新增的通路:尾部追加是压实的邻居——压实从头部写起、新增往尾部挂,两头的游标互不相见,这也是记号期能照常进货的原因——进货不停摆,压实才敢随时做。第一笔是计数的两本:总数和记号数分开记——存活数等于两数相减,混记一本的版本压实前后对不上账。常见坑三个:压实期间新增的成员被游标覆盖;遍历裸拿下标撞上记号格;压实后外部存的下标全部失效没人通知。

实战里懒删除是"边遍历边删"的通用答案:掉线成员的清理、过期加持的扫除、飘字到期的回收,全是同一套记号加压实的骨架。组里的约定是:凡是删除发生在遍历中的账,一律懒删不硬删——遍历的安全比删得利索值钱。这篇的模块照抄能跑,改的就是 CONST 那张表。

写完留一句给做账本系的同学:懒删除卖的是"删也要挑时候"——那一格一格的黄叉记号和压实一拍的整体前移,是把删除的开销从频率里摘出来的一次延迟清账,清得体面。

作者履历与出处

本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理。团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与商业版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。

← 返回文章地图返回研学路径

幂尔框架 · 实战干货 · 接口调用

LATEST ARTICLES

全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →

进阶实战游戏功能

Lua自动炮台:架一个自动索敌开火

【语法算法】 上一版的炮台索敌逻辑有个隐蔽报错:炮台永远打同一个怪——即使那只怪已经死了,索敌的游标也不挪窝。追到底索敌的游…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua元素转换:火伤转冰伤附带减速

【语法算法】 先抛一个坑:打不完的火系怪怎么办?火抗怪火打不动,换冰系技能要切装备要换面板——切完黄花菜都凉了。元素转换的答…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua连锁引燃:一个着火引燃下一个

【语法算法】 上个月的事故复盘会上有个数字被念了三遍:四成——策划写的是"同伴陪疼四成",代码落下去成了"陪疼四十点",两只…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua弹射地垫:踩上去弹飞到空中

【语法算法】 单行代码拆解:弹射初速=-420——弹射的全部动力就这一行的负初速。负号朝上、四百二十是弹射的初速大小——踩上…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua时间减缓:流速减半的局部时差

【语法算法】 上一版的减速类模块全按"乘以零点五"来写,帧率无关的版本照搬了这套写法——结果高帧率机上减速效果好,低帧率机上…

2026-10-02 04:50 996 技术组
进阶实战游戏功能

Lua引力球:丢出去把怪吸过来

【语法算法】 先抛一个坑:怎么把散在四处的怪聚到一起打?逐个拉是笨办法,一个范围技又只能打一片——引力球的答案是一个会动的吸…

2026-10-02 04:50 996 技术组