Files
orisys-app/apps/studio
ykcheungab d06e116c15 fix(studio-3d): 取消 epoch —— 停止/隐藏/换会话之后,已在跑的后台合成不能再把旧测量放回屏幕
- What:
  1. `ThreeDCoordinator` 新增 **epoch**(显示声明代次)与 `_Inflight` 记录:`work()` 不再直接写
     `_composed` mailbox,只 `return` 合成好的帧;结果在 GUI 线程的 `_collect_inflight()` 里被接受,
     且必须同时满足 `item.epoch == self._epoch` 与 `_identity_is_current(identity)` 才会入队。
     `activate()`/`begin_session()`/`set_visible(False)`/`show_rest()`/`close()` 统一走新的
     `_invalidate()`:epoch+1、清空两块 mailbox、清 `_last_request_id`/`_last_composed_identity`/
     `_last_field_frame`/`_settings_dirty`,并**尝试**取消 future。
  2. **未成功取消的 future 仍占用唯一合成槽**:`_inflight` 只在 `future.done()` 时由 `_collect_inflight()`
     释放,所以反复隐藏/显示不会在线程池里排起第二个合成;`CancelledError` 单独吞掉(取消不是失败,
     不产生诊断),其它异常仍记成 `field_unavailable` 诊断(文案由 "compose raised" 回到 "compose failed")。
  3. **request_id 单调**:`submit()` 拒绝小于本 epoch 已见值的 request_id(>0 时)并计 `stale` + 给
     `stale_frame` 诊断;`_identity_is_current()` 同样比较,所以"晚到的旧结果"也进不了屏幕。
  4. 新增 `ThreeDCoordinator.epoch` 与 `last_presented()`(帧号 + 单调时刻,供后续面板帧计数用);
     构造参数新增关键字 `clock`(默认 `time.monotonic`)与 `stale_grace_s`(默认 1.0,本提交尚未使用)。
- Why: 审查报告 P1/R1 的第一条。`Future.cancel()` 无法中断已经开始运行的函数,而旧代码在
  `_cancel_inflight()` 里就把 `_inflight` 置空并只比较 generation —— 于是"停止显示"之后旧 work 结束仍然
  `offer` 进 `_composed`,下一次 tick 取到它并把状态改回 live(审查用可控后台任务复现:
  `valid_after_show_rest=false` → 放行旧 compose → `valid_after_old_worker_finishes=true`、`state=live`)。
  同一个洞也适用于"停止重算后定时器仍继续 tick"的场景。把接受点移到 GUI 线程后,"发布前校验"和
  "接收时校验"是同一处代码,epoch 让"这一刻的显示声明"成为可比较的事实。
- Verification:
  · `test_scene3d_coordinator.py` +7(24 通过):合成已进入后被 `show_rest`/`set_visible(False)`/
    `begin_session` 撤销 → 旧结果永不 present 且 state 不为 live、epoch 递增、槽位在 work 真正结束后释放;
    `ManualExecutor` 复现"提交后立即取消" → `CancelledError` 不产生诊断且槽位可复用;
    20 轮 begin_session→submit→tick→show_rest 循环里最后一个画面上永远没有有效测量、`max_in_flight==1`;
    request_id 逆序在入口被拒(`stale` + `stale_frame`)且画面停在较新的请求上,
    "较旧请求的结果晚到"也在接收侧被丢弃。
  · 审查复现脚本对照(`.repro/probe_stage6_r1.py` → `probe_stage6_r1.json`):
    `stopped_frame_returns` 由 `{valid_after_old_worker_finishes: true, state: "live"}` 变为
    `{false, "no_data", epoch_bumped: true, slot_free_again: true}`;
    `request_id_not_checked` 由 `{old_submit: "accepted", shown_request: 1}` 变为
    `{old_submit: "stale", shown_request: 2, diagnostic: "stale_frame"}`。
  · apps/studio 全量 pytest 退出码 0(含真 GL 冒烟用例)。
- Not in this change: `stale_grace_s`/`clock` 只落地参数与 `last_presented()` 的记录,**失效状态的迁移
  (`stale` 状态、宽限到期回参考模型)留给下一提交**;adapter 侧的字段级有效性、设备几何身份、
  资产加载请求号、二维后端统一均未动;审查报告里那句"用 `coordinator._inflight` 当 Future 用"的复现
  脚本无法再原样运行(槽位现在是 `_Inflight` 记录),改用上面那份等价探针。
2026-09-14 20:53:38 +08:00
..
删除深度重算引擎;修好回放视频每帧重开文件导致的无法播放,运输控制改到状态轨主按钮 - What: 1) 彻底删除深度重算引擎:shared/.../record/depth.py(DepthRecomputer/DepthResult)与 depth_backends.py(BoxReplay/FingerReplay)、其专属测试 test_depth_replay.py,record/__init__ 不再导出,playback.py::replay_warnings 不再报 depth_recomputed;apps/studio/replay.py 由 515 行瘦身为只做读盘+游标+视频帧+提示,回放视图的 2D 面板只提供「原始图像」;session.json 的 depth.replay 由 recompute 改为 none;删除 31 个只剩深度使用的文案键(depth_*/warn_state_*/warn_reset_*),warn_config_changed 与 warn_sdk_changed 改为描述"由视频重算";ui_check 深度段、studio 两个深度用例、test_playback 三例、drive_shared 的 test_depth_replay 与 reproduce.py 的深度段一并清理(shared 套件 150→134 项)。2) 无法播放的根因是 RecordingReader.video_frame_at 每帧都 cv2.VideoCapture 重开并 seek:小片段已 ~10 ms/帧,1080p H.264 每次 seek 需从关键帧解码(上百 ms),而播放每 33 ms 要一帧且全在 UI 线程上,视频因此看着不动;改为按分段缓存解码器、顺序帧用 read() 步进(实测 10 ms → 0.5 ms/帧)、跳转才 seek,新增 RecordingReader.close() 释放。运输控制按用户建议上移到状态轨主按钮:toolbar.set_run_role("playback") 使回放视图的主按钮变成「▶ 回放」/「⏸ 暂停」(实时视图仍是「▶ 运行」),点按以 1× 推进游标(墙钟计时,漏拍不变慢)、到末尾自动停,拖动游标/框选/滚轮/拖总览条经 ForcePlotsPanel.timeline_taken_over 自动暂停,空格键同效;曲线表头不再放第二个播放按钮;按钮样式在回放角色下不再套用"停止"的红色语义。 - Why: 用户要求先清掉深度重算引擎、并反馈"现在无法播放视频",建议把上方播放按钮在历史回放形式下改为回放。 - Verification: ui_check.py 554 项 0 失败(新增 seek<250ms/步进<60ms 的解码预算、回放按钮文字随播放/暂停切换、手动拖游标即暂停、切回实时视图按钮恢复运行、按深度视图已移除);test_ui_smoke.py 71 项 0 失败;drive_shared.py 134 项 0 失败(深度引擎用例随代码删除);locale_studio.py 262 键齐全、zh==en;reproduce.py 退出 0;.repro/probe_play.py 实测按「回放」后 0.75 s 内游标推进 0.689 s、面板 12/12 帧都在更新;两张复核图(回放视图的「▶ 回放」、实时视图的「▶ 运行」)。 - Not in this change: 录制文件格式与 CSV 列序不变(raw_*.mp4 与 forces.csv 继续一起写);「由视频重算」复核模式保留(它复用实时管线,是有状态、只能顺序前进的一遍)。
2026-09-14 09:31:21 +08:00
2026-09-12 14:03:00 +08:00