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、解绑 Observer | Brain、初始状态、决策节拍 |
| 导航 | AbortMove、释放 NavLink Lease、清路径 | QueryFilter、Crowd 注册、目标请求 |
| 战斗 | Cancel Ability、移除实例 Effect、关命中窗 | 属性基线、冷却策略、攻击实例 |
| 感知 | 忘记 Stimulus、取消注册 | Sense 配置、阵营与过滤条件 |
| UE 生命周期 | Timer、Delegate、Latent Action、Tick | Possess、碰撞、可见性、组件激活 |
| 网络 | ForceNetUpdate 后休眠、代际递增 | 唤醒后先复制新代际与初始快照 |
真正可复用的不是“还活着的 Actor”,而是一个已经被证明静默、能重新建立完整身份的运行时容器。做不到这一点时,受控 Spawn 比不可靠的 Pool 更便宜。