- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
182 lines
15 KiB
Markdown
182 lines
15 KiB
Markdown
# lifecycle.bootstrap_phase_host
|
||
|
||
## Scope
|
||
|
||
对比 40250 的应用启动、单帧处理顺序、网络阶段属主、Loading 入口清理、DEAD/CLOSE、窗口焦点/后台和最终销毁,与当前 Godot `client_main`、`AppFlow`、`AppLifecycle`、`M2Client` 和场景树行为。
|
||
|
||
## Reference call chain
|
||
|
||
### 启动与每帧
|
||
|
||
- `UserInterface/PythonApplication.cpp::Run`
|
||
- `UserInterface/PythonApplication.cpp::Process`
|
||
1. `ELTimer_SetFrameMSec` / `CTimer::Advance`
|
||
2. `CPythonNetworkStream::Process`
|
||
3. guild mark uploader/downloader、account connector
|
||
4. keyboard/mouse input
|
||
5. camera、resource manager、camera update、mouse update、UI update
|
||
- `UserInterface/PythonApplicationEvent.cpp`
|
||
- `UserInterface/PythonApplicationProcedure.cpp`
|
||
|
||
### 阶段属主与清理
|
||
|
||
- `UserInterface/PythonNetworkStream.cpp::RecvPhasePacket`
|
||
- `PHASE_CLOSE -> ClosePhase -> SetLoginPhase`
|
||
- `PHASE_HANDSHAKE -> SetHandShakePhase`
|
||
- `PHASE_LOGIN -> SetLoginPhase`
|
||
- `PHASE_SELECT -> SetSelectPhase`
|
||
- `PHASE_LOADING -> SetLoadingPhase`
|
||
- `PHASE_GAME -> SetGamePhase`
|
||
- `PHASE_DEAD` 只消费阶段包,不改变网络阶段
|
||
- `UserInterface/PythonNetworkStreamPhaseLogin.cpp::SetLoginPhase`
|
||
- `UserInterface/PythonNetworkStreamPhaseSelect.cpp::SetSelectPhase`
|
||
- `UserInterface/PythonNetworkStreamPhaseLoading.cpp::SetLoadingPhase`
|
||
- `CPythonPlayer::Clear`
|
||
- `CFlyingManager::DeleteAllInstances`
|
||
- `CEffectManager::DeleteAllInstances`
|
||
- direct-enter 状态初始化
|
||
- `UserInterface/PythonNetworkStreamPhaseGame.cpp::SetGamePhase`
|
||
- `UserInterface/PythonNetworkStreamPhaseGame.cpp::__LeaveGamePhase`
|
||
- PVP key、network actor、combo flag、character manager、item manager
|
||
- `UserInterface/PythonNetworkStreamPhaseGameActor.cpp::__ClearNetworkActorManager`
|
||
|
||
### 关闭与销毁
|
||
|
||
- `EterLib/MSApplication.cpp::MessageProcess`
|
||
- `EterLib/MSWindow.cpp::WindowProcedure/Destroy`
|
||
- `UserInterface/PythonApplicationProcedure.cpp::WindowProcedure`
|
||
- `WM_ACTIVATEAPP`: 保存/恢复音量;全屏窗口最小化/恢复
|
||
- `WM_CLOSE`: debug 退出,release 走 `RunPressExitKey`
|
||
- `UserInterface/PythonApplication.cpp::Destroy`
|
||
- UI、事件、飞行物、角色、物品、背景、特效、网络、资源、声音、设备按显式顺序销毁
|
||
|
||
## Current call chain
|
||
|
||
### 启动与每帧
|
||
|
||
- `project/client_main.gd::_ready`
|
||
- `AssetPack.ensure`
|
||
- 创建 `AppFlow`
|
||
- `project/app_flow.gd::start`
|
||
- 创建并挂载一个 `M2Client`
|
||
- 创建 `LoadingScreen`、`AppLifecycle`、`ReconnectUI`
|
||
- 进入 Login
|
||
- `extension/src/net/m2_client.cpp::_process/net_poll`
|
||
- 依次 pump classic/auth/game/mark 连接
|
||
- classic 侧再转发 `EntityStore` 变化和 Godot signals
|
||
- `project/game_scene.gd::_process`
|
||
- 主角同步、诊断、音频 listener/环境音
|
||
- `project/app_flow.gd::_goto_game`
|
||
- 以协程构造 GameScene;等待模型时只让出帧,不夺取 `M2Client._process` 的网络 pump owner
|
||
- `project/login.gd`
|
||
- 保留一条独立的 SceneTree 入口:自行创建 `AppLifecycle`、`M2Client`、登录层和 `GameScene`
|
||
- 不经过 `client_main.gd -> AppFlow`,因此与主入口不是同一套阶段、关闭和销毁属主
|
||
|
||
### 阶段属主与清理
|
||
|
||
- `extension/src/net/classic/classic_session.cpp::on_phase`
|
||
- LOGIN/SELECT/LOADING/GAME 映射到 `INetSession::Stage`
|
||
- `PHASE_CLOSE` 直接映射为 `Stage::Failed`
|
||
- `PHASE_DEAD` 没有 case,仅被前置的 time-sync 判断覆盖,未改变 stage
|
||
- `extension/src/net/m2_client.cpp::pump_classic`
|
||
- Stage 变化才发 `phase_changed`
|
||
- stage 表只有 close/handshake/login/select/loading/in_game/failed,没有 dead
|
||
- `EntityStore` 的实体、死亡、掉落、地图 warp 变化在同一 pump 中派发
|
||
- `project/ui/loading_screen.gd`
|
||
- 只按 `phase_changed` 显示/隐藏 Loading 遮罩
|
||
- `project/game_scene.gd::_on_world_reset`
|
||
- 清 NetPlay、NetWorld、掉落、弹道、临时 UI 和 BGM
|
||
- 由 `M2Client` 的 classic `on_loading_phase` 在保留旧 world 时触发;空 world 或已由 GC_WARP 预清理的连接不重复触发
|
||
- `project/net_play.gd::clear_for_map_change`
|
||
- `project/net_world.gd::clear_for_map_change`
|
||
- `extension/src/net/entity_store.cpp::reset_for_map_change`
|
||
|
||
### 窗口与后台
|
||
|
||
- `project/app_lifecycle.gd::_notification`
|
||
- application paused/resumed 时保存/静音/恢复音频、暂停/恢复 `M2Client` 和 SceneTree
|
||
- focus 只发 `focus_changed`,没有 40250 的音量/全屏处理
|
||
- `NOTIFICATION_WM_CLOSE_REQUEST` 只发 `close_requested`
|
||
- `AppFlow` 与旧 `login.gd` 分别消费各自 `AppLifecycle.close_requested`,关闭时按网络→音频解绑→场景释放→进程退出顺序协调;两条入口仍不是同一个启动拓扑。
|
||
|
||
### 销毁
|
||
|
||
- `AppFlow::_goto_login` 对 GameScene/UI 使用 `queue_free`
|
||
- `M2Client::~M2Client` 为 default;连接由成员 RAII 析构关闭
|
||
- 未发现与 40250 `CPythonApplication::Destroy` 对应的、可观测且有顺序保证的网络/实体/特效/UI/音频总清理函数
|
||
|
||
## Branch matrix
|
||
|
||
| 场景 | 40250 | 当前端 | 结论 |
|
||
|---|---|---|---|
|
||
| 启动/正常帧 | 固定在 `Process` 中先计时、网络,再输入、资源、相机和 UI | Godot 由 `M2Client._process` 持有网络 pump;`_goto_game` 等待模型只 yield 并同步视图 | PARTIAL:跨节点顺序和模型构建期间的完整事件时序仍未证明一致 |
|
||
| 主入口/旧入口 | `CPythonApplication` 只有一个启动和销毁拓扑 | `client_main.gd -> AppFlow` 与旧的 `login.gd` 各自创建生命周期、客户端和游戏场景 | GAP:主入口测试不能证明旧入口也遵守单一生命周期属主 |
|
||
| LOGIN/SELECT | `RecvPhasePacket` 作为唯一阶段 owner,阶段切换先执行旧 phase leave,再安装新 process/leave 回调 | `ClassicSession::Stage`、`M2Client` signals、`AppFlow` 三层分担 | PARTIAL:状态 owner 不唯一,乱序和重复 phase 规则未统一 |
|
||
| SELECT -> LOADING | `SetLoadingPhase` 先切 phase,再清 player、飞行物、特效和 direct-enter 状态 | classic `on_loading_phase` 在阶段回调内先清旧 `EntityStore` 并发 `world_reset`,再继续 Loading;空 world 直接跳过 | PARTIAL:NetPlay/NetWorld/特效的完整 owner 顺序和真实 server 包序仍待补齐 |
|
||
| GAME -> DEAD | 40250 的 `PHASE_DEAD` 本身不改 phase,死亡包在 Game phase 内走 GameOver/角色死亡链 | `ClassicSession` 不发 dead phase;`DeathUI` 现在只从 `GC_DEAD -> entity_dead` 进入,和 40250 的死亡包入口一致 | PARTIAL:死亡入口已闭环,GameOver/决斗例外、死亡动作和真实包序仍待补齐 |
|
||
| PHASE_CLOSE | 回调 `SetLoginPhase`,由登录阶段继续处理 | `PHASE_CLOSE -> Stage::Failed`,再发 `login_failed` 或 `disconnected` | GAP:状态和 UI 入口不同 |
|
||
| GC_WARP | 进入 Loading,显式清旧 actor/物品/特效,再连接/加载新地图 | `EntityStore::reset_for_map_change -> world_reset -> connect_warp`,并由 Loading 回调的空 world guard 避免重复 reset | PARTIAL:NetPlay/NetWorld/特效的完整 owner 顺序和真实 server 包序仍待补齐 |
|
||
| 断线重连 | old phase leave、连接状态和角色数据按网络 owner 协调 | `ReconnectUI` 负责重试,`M2Client` 重新建/切 session;场景可能保留,world reset 不是统一前置 | PARTIAL;与 `network.reconnect_warp` 交叉记录 |
|
||
| 窗口失焦/后台 | Windows `WM_ACTIVATEAPP` 保存/恢复音量并处理全屏,主循环仍由应用控制 | focus 仅广播;application pause 会暂停 socket/SceneTree 并静音 | PARTIAL / platform adaptation:行为不是 1:1 |
|
||
| 窗口关闭 | `WM_CLOSE` 经退出键/退出流程,最终 `Destroy` | `AppFlow`/旧 `login.gd` 唯一消费 `close_requested`,先断开并解绑资源后退出;Windows 退出确认/保存细节仍未证明 | PARTIAL |
|
||
| 最终销毁 | 明确 manager 顺序、网络/声音/设备显式 Destroy | Godot `queue_free` + C++ default destructor/RAII | PARTIAL:资源最终可能释放,但副作用顺序与可观测状态未等价 |
|
||
|
||
## Equivalence matrix
|
||
|
||
| 项目 | 状态 | 证据 |
|
||
|---|---|---|
|
||
| Preconditions | PARTIAL | 当前入口有资源、扩展和 serverlist 检查,但没有统一阶段/关闭前置条件 |
|
||
| Branch structure | PARTIAL | LOGIN/SELECT/LOADING/GAME 已映射;DEAD、CLOSE、窗口关闭和重复启动/关闭缺统一分支 |
|
||
| Algorithms/formulas | MAPPED | 主循环没有业务公式;计时和网络 pump owner 不同 |
|
||
| State transition order | PARTIAL | 参考由 `RecvPhasePacket + phaseLeaveFunc` 单 owner 串行;当前由 session/stage/signal/AppFlow 分散推进 |
|
||
| Constants/units | PARTIAL | 1500ms enter-game 等已有实现,但 frame/timer、Loading 清理和重连窗口未证明相同 |
|
||
| Timing/event sources | PARTIAL | 当前有 Godot `_process`、signal、协程和模型同步;网络 pump 已收敛到 M2Client owner,参考是 Windows message + phase callback |
|
||
| Resource/data sources | PARTIAL | 当前资源由 scene/RAII 管理;参考是各 manager 显式拥有,且正常 Loading 与 close 清理源不同;`client_main` 与旧 `login.gd` 还存在两套资源/生命周期装配源 |
|
||
| Protocol side effects | PARTIAL | LOGIN/SELECT/ENTERGAME/warp 已有路径;CLOSE、DEAD 和 loading reset 的副作用不等价 |
|
||
| Interruption/failure/cleanup | PARTIAL | 断线/warp/关闭消费者已有部分回归;普通重连 reset、重复关闭、socket 失败和 final destroy 的完整顺序仍缺证据 |
|
||
|
||
## Evidence and regression boundary
|
||
|
||
`project/app_flow_lifecycle_test.gd` 已验证:AppFlow 只创建一个 AppLifecycle、登录/游戏切换不重复创建、游戏断线保留场景并按原槽位重进、显式槽位不存在时不错误自动选人,并且桌面关闭信号有唯一消费者。`project/test_death_restart_parity.gd` 另外验证 `PHASE_DEAD` 不作为死亡面板入口,`GC_DEAD -> entity_dead` 才打开 RestartDialog。`project/gamescene_test.gd` 和 `project/channel_status_test.gd` 只提供场景装配/频道状态旁证。它们没有证明 40250 的固定每帧顺序、Loading 清理、DEAD/CLOSE 的完整副作用、旧 `login.gd` 入口或最终销毁等价,因此本合同不能升级为 MAPPED。
|
||
|
||
## Required tests
|
||
|
||
- 记录并断言 `M2Client._process`、GameScene/UI `_process` 和 loading 协程的同帧顺序,禁止等待模型时重入 pump 造成重复 phase/entered_game。
|
||
- LOGIN/SELECT/LOADING/GAME/DEAD/CLOSE 的正常、乱序、重复和断线状态矩阵;特别断言 `PHASE_DEAD` 的消费和 `PHASE_CLOSE` 的目标状态。
|
||
- SELECT -> LOADING、GC_WARP、普通重连三条路径分别验证 actor、飞行物、特效、NetPlay 动作、掉落、临时 UI、BGM 和 `EntityStore` 的清理顺序。
|
||
- focus out/in、application pause/resume、重复 pause/resume、窗口 close、网络失败和重复 close 的音频、socket、SceneTree、退出状态测试。
|
||
- GameScene queue_free、M2Client 析构、旧 session 关闭和再次启动的资源/信号泄漏测试。
|
||
- 分别从 `client_main.tscn` 和旧 `login.gd` 启动,验证 AppLifecycle、M2Client、GameScene、音频和 close 信号没有重复属主或悬空连接。
|
||
|
||
### Active UserInterface application/security review
|
||
|
||
- `UserInterface/AbstractApplication.h` 把鼠标、全局时间、服务器时间、中心位置、事件/默认相机和 IME update/tab/return/code-page/candidate/reading callbacks 作为应用宿主接口;这与当前仅由 Godot notification/signal 分散处理不是同一 host contract。
|
||
- `UserInterface/PythonApplicationModule.cpp` 暴露应用 process/update/render FPS、相机参数、硬件/软件光标、连接数据、文本 loader 和 pack 读取等 Python 边界;`PythonApplicationLogo.cpp`、`PythonApplicationWebPage.cpp`、`MovieMan.cpp` 管理视频/网页/媒体 surface 的创建、更新、跳过、淡出和释放。
|
||
- `PythonSystem.cpp`/`PythonSystemModule.cpp` 以 D3D adapter/resolution/frequency、窗口状态、音量、阴影级别、保存 ID、配置文件和 interface status 为权威;当前 `AppLifecycle`/系统设置只覆盖 Godot 的一部分状态,Windows display mode、旧配置格式和媒体资源清理没有等价证据。
|
||
- `ProcessCRC.cpp`/`CheckLatestFiles.h`、`ProcessScanner.cpp`、`PythonExceptionSender.cpp`、`HackShield`/`NProtectGameGuard`/`WiseLogicXTrap` 构成参考端完整性、进程扫描、安全组件和异常上报链。当前客户端没有对应 Windows 安全/CRC/异常服务链;这些是明确的部署能力差异,不应被普通启动测试掩盖。
|
||
- 本轮完成上述 UserInterface 应用/安全活跃文件静态核对,并修正 DeathUI 对不可达 `phase_changed("dead")` 的错误依赖;当前端基础 AppFlow 可运行,但 bootstrap/关闭/安全/发布合同继续为 `PARTIAL`。
|
||
|
||
## Status
|
||
|
||
`PARTIAL`。本轮完成了 40250 参考调用链与当前实现调用链的逐分支审计,并修正了 DeathUI 对不可达 `phase_changed("dead")` 的错误依赖、classic Loading 阶段缺少旧 world 清理入口、主入口/旧入口遗漏关闭消费者,以及 `_goto_game` 等待模型时重复夺取网络 pump 的问题;DEAD/CLOSE 其余状态语义、focus/后台、旧入口分叉和显式销毁链仍存在未闭合差异,因此不能宣称已全部按 40250 修复。
|
||
|
||
## Implementation round 2026-09-21T04:20Z — close request ownership
|
||
|
||
本轮针对 40250 `PythonApplicationProcedure::WindowProcedure(WM_CLOSE)` 和
|
||
`CPythonApplication::Destroy` 的关闭入口补齐 Godot 两条启动拓扑:
|
||
|
||
- `AppFlow.start` 将唯一的 `AppLifecycle.close_requested` 连接到 `_on_close_requested`;处理器具备幂等锁,先调用 `M2Client.disconnect_from_server()`,再解绑生命周期持有的音频、释放 GameScene,最后退出 SceneTree。
|
||
- 旧 `login.gd` 自己创建 `AppLifecycle`,因此也接入同样的断开/解绑/场景释放/退出顺序,避免只修 client_main 主入口。
|
||
- `app_flow_lifecycle_test.gd` 新增失败回归:没有消费者时失败,修复后验证 AppFlow 连接了唯一关闭处理器;Godot editor/headless 脚本加载检查通过。
|
||
|
||
仍保持 `PARTIAL`:Windows 退出确认与保存细节、真实 socket close 回调顺序、重复 close 与最终 native manager 销毁尚未由运行时 fixture 完整证明。
|
||
|
||
## Implementation round 2026-09-21T04:25Z — frame pump ownership
|
||
|
||
本轮按 40250 `CPythonApplication::Process` 的单一网络处理 owner 修复进入游戏等待段:
|
||
|
||
- 删除 `AppFlow::_goto_game` 等待 `_model_built` 时的直接 `client.net_poll()`;每次循环只同步模型并让出一帧,由 `M2Client._process` 继续负责唯一网络 pump。
|
||
- 失败回归在 `app_flow_lifecycle_test.gd` 中检查 `_goto_game` 源码段不得重新调用 `client.net_poll()`;修复后生命周期、GameScene 和 world reset 回归均通过。
|
||
|
||
仍保持 `PARTIAL`:GameScene 内部模型构建仍有传入 pump callable 的资源加载适配,完整跨节点同帧顺序、阶段重复/乱序包和 entered_game 重入矩阵仍需运行时 fixture。
|