玩家用传送门从岛屿跨图回主城,Loading 卡死 90 秒。
根子在一个不起眼的地方:流送源 actor 被生在了"要被销毁的旧关卡"里。
access a null object → 命令卡死 90 秒。TS 里有个 全局单例 GameModeModel.TargetStreamingSourceInfo,它跨关卡不重建,一直活着。
但它管理的那个 流送源 actor,是靠 ActorSystem.Get → SpawnActorForEntity(当前World) 生出来的——actor 归属"当时所在的那个关卡",关卡没了它就没了。
✨ 永生对象持有短命对象的句柄 —— 经典的 悬垂引用 (dangling reference) 温床。
旧场景销毁命令 SceneLoadingDestroyOldSceneCmd 尝试回收流送源时打出:
① 路径带 Island001_WP:PersistentLevel → 流送源确实生在旧关卡里。
② Destroyed:true → 这俩 actor 已经被回收了。
这两个 被回收的流送源 就是你说的"几个人的 StreamingSource 都没了"。
Start Preload WP DataLayerCleanupWorld for Island001_WP / WorldPartition Uninitializepublic EnsureStreamingSource(): void { if (!this.StreamingSourceInternal) { // ← 只判 undefined,不判 IsValid this.CreateStreamingSourceDisabled(); // 悬垂句柄 → 跳过 → 留住死 actor } }
if (this.StreamingSourceInternal) { // truthy 但指向已销毁 actor const component = this.StreamingSourceInternal.GetComponentByClass(...); component.EnableStreamingSource(); // 💥 access a null object }
this.StreamingSourceInternal = ActorSystem.Get(BP_StreamingSource_C, transform)!; // Get 可能返回 undefined
CommandBase 的 try/catch 只打日志、既不 Fail 也不 Succeed,命令永远停在 Executing → 只能等 90 秒超时。
这把"1 帧崩溃"放大成了"90 秒黑屏卡死"。
根因是历史遗留:StreamingSourceInfo(2026-04-28)"永生单例持关卡级 actor + 不校验有效性"的写法,早就在那了,不是同步瞬移改造新写的。
但传送改造是放大器:改造在 BaseMoveSyncComponent、SceneLoadingController 等多处新增了 TargetStreamingSourceInfo.EnsureStreamingSource() 调用,让同一个全局单例在关卡内被反复启用/锁定,于是更容易在关卡销毁后把悬垂句柄留给下次跨图复用 —— 把这个老坑踩响了。
代码注释里本就有多处"污染全局单例 TargetStreamingSourceInfo 会卡死保底"的警告,说明这个单例的生命周期一直很脆。
EnsureStreamingSource 把 !StreamingSourceInternal 改成 !ObjectUtils.IsValid(...);UpdateStreamingState 取组件前也校验,失效就先重建。去掉 ActorSystem.Get(...)! 的非空断言。
DestroyStreamingSource 无论 ActorSystem.Put 成败,都要把 StreamingSourceInternal 清成 undefined,不给下次跨图留悬垂句柄。
CommandBase 异常分支置终止态,杜绝任何命令抛异常后死等 90 秒。独立于流送源问题,所有命令受益。