TeleportController 重构 · 第二次:流程命令化与 V2 拆分
本次挂在 CL856231(pending,2026-10-12):把传送编排从「大段回调 + 代次标志」改为「分阶段命令(Command)」驱动,并把 4371 行的 V2 表现单体拆成流程、组装器、命令与协作类。变更共 40 个文件:24 增、15 改、1 删。
1. 背景与目标
第一次拆分解决了「控制器太大」,但每条表现路径内部的阶段推进仍写成连续回调:成功、让位、失败、致命四种收口散落在各处,阶段之间缺少统一边界;V2 表现层 TeleportV2Flow.ts 单体达 4371 行,改一处要通读全文件。第二次重构做两件事:把阶段推进收敛为命令(单阶段只回答「成了没有」),把 V2 单体按职责拆开(流程持状态、组装器给计划、命令做阶段、协作类管资源与绑定)。
2. 文件变更总览
| 目录 | 新增 | 改动 | 内容 |
|---|---|---|---|
| Module/Teleport/Cmd/ | 10 | — | 命令基类 TeleportCommandBase、上下文 TeleportCmdContext 与轮次令牌 TeleportRoundToken,以及 Init / Casting / EntryCover / Streaming / Landing / Finalize 六个阶段命令与 SeqV1、Simplified 两套组装文件 |
| Module/Teleport/V2/ | 11 | — | 新 TeleportV2Flow(流程躯干)、TeleportFlowAssembler(阶段计划)、TeleportV2CmdContext、常量与阶段资源/等待,以及 AvatarBinder / Seq3Binder / CameraAnchor / EntryCover / FogControl 等协作类 |
| Module/Teleport/V2/Cmd/ | 3 | — | V2 三个阶段命令:TeleportLoadTransitionCmd / TeleportSequenceCmd / TeleportDownCmd |
| Module/Teleport/ | — | 9 | 门面 TeleportController 接入命令线与 V2 分流;TeleportTransitionFlow、TeleportSameSceneFlow、TeleportSequenceFlow、TeleportSeq3Binder、TeleportPresentation、TeleportGroundProbe、TeleportDefine、TeleportEntityBufferController 配合调整 |
| 技能 / World / SceneLoading | — | 6 | 上升/下降技能 LevelSkillLogicLightTeleportUp/Down(2);GameModeModel(1,仅注释);场景加载命令 SceneLoadingWaitTeleportCmdFinishedCmd / SceneWaitWpStreamingCmd / SceneLoadingCommandHelper(3) |
| Module/Teleport/ | — | — | 删除 旧单体 TeleportV2Flow.ts(4371 行) |
关键行数变化(本次实测):
| 文件 | 行数 | 说明 |
|---|---|---|
| TeleportV2Flow.ts(旧,已删) | 4371 | V2 表现单体,本次删除 |
| V2/TeleportV2Flow.ts(新) | 1647 | V2 流程躯干:状态与编排留在流程,具体工作下沉到协作类与命令 |
| TeleportTransitionFlow.ts | 985 | 默认线编排:持有阶段组装与 CommandExecutor,不再内联各阶段实现 |
| TeleportController.ts | 718 | 门面新增 V2 分流入口(UseTeleportV2Flow),较第一次重构后的 467 行上升 |
| TeleportSameSceneFlow.ts | 921 | 同场景流送与安全等待段 |
| Cmd/TeleportSeqV1Cmd.ts | 334 | SeqV1 完整链的阶段命令与组装器 |
| Cmd/TeleportSimplifiedCmd.ts | 201 | 简化段 Down / Load / Seamless 三段命令与组装器 |
3. 命令层:四出口与轮次身份
阶段命令统一继承 TeleportCommandBase<T>,只固定两件事:阶段名(日志前缀 传送-阶段[名])与四个收口出口——Complete(成功继续)、Skip(主动让位)、Abort(失败还原)、FatalError(致命终止)。四者都先判 Executing 才结算,避免迟到回执重复收口。
Succeed() 跳过继续」是两种语义。
命令之间通过 TeleportCmdContext 共享本轮状态(传送类型、参数、是否主控),并携带 TeleportRoundToken 做轮次身份隔离:每轮新建、取消后永不复用;异步等待抓到令牌后,回执到达时用 IsRoundValid 自检,不成立就 Skip 让位。命令只依赖受限接口 ITeleportStageFlow,不反向依赖流程实现——这也是它能在两套流程里复用的原因。
命令层的完整机制(四出口语义、上下文与令牌、InvalidateFlow 收口、异步「先监听再启动」)见 命令模式专栏 · 案例二 · 传送的 cmd,本文不重复。
4. 默认路径:SeqV1 完整链与 Simplified 简化段
默认线由 TeleportTransitionFlow 驱动,一个阶段一个 CommandExecutor:SeqV1 完整链用 SeqV1Executors: Set 记账,简化段用单个 SimplifiedExecutor。是否走 SeqV1 由 ShouldUseSeqV1Commands() 判定(非系统 Loading 且非淡入淡出传送)。各阶段组装命令如下:
| 线 | 组装入口 | 阶段 → 命令 |
|---|---|---|
| SeqV1 完整链 | TeleportSeqV1Assembler.BuildCommands(stage) | Up → Init / Casting / EntryCover;Load → LoadTransition / Streaming;Down → Sequence / Down / Landing / Finalize;ActiveUp → Casting / EntryCover |
| 简化段 | TeleportSimplifiedAssembler.BuildCommands(stage, IsSceneLoading) | Load → Cover(切图时仅此一条)/ Cover + Streaming;Down → Exit;Seamless → SeamlessStart |
每个 executor 设 EnterSeqBackstopTimeoutMs(40 秒)作为兜底超时,正常结束时在 finally 里 RemoveExpireTime() 撤掉——是「显式设超时 + 用后移除」的正面用法,不依赖框架默认(框架默认超时只打日志、不结算)。取消整轮时 InvalidateFlow() 会作废旧令牌、换新令牌,并对挂起的流送等待与仍在跑的 executor 分别兑现与 CommandBail()。
await ControllerHolder.QuestController.TeleportHookAsync() 等悬赏事件弹窗的用户选择。这处等待挂在阶段 40 秒兜底超时约束下(见第 7 节的 R1)。
5. V2 线:单体拆分
V2 表现(飞天遁地 / Seq 过场)是独立的第二条线,由 TeleportController 上的 UseTeleportV2Flow 开关接入(默认 false,即默认走上面的 SeqV1 / 简化线)。V2 线把旧单体拆开:
| 角色 | 文件 | 职责 |
|---|---|---|
| 流程 | V2/TeleportV2Flow.ts | 持本轮状态与编排入口:起播、加载、收尾、Tick、Seq3 剩余时长与相机转场 |
| 组装器 | V2/TeleportFlowAssembler.ts | ResolvePlan(type, isLocalPlayer) 由表现形式给出 Up / Load / Down 三段阶段计划;表现形式枚举 ETeleportPresentationForm:Seamless / BlackScreen / UiLoading / SeqV1 / SeqV2 / Simulated |
| 上下文 | V2/TeleportV2CmdContext.ts | V2 命令共享的本轮状态 |
| 命令 | V2/Cmd/Teleport{LoadTransition,Sequence,Down}Cmd.ts | V2 的加载过渡、序列、下降三段阶段命令 |
| 协作类 | V2/TeleportV2{AvatarBinder,Seq3Binder,CameraAnchor,EntryCover,FogControl,StageResources,StageWait}.ts + TeleportV2FlowConstants.ts | 角色绑定、Seq3 绑定、相机锚点、入口遮挡、雾控、阶段资源与等待、常量 |
TeleportFlowAssembler.ResolvePlan 与 ResolveForm 的注释明确写着「按运行期开关做替换或插入……待后续步骤接入运行期开关」「占位映射,待后续步骤改为读运行期配置与开关」。也就是说当前表现形式的推导是固定映射,尚未接到运行期开关 / 配置。
V2 过场衔接的时序(Seq2 循环退出到 Seq3 起播的 7 个阶段与实测耗时)见 V2 表现 · Seq2 → Seq3 衔接问题。
6. 保留的不变量
- 外部入口不变:调用方仍通过 ControllerHolder.TeleportController 使用原有公开方法,命令化没有改变门面签名。
- 默认路径不变:
UseTeleportV2Flow默认false,默认仍走 SeqV1 完整链 / 简化段。 - 表现分流不变:无缝、黑屏、系统 Loading、SeqV1 / SeqV2 与模拟端六种表现形式继续保留。
- 协议与安全收尾不变:同场景 LoadDone 语义、地面确认、遮罩所有权、Seq3 成组恢复等来自第一次重构的不变量继续成立。
- 日志口径不变:日志继续带
传送-前缀,阶段命令统一用传送-阶段[名],便于按同一关键词复测。
7. 验证状态与已知遗留
| 检查项 | 结果 | 说明 |
|---|---|---|
| 变更清单 | 40 文件 | CL856231(pending,2026-10-12):24 增 / 15 改 / 1 删 |
| 组装器运行期开关 | TODO | 表现形式推导暂为静态映射,运行期开关 / 配置接入待后续步骤 |
| PIE(编辑器内运行)功能回归 | 待测 | SeqV1 完整链、简化段、V2 主动传送与异常兜底路径均需回归 |
以下四项为代码审查阶段发现的遗留,不是本次重构本体的既定结论,均待修复 / 待确认:
| 编号 | 范围 | 问题 | 状态 |
|---|---|---|---|
| R1 | 默认线(SeqV1) | 悬赏事件弹窗的用户确认(TeleportHookAsync)挂在阶段内,可能被 40 秒兜底超时取消 | 待修 |
| R2 | 仅 V2 | TeleportV2CameraAnchor.EnsureHold 只置保持标记,缺少真实的 SwitchCamera(anchor, Level, reason) | 待修 |
| R3 | 仅 V2 | TeleportV2EntryCover.OpenBlack 的前置等待无轮次令牌校验 | 待修 |
| R4 | 仅 V2 | TeleportV2FogControl 丢失 Console 高优先级写入回退,仅走 SetCVarInt32('ShowFlag.Fog') | 待确认 |
除以上四项外,默认(非 V2)路径未发现 P0 / P1 级问题。