AI池化:复用 Actor,不能复用上一世

自动关联目录:AI池化:复用 Actor,不能复用上一世

核心判断:Actor Pool 节省的是构造、注册与复制初始化尖峰,不是一次普通的显隐切换。AI 回池前必须终止行为树、路径、Ability、Timer、感知注册和委托;出池后的任何旧回调都必须被 Incarnation 拒绝。

一、业务冲突:波次生成要平滑,复用又必须像新对象

PVE 波次可能在短时间内生成和销毁大量 AI。直接 Spawn/Destroy 会放大 Actor 构造、组件注册、Controller Possess 和网络初始复制的尖峰;池化能摊平这些成本。但只要复用对象还保留上一轮的目标、路径、效果或异步回调,性能优化就会改变玩法正确性。

二、实际实现:生命周期骨架完整,重置契约仍靠约定

通用池以精确 Class 建立固定预热队列,耗尽时不动态扩容;默认强制关闭,需要玩法显式启用。对象用复制的 CycleId 奇偶表示生命周期代际:出池先唤醒 Dormancy,再递增代际、执行 Awake 并 ForceNetUpdate;回池则递增代际、Reset/Sleep、ForceNetUpdate,最后进入 DormantAll。

Sleep 会关闭 Tick 和碰撞、重置网络角色并广播销毁语义,Awake 则恢复状态并广播创建语义。AI 的普通销毁路径还会先停止行为树并 UnPossess。这说明工程已经意识到“网络休眠顺序”和“AI 脑停止”是池化的一部分;不足在于 ResetAll 依赖各组件自觉实现,无法静态证明路径、感知、Ability、Timer 和委托都被覆盖。

stateDiagram-v2
    [*] --> Dormant
    Dormant --> Acquiring: wake dormancy + generation++
    Acquiring --> Configuring: reset identity/config
    Configuring --> Active: possess + register + force update
    Active --> Quiescing: death/despawn/abort
    Quiescing --> Dormant: cancel work + unpossess + reset + sleep
    Quiescing --> Quarantine: invariant failed
    Quarantine --> Dormant: full repair

三、值得保留:代际与 Dormancy 顺序已经进入底层

CycleId 比单纯 bInPool 更有价值:客户端能识别同一 NetGUID 对应的新一次生命。先 Flush Dormancy 再修改复制代际、回池 ForceNetUpdate 后再 DormantAll,也比直接隐藏 Actor 更接近正确的复制事务。固定池拒绝偷偷扩容,则让内存上限与容量不足显性化。

四、结构性缺陷:ResetAll 无法证明异步世界已经安静

关闭 Actor Tick 不会自动取消 TimerManager、PathFollowing、行为树 Task、感知 Stimulus、GameplayAbility、异步导航查询或外部委托。旧回调若只持有弱对象引用,Actor 仍然存活,弱引用依旧有效;它会在下一世修改新状态。只递增代际但回调不捕获并校验代际,同样没有保护作用。

固定预热池还存在另一种取舍:容量不足时直接失败会造成刷怪缺失,容量过大又让休眠 Actor、组件和复制元数据长期占用内存。精确 Class 分池也容易形成许多低利用率小池。池化是否值得,应由 Spawn/Destroy 峰值和常驻内存共同决定,而不是默认所有 AI 都入池。

五、目标设计:先静默,再重置,最后休眠

生命周期应拆成 Acquire -> Configure -> Possess -> Ready 与 Quiesce -> AbortWork -> Unpossess -> Reset -> Dormant。所有异步入口捕获 Incarnation;组件实现显式 PoolParticipant 接口,分别报告未清理任务。回池检查失败的对象进入隔离队列,不直接交给下一次 Spawn。池按 Archetype 而非任意派生 Class 规划,并记录命中率、耗尽率与常驻成本。

PoolHandle Acquire(ArchetypeId type)
{
    Actor& actor = Pools[type].Pop();
    actor.FlushDormancy();
    actor.Incarnation++;
    actor.ResetToBaseline();
    return {&actor, actor.Incarnation};
}

void Release(PoolHandle h)
{
    if (!h.IsCurrent()) return;
    h.actor->Quiesce();
    h.actor->AbortBehaviorPathAbilityAndTimers();
    h.actor->Unpossess();
    ensure(h.actor->AuditNoOutstandingWork());
    h.actor->Incarnation++;
    h.actor->EnterDormancy();
}

六、决策规则:池化收益必须覆盖重置复杂度

对象状态回池必须处理出池必须重建
AI 决策StopTree、清 Blackboard、解绑 ObserverBrain、初始状态、决策节拍
导航AbortMove、释放 NavLink Lease、清路径QueryFilter、Crowd 注册、目标请求
战斗Cancel Ability、移除实例 Effect、关命中窗属性基线、冷却策略、攻击实例
感知忘记 Stimulus、取消注册Sense 配置、阵营与过滤条件
UE 生命周期Timer、Delegate、Latent Action、TickPossess、碰撞、可见性、组件激活
网络ForceNetUpdate 后休眠、代际递增唤醒后先复制新代际与初始快照

真正可复用的不是“还活着的 Actor”,而是一个已经被证明静默、能重新建立完整身份的运行时容器。做不到这一点时,受控 Spawn 比不可靠的 Pool 更便宜。