异步选敌:TraceHandle完成不代表仍属于本次施法
自动关联目录:异步选敌:TraceHandle完成不代表仍属于本次施法
核心判断:
FTraceHandle只能证明某次 Chaos 查询可以取结果,不能证明这份结果仍属于当前能力尝试。异步选敌必须同时冻结“哪次施法”和“在哪个空间、用哪些参数查询”。
一、业务问题:选敌跨帧后,施法条件可能已经变化
范围技能用同步 Overlap 很直接,但同帧大量释放会把物理查询集中到 Game Thread。改成异步查询能平滑尖峰,却引入一帧以上的时间差:施法者可能移动、转向、取消,动态半径或过滤条件也可能变化,World 甚至已经退出。
问题不是“查询有没有完成”,而是完成时该采用哪一种语义:
- 快照语义:命中属于按键那一刻的空间和参数;
- 新鲜语义:提交前必须仍满足当前空间和参数,否则重查。
两者都可以,混用不可以。
二、实际实现:轮询模型合理,但 Handle 承担了过多语义
实际能力上下文只保存一个异步查询 Handle。第一次进入启动检查时,代码计算动态位置、形状和碰撞对象类型并发起 Overlap;组件把上下文放入异步队列,随后在 Tick 中再次进入完整启动检查。查询完成后写入目标、清空 Handle,再继续目标必需性和自定义条件检查。
这个设计有三点值得保留:查询队列由能力组件统一驱动;同步与异步结果复用同一过滤链;没有给每次查询散落一个长生命周期 Delegate。
缺口也很具体:
- Handle 没有 AttemptId、上下文代次和超时;
- 轮询前会重新计算动态参数,锥形查询虽保存了发起时 Transform,却用轮询时的角度、高度和长度处理旧 Overlap,形成混合快照;
- 重新进入完整启动函数,会再次执行实例策略和前置检查,而不是只推进“查询等待”阶段;
- 所检查的结束路径会清理活动实例和待取消项,但异步查询队列没有同等明确的关闭协议。
三、目标方案:把能力尝试建模为分阶段事务
struct FTargetQueryTicket {
uint64 AttemptId;
uint32 ContextGeneration;
TWeakObjectPtr<UWorld> World;
FTransform IssuedTransform;
FQueryParamsSnapshot Params;
FTraceHandle Handle;
double Deadline;
};
void Poll(FTargetQueryTicket& T) {
if (!IsCurrentAttempt(T.AttemptId, T.ContextGeneration) ||
T.World != GetWorld() || Now() > T.Deadline) {
Retire(T, EReason::Stale);
return;
}
FOverlapDatum Data;
if (!T.World->QueryOverlapData(T.Handle, Data)) return;
auto Targets = FilterAgainstSnapshot(Data, T.Params, T.IssuedTransform);
if (FreshnessPolicy == EFreshness::RequireCurrent &&
HasQueryInputChanged(T)) {
ReissueForSameAttempt(T.AttemptId);
return;
}
CommitTargets(T.AttemptId, MoveTemp(Targets));
}启动路径应拆成 Prepare -> AwaitQuery -> ValidateCurrentState -> Commit。Prepare 只执行一次;轮询只推进 Ticket;Commit 再检查冷却、打断关系和自定义条件。上下文回池或 EndPlay 先提升 Generation、退休队列,再释放上下文。即使底层查询不能取消,旧结果也已经失去提交权。
四、取舍与验证
快照语义响应稳定,但高速移动时视觉上可能命中旧位置;新鲜语义更符合“持续瞄准”,代价是重查和额外延迟。这个选择应成为能力数据,而不是查询实现的偶然结果。
验证至少覆盖:发起后转身或位移、动态范围改变、同一上下文连续激活、查询完成与取消同帧、World 结束、超时、查询失败,以及异步关闭后上下文立即回池。日志应关联 AttemptId、ContextGeneration、IssuedFrame、CompletedFrame、SnapshotDigest、TerminalReason。
五、反思
现有实现正确地把昂贵查询移出同步启动路径,却只异步化了 Chaos 工作,没有异步化业务状态机。引擎 Handle 是完成凭证;Attempt Ticket 才是结果归属凭证。