docs: plan for eliminating EngineEventBridge (architecture + good first issues)
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
# 消灭 EventBridge:Good First Issue 清单 / Eliminating EventBridge: Good First Issues
|
||||
|
||||
目标 / Goal:app 不再通过 `EngineEventBridge` / `oakengine_event_subscribe` 接收引擎事件,
|
||||
引擎回归纯库(只保留内部 Qt 信号自用)。
|
||||
The app stops receiving engine events via `EngineEventBridge` / `oakengine_event_subscribe`;
|
||||
the engine becomes a pure library (its internal Qt signals are for its own use only).
|
||||
|
||||
每个 issue 相互独立、可单独认领和提交(基建类两个建议最先做)。
|
||||
Each issue is independent and can be claimed and shipped separately (do the two
|
||||
infrastructure ones first — they simplify everything else).
|
||||
|
||||
---
|
||||
|
||||
## 通用规则 / Ground rules
|
||||
|
||||
- **每个 issue 都必须跑测试再提交 / Run the tests before submitting, every time**:
|
||||
```bash
|
||||
cmake --build cmake-build-debug -j8 && cd cmake-build-debug && ctest -j4
|
||||
```
|
||||
构建零错误、122 个测试全绿才算完成;另需按该 issue 的验收项手工验证。
|
||||
Zero build errors and 122/122 tests passing are the definition of done,
|
||||
plus the manual acceptance checks listed in each issue.
|
||||
- 迁移模式只有三种,照搬即可 / Only three migration patterns, copy them:
|
||||
- **A. app 内部 Qt 信号 / app-internal Qt signal**:事件其实是 app 自己引发的
|
||||
(播放头、编辑、undo),在发起处直接 emit app 内信号,订阅者改连它。
|
||||
The event is app-initiated (playhead, edits, undo) — emit an app-internal
|
||||
signal at the origin and re-point subscribers to it.
|
||||
- **B. 异步保留 / keep async**:事件真是异步的(任务、缓存、音频节拍),收敛到
|
||||
唯一 dispatcher(issue 0b),回调统一 QueuedConnection 回 GUI 线程。
|
||||
Truly async events (tasks, caches, audio beats) go through one dispatcher
|
||||
(issue 0b) that queues everything back to the GUI thread.
|
||||
- **C. 删除 / delete**:调用点本来就知道结果,直接就地刷新,订阅整个删掉。
|
||||
The call site already knows — refresh inline and drop the subscription.
|
||||
- 每完成一个 issue:删掉对应 `bridge_->subscribe` / `oakengine_event_subscribe`
|
||||
调用,并在本文勾掉该条。
|
||||
When done: remove the matching subscribe calls and check off the item here.
|
||||
- 架构总览 / Architecture background:`docs/zh/plans/eliminate-event-bridge.md`
|
||||
|
||||
---
|
||||
|
||||
## 基建 / Infrastructure(建议先做 / do first)
|
||||
|
||||
### issue 0a — 修音频事件 ID 撞号 / Fix the audio event ID collision(真 bug,半天)
|
||||
`engine/include/oakengine/events.h:210` 的 `AUDIO_MANAGER_OUTPUT_NOTIFY = 141`
|
||||
与 :213 的 `PLAYBACK_CACHE_INVALIDATED = 141` 撞号;events.cpp 的 case 141 只做
|
||||
PlaybackCache 转换,`AudioManager::output_notify` 从未接线,`viewer.cpp:1406` 的
|
||||
音频续推订阅实际永远收不到。
|
||||
`AUDIO_MANAGER_OUTPUT_NOTIFY` shares ID 141 with `PLAYBACK_CACHE_INVALIDATED`;
|
||||
the audio notification is never wired up, so the viewer's audio keep-alive
|
||||
subscription never fires.
|
||||
- 改动 / Change:给 OUTPUT_NOTIFY 分配新 ID(>=144),events.cpp 补 AudioManager
|
||||
的 connect。/ Assign a fresh ID (>=144) and wire up AudioManager in events.cpp.
|
||||
- 验收 / Acceptance:播放长素材(>5s)音频不中断。
|
||||
Audio keeps playing past 5 seconds without cutting out.
|
||||
- **必做 / Required:build + ctest 全绿(见通用规则)。**
|
||||
|
||||
### issue 0b — 建唯一的异步事件 dispatcher / Single async event dispatcher(1 天)
|
||||
新建 `app/asyncengineevents.{h,cpp}`(QObject,Core 持有单例),只订阅
|
||||
TASK 族(120–127)、PLAYBACK_CACHE_VALIDATED/INVALIDATED、
|
||||
FRAME_CACHE_INVALIDATED、AUDIO_OUTPUT_PARAMS/NOTIFY。
|
||||
C 回调里只做 `QMetaObject::invokeMethod(this, ..., Qt::QueuedConnection)`,
|
||||
然后 emit 类型化 Qt 信号。
|
||||
Create one dispatcher (Core-owned QObject) subscribing only to the truly async
|
||||
events; C callbacks just queue to the GUI thread and re-emit typed Qt signals.
|
||||
- 验收 / Acceptance:转码一次,状态栏进度条照常走;之后所有 (b) 类迁移都指向它。
|
||||
Transcode once — the status bar progress still works. All later (b) migrations
|
||||
must use it instead of their own subscriptions.
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 0c — 建 app 内 PlaybackController(1 天)
|
||||
新建 `app/playback/playbackcontroller.{h,cpp}`,信号
|
||||
`playhead_changed(oak::Node viewer, Rational)`。
|
||||
把 app 内所有 `oakengine_viewer_set_playhead` 调用点(ViewerWidget 播放循环、
|
||||
timelinewidget、timeruler、multicam、export)统一经它转发并 emit。
|
||||
One app-side controller that every `oakengine_viewer_set_playhead` call site
|
||||
goes through; it re-broadcasts `playhead_changed` as an app-internal Qt signal.
|
||||
- 验收 / Acceptance:播放/拖动/seek 时各视图照常刷新(先并存,后续逐个切换)。
|
||||
All views keep updating during playback/seek (bridge may coexist until later
|
||||
issues migrate each subscriber).
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
---
|
||||
|
||||
## playhead 族迁移 / Playhead migrations(模式 A,约半天/个)
|
||||
|
||||
### issue 1 — timebasedview 的 playhead 订阅
|
||||
`app/widget/timebased/timebasedview.cpp:168`(裸 C 回调)。改连
|
||||
PlaybackController::playhead_changed。
|
||||
- 验收 / Acceptance:播放时 ruler 播放头线正常移动;无裸 C 订阅残留。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 2 — NodeParamViewWidgetBridge playhead
|
||||
`app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp:1140`(裸 C)。
|
||||
- 验收 / Acceptance:播放时关键帧插值滑条值随播放头更新。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 3 — NodeParamViewKeyframeControl playhead
|
||||
`app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp:222`(裸 C)。
|
||||
- 验收 / Acceptance:播放头移动时 prev/next/toggle 关键帧按钮状态正确。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 4 — NodeParamViewConnectedLabel playhead
|
||||
`app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp:131`(裸 C)。
|
||||
- 验收 / Acceptance:值树随播放头刷新。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 5 — ExportDialog playhead
|
||||
`app/dialog/export/export.cpp:311`(裸 C)。
|
||||
- 验收 / Acceptance:导出对话框 in/out 时间随播放头同步。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 6 — timelinewidget / viewerdisplay 的 playhead 族(bridge 订阅)
|
||||
`app/widget/timelinewidget/timelinewidget.cpp`(:741-743 一带)与 viewerdisplay
|
||||
的相关订阅。改连 PlaybackController。
|
||||
- 验收 / Acceptance:时间线时间码/播放头位置显示正确。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
---
|
||||
|
||||
## undo/modified 族迁移 / Undo & modified migrations(模式 A,约半天/个)
|
||||
|
||||
### issue 7 — historywidget 的 UNDO_INDEX_CHANGED
|
||||
`app/widget/history/historywidget.cpp:33,127`(裸 C 两处)。
|
||||
在 Core 的 undo/redo/push 出口 emit app 内 `undo_index_changed(int)`,
|
||||
historywidget 改连它。
|
||||
- 验收 / Acceptance:undo/redo 后历史列表 model reset 且选中行正确。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 8 — core.cpp 的 PROJECT_MODIFIED_CHANGED
|
||||
`app/core.cpp:1108`(裸 C)。modified 由 undo 栈驱动,app 在
|
||||
push/undo/redo/load 处即可推导;改为 app 内信号驱动 `setWindowModified`。
|
||||
- 验收 / Acceptance:编辑/撤销/保存后标题栏修改标记正确。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
---
|
||||
|
||||
## 结构类订阅迁移 / Structure migrations(模式 C/A,0.5–1 天/个)
|
||||
|
||||
### issue 9 — seekablewidget 的 marker/workarea 订阅
|
||||
`app/widget/seekable/seekablewidget.cpp:95-101,146-154`。
|
||||
编辑走 undo 命令:命令执行处直接 `viewport()->update()`;undo 后同样刷新
|
||||
(复用 issue 7 的信号)。
|
||||
- 验收 / Acceptance:增删 marker、改 workarea 后标尺即时刷新,undo 同样正确。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 10 — resizabletimelinescrollbar 的 marker/workarea
|
||||
`app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp:83-98,124-129`。
|
||||
- 验收 / Acceptance:滚动条上 marker 绘制正确。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 11 — nodeviewitem 的 label/color/message/array 订阅
|
||||
`app/widget/nodeview/nodeviewitem.cpp:85-119`。
|
||||
label/color 编辑就地刷新;array size 走结构命令调用点;undo 由 issue 7 信号兜底。
|
||||
- 验收 / Acceptance:改名/换色/数组增删后节点块即时刷新。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 12 — NodeParamViewItem / arraywidget / keyframecontrol 桥订阅
|
||||
`app/widget/nodeparamview/nodeparamviewitem.cpp:95-296`、
|
||||
`nodeparamviewarraywidget.cpp:43-45`、keyframecontrol 的 keyframe 族。
|
||||
- 验收 / Acceptance:参数项 label/flags/array/keyframe 按钮随编辑与 undo 正确刷新。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 13 — nodeparamviewwidgetbridge 的参数值订阅
|
||||
`app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp:161-178`。
|
||||
滑条编辑路径直接 set;undo/加载后统一重读。
|
||||
- 验收 / Acceptance:滑条值在编辑、undo、加载后都正确。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 14 — NodeParamView group passthrough / context 订阅
|
||||
`app/widget/nodeparamview/nodeparamview.cpp:189-205,350-352,871-874`。
|
||||
- 验收 / Acceptance:group 打开/关闭、context 增删节点后参数面板正确重建。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 15 — nodeviewcontext 的节点/边增删订阅(结构核心)
|
||||
`app/widget/nodeview/nodeviewcontext.cpp:99-138,180-183,427-430`。
|
||||
增删来自:app 编辑命令(调用点处理)、undo(issue 7 信号兜底重建)、
|
||||
项目加载(新增"项目加载完成"钩子,Core 在 TaskDialog 成功后广播,
|
||||
替代批量事件驱动建图)。
|
||||
- 验收 / Acceptance:增删节点/连线即时刷新;undo 恢复正确;重新打开项目视图完整。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 16 — nodeview 的 NODE_REMOVED_FROM_GRAPH
|
||||
`app/widget/nodeview/nodeview.cpp:91,1911` 与 mainwindow.cpp:58,351。
|
||||
删除节点的命令处 + undo 信号处理。
|
||||
- 验收 / Acceptance:删节点后 item/ViewerPanel 关闭;undo 恢复。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 17 — timelinewidget 的 track/block 结构订阅
|
||||
`app/widget/timelinewidget/timelinewidget.cpp:665-753,2177-2228`。
|
||||
block/track 增删走 timeline 命令处直接增删 item;undo 与加载统一重建。
|
||||
- 验收 / Acceptance:增删 clip/track 即时刷新,undo/加载正确。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 18 — trackviewitem 的 index/muted
|
||||
`app/widget/timelinewidget/trackview/trackviewitem.cpp:66,117`。
|
||||
- 验收 / Acceptance:轨道移动/静音状态即时刷新。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 19 — projectviewmodel 的 folder/label 订阅
|
||||
`app/panel/project/projectviewmodel.cpp:45-61,505-512`。
|
||||
导入/建文件夹/改名走命令处;加载完成统一 model reset(复用 issue 15 的钩子)。
|
||||
- 验收 / Acceptance:项目浏览器增删/改名即时刷新。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 20 — 其余散点 / misc
|
||||
multicamwidget(:107/114 裸 C)、manageddisplay(OCIO,:143)、
|
||||
audiowaveformview(:57,76)、viewerdisplay(字幕,:100,264)、
|
||||
panel/timebased(label,:162)。
|
||||
- 验收 / Acceptance:各自 UI 随编辑刷新。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
---
|
||||
|
||||
## 异步类迁移 / Async migrations(模式 B,约半天/个,依赖 0b)
|
||||
|
||||
### issue 21 — mainstatusbar / taskmanager / taskviewitem / task dialog
|
||||
`app/window/mainwindow/mainstatusbar.cpp:88-146`、
|
||||
`app/panel/taskmanager/taskmanager.cpp:41-56`、
|
||||
`app/widget/taskview/taskviewitem.cpp:82-88`、
|
||||
`app/dialog/task/task.cpp:47-48`。全部改连 dispatcher 的 task 信号。
|
||||
- 验收 / Acceptance:转码/导出任务进度条、任务列表实时更新。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 22 — timeruler 缓存条 + viewer 的 cache invalidated
|
||||
`app/widget/timeruler/timeruler.cpp:82-113`、
|
||||
`app/widget/viewer/viewer.cpp:366-394`。
|
||||
- 验收 / Acceptance:播放时缓存绿条随后台渲染增长;编辑后缓存条正确失效。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
### issue 23 — viewer 的音频续推(AUDIO_OUTPUT_NOTIFY)
|
||||
`app/widget/viewer/viewer.cpp:1406`(裸 C,当前因撞号失效)。依赖 0a+0b。
|
||||
- 验收 / Acceptance:长素材播放音频持续不中断。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
|
||||
---
|
||||
|
||||
## 收尾 / Final teardown(最后做 / do last)
|
||||
|
||||
### issue 24 — 删除 EngineEventBridge
|
||||
前面 issue 全部勾掉后:`grep -rn "EngineEventBridge\|bridge_->subscribe" app`
|
||||
应为零引用。删除 `app/engineeventbridge.{h,cpp}` 与 CMake 条目。
|
||||
- 验收 / Acceptance:构建通过;全功能回归(播放/节点图/时间线/参数/历史/任务)。
|
||||
- **必做 / Required:build + ctest 全绿 + 完整手工回归。**
|
||||
|
||||
### issue 25 — 收窄 engine 事件面
|
||||
`engine/src/capi/events.cpp` 只保留 dispatcher 需要的异步事件 +
|
||||
UNDO_INDEX_CHANGED / PROJECT_MODIFIED_CHANGED(给未来 AI 助理等外部写入方
|
||||
保留的最小例外);`engine/include/oakengine/events.h` 同步事件常量删除或移入
|
||||
内部头;引擎内部 Qt 信号保持不变。
|
||||
- 验收 / Acceptance:ctest 全绿;nm 检查 C ABI 不再暴露同步事件订阅面。
|
||||
- **必做 / Required:build + ctest 全绿。**
|
||||
@@ -0,0 +1,136 @@
|
||||
# 消灭 EngineEventBridge:引擎纯库化计划
|
||||
|
||||
## 背景与目标
|
||||
|
||||
现状:app 通过 `EngineEventBridge`(app/engineeventbridge.*,十几个实例)+ 引擎 C 事件
|
||||
(engine/src/capi/events.cpp,75 个 OAKENGINE_EVENT_*)接收引擎回调。问题:
|
||||
|
||||
- 约 60% 的事件是 app 自己调用引擎时同步产生的,app 本来就知道,不需要被"通知"。
|
||||
- 裸 C 回调(userdata=this)无生命周期追踪,已制造多起 UAF 崩溃(最近修掉 3 处,仍有
|
||||
14 处靠人工 unsubscribe 保命)。
|
||||
- `OAKENGINE_EVENT_AUDIO_MANAGER_OUTPUT_NOTIFY(141)` 与
|
||||
`PLAYBACK_CACHE_INVALIDATED(141)` ID 撞号,音频续推订阅实际从未生效(现存 bug)。
|
||||
- 引擎因此不是一个纯库,且后续 AI 助理等新写入方会进一步放大"变更来源"的复杂度。
|
||||
|
||||
目标架构(单向依赖):
|
||||
|
||||
```
|
||||
app 编辑路径 ──同步调用──> engine(纯库,只保留内部 Qt 信号供自身机制使用)
|
||||
app 编辑路径 ──app 内部 Qt 信号──> app 各视图(播放头、undo、结构、参数)
|
||||
engine 异步事件(任务/缓存/音频)──极窄 C 通道(queue 到 GUI 线程)──> app
|
||||
```
|
||||
|
||||
- 引擎内部信号(Node::value_changed、Project::node_added 等)保留——它们是
|
||||
PreviewAutoCacher/ProjectCopier 等引擎自身机制的生命线,但不许越界给 app 同步事件。
|
||||
- C ABI 事件面从 75 个收窄到 ~10 个真异步事件。
|
||||
- 消灭 `EngineEventBridge` 类和全部裸 `oakengine_event_subscribe` 调用点。
|
||||
|
||||
## 事件分类结论(来自全量盘点)
|
||||
|
||||
- **(a) 同步 app 发起(~60%)**:Node 编辑族、folder、track/block 结构、marker、
|
||||
workarea、viewer 参数、color、context position、group passthrough。
|
||||
注意每个都有三个旁路来源:undo/redo 重放、项目加载批量、引擎内部联动——
|
||||
不能只在调用点处理,必须经"命令执行器/加载完成"统一钩子。
|
||||
- **(b) 真异步(必须保留通知)**:TASK 族(120–127)、PLAYBACK_CACHE_VALIDATED/
|
||||
INVALIDATED、FRAME_CACHE_INVALIDATED、AUDIO_MANAGER_OUTPUT_PARAMS/NOTIFY。
|
||||
- **(b') playhead/length**:playhead 是 app 播放循环自己 set 的,消费者遍布 10+ 组件
|
||||
——本质是 app 内部广播,挪进 app 侧 PlaybackController,引擎零参与。
|
||||
length 是引擎推导状态,改为编辑后主动查询。
|
||||
- **(c) undo 驱动**:UNDO_INDEX_CHANGED、PROJECT_MODIFIED_CHANGED 及全部会被
|
||||
undo 重放重发的结构事件——app 侧 UndoController 发粗粒度信号,视图惰性刷新。
|
||||
|
||||
## 阶段计划
|
||||
|
||||
### P0 — 冻结分类表 + 修音频事件 ID 撞号(先行 bug fix)
|
||||
- events.h 给 AUDIO_MANAGER_OUTPUT_NOTIFY 分配新 ID(如 144),events.cpp 补
|
||||
AudioManager::output_notify 的 wire-up(当前 case 141 只做 PlaybackCache cast,
|
||||
音频订阅永远返回 0)。
|
||||
- 产出 docs/zh/event-bridge-inventory.md:75 事件 → (a)/(b)/(b')/(c) 四类的映射表
|
||||
(基于本次盘点结果),作为后续迁移的对账单。
|
||||
|
||||
### P1 — PlaybackController(消灭 playhead/length 订阅,~12 处)
|
||||
- 新建 `app/playback/playbackcontroller.{h,cpp}`(QObject,Core 持有单例):
|
||||
- 信号:`playhead_changed(oak::Node viewer, Rational time)`、
|
||||
`viewer_length_changed(oak::Node, Rational)`。
|
||||
- ViewerWidget 播放循环/seek 的 `oakengine_viewer_set_playhead` 调用处统一经它发出;
|
||||
其它 set_playhead 调用点(timelinewidget、timeruler、multicam)同样经它。
|
||||
- length:各 viewer 参数编辑点 + undo 后主动 `viewer_output_length()` 查询并广播变化。
|
||||
- 迁移订阅者:timebasedview、resizabletimelinescrollbar(部分)、
|
||||
nodeparamviewwidgetbridge/keyframecontrol/connectedlabel(3 个裸 C)、export dialog、
|
||||
timelinewidget、viewerdisplay。
|
||||
- 收益:顺带消灭 4 个裸 C 订阅中的 3 个和播放头族全部事件。
|
||||
|
||||
### P2 — EngineAsyncEvents(收窄 C 事件面到 ~10 个)
|
||||
- 新建 `app/asyncengineevents.{h,cpp}`(唯一 dispatcher,Core 持有):
|
||||
- 对 TASK 族、CACHE_VALIDATED/INVALIDATED、FRAME_CACHE_INVALIDATED、
|
||||
AUDIO_OUTPUT_PARAMS/NOTIFY 各建一个 C 订阅,回调里只做
|
||||
`QMetaObject::invokeMethod(this, ..., Qt::QueuedConnection)` 统一回 GUI 线程,
|
||||
再 emit 类型化 Qt 信号。
|
||||
- 明确文档化:这是引擎→app 的唯一反向通道,只覆盖真异步事件。
|
||||
- 迁移订阅者:mainstatusbar、taskmanager 面板、taskviewitem、task dialog、
|
||||
timeruler 缓存条、viewer 的 cache invalidated、viewer 音频续推(接 P0 修好的 ID)。
|
||||
- engine/include/oakengine/events.h:同步事件常量标注 deprecated(先不删,P5 后清理)。
|
||||
|
||||
### P3 — UndoController + 粗粒度模型变更信号(消灭 undo 族 + 结构类订阅)
|
||||
- 新建 `app/undo/undonotifier.{h,cpp}`:
|
||||
- 包装所有 `oakengine_undo_push/undo/redo` 的 app 调用点(集中在少数几处:
|
||||
oakengine_undo_push_or_run 的 app 侧、Core、各面板直接 push 处);
|
||||
命令执行后、undo/redo 后、项目加载完成后,emit:
|
||||
- `structure_changed()`(节点/边/track/block/folder 增删移动)
|
||||
- `params_changed()`(值/关键帧/label/flags)
|
||||
- `modified_changed(bool)`(给 core.cpp:1108 的 setWindowModified)
|
||||
- `undo_index_changed(int)`(给 historywidget)
|
||||
- 项目加载(TaskDialog 完成)后也发一次 structure_changed + params_changed,
|
||||
替代加载期批量事件驱动建视图的路径(nodeviewcontext、projectviewmodel 依赖它)。
|
||||
- 迁移结构类订阅:nodeviewcontext(节点/边增删)、nodeview(removed_from_graph)、
|
||||
timelinewidget(track/block 结构)、projectviewmodel(folder/label)、
|
||||
mainwindow(ViewerPanel 关闭)、trackviewitem。
|
||||
迁移方式:订阅粗粒度信号 → 对应视图做惰性重建/刷新(NodeView/Timeline 本就按
|
||||
context 重建,成本可控;projectviewmodel 用 model reset)。
|
||||
- historywidget 与 core.cpp 的 UNDO_INDEX_CHANGED/MODIFIED 订阅改为 UndoController 信号。
|
||||
|
||||
### P4 — 参数类订阅迁移(最大量、逐面板)
|
||||
- 原则:编辑发起处直接刷新 + UndoController::params_changed 惰性兜底。
|
||||
- nodeparamviewwidgetbridge(滑条值):SliderBase 编辑路径已知道新值(直接 set);
|
||||
undo/加载后靠 params_changed 触发受影响 item 重读。
|
||||
- keyframeview/keyframecontrol(关键帧按钮/曲线):编辑路径直接刷新 +
|
||||
params_changed 全量重读。
|
||||
- nodeviewitem(label/color/message/array size)、nodeparamviewitem/arraywidget、
|
||||
connectedlabel、seekablewidget(marker/workarea)、viewerdisplay(字幕)、
|
||||
multicamwidget(size/par)、manageddisplay(OCIO 配置)、audiowaveformview。
|
||||
- 每迁完一个面板,删除其 EngineEventBridge 成员与全部 `bridge_->subscribe` 调用。
|
||||
|
||||
### P5 — 拆除与收尾
|
||||
- 删除 app/engineeventbridge.{h,cpp} 及全部引用;删除各裸
|
||||
`oakengine_event_subscribe` 调用点(P1–P4 应已清零,grep 验证)。
|
||||
- engine/src/capi/events.cpp:同步事件订阅路径下线(保内部引擎信号);
|
||||
events.h 同步事件常量删除或移入内部头;C ABI 只保留 P2 的异步集合 + undo 族
|
||||
(undo 族是否保留视 P3 后 app 是否还有 C 调用方——AI 助理未来仍可能需要,
|
||||
保留 UNDO_INDEX_CHANGED 和 MODIFIED 两个 C 事件是可接受的例外)。
|
||||
- 更新 docs/zh/event-bridge-inventory.md 为"迁移完成对照表",
|
||||
更新 docs/zh/investigation-edge-display-and-playback.md 收尾记录。
|
||||
|
||||
## 验证方案(每阶段都要过)
|
||||
|
||||
1. `cmake --build cmake-build-debug -j8` 零错误零新警告。
|
||||
2. `ctest -j4` 122/122。
|
||||
3. 手工检查表(每阶段对应项):
|
||||
- 播放:画面/声音正常、播放头移动、拖动播放头不崩。
|
||||
- 节点图:边全部显示、增删节点/边即时刷新、切换素材不丢。
|
||||
- 时间线:增删 clip/track 即时刷新、缓存条随渲染增长。
|
||||
- 参数面板:滑条值随编辑/undo 刷新、关键帧按钮状态正确。
|
||||
- 项目浏览器:导入素材/改名/撤销即时刷新。
|
||||
- 状态栏/任务面板:转码任务进度实时更新。
|
||||
- 历史面板:undo/redo 列表与选中行正确,标题栏修改标记正确。
|
||||
|
||||
## 风险与对策
|
||||
|
||||
- **粗粒度信号导致过度刷新**:NodeView/Timeline 重建成本高。对策:P3 先只在
|
||||
structure_changed 上做惰性(queued + 合并多次变更一次刷);profiling 不行再退回
|
||||
按面板细化。
|
||||
- **漏掉旁路发射源**(引擎内部联动、multicam):对策:迁移期在引擎 emit 点临时加
|
||||
计数日志(复用 OAK_DEBUG_EDGES 模式),手工操作对照表逐项过一遍确认无未覆盖事件。
|
||||
- **加载期建视图路径**:原来靠批量事件驱动;P3 的"加载完成后 structure_changed"
|
||||
必须覆盖 nodeviewcontext、projectviewmodel、timelinewidget 三处的初始构建。
|
||||
- **UndoController 覆盖不全**(某些面板直接 oakengine_undo_push):grep 全部调用点
|
||||
收口,禁止新增未经 UndoController 的 push(code review 条目)。
|
||||
Reference in New Issue
Block a user