Files
oak-editor/docs/zh/plans/completed/r5-app-migration-guide.md
T
Mike-Solar dd5508e571 docs: archive completed C ABI campaign docs under plans/completed/
Move the finished migration campaign docs (handoffs v3-v6, roadmap,
R5 guides, R6 cleanup, R7 pure-ABI) from docs/zh/ into
docs/zh/plans/completed/ with an archive README; fix all
cross-references; refresh plans/README.md index (active plans now
marked unlocked).
2026-07-27 01:52:52 +08:00

12 KiB
Raw Blame History

R5 app 侧调用点迁移 — 实施指引

本文是 R5 阶段(消灭 oak-editor 对 olive:: C++ 符号的引用)的执行手册。 面向没有此前对话记忆的执行者,自包含。 与 c-abi-migration-handoff.mdv3)、c-abi-migration-handoff-v4.md 的关系: 那两份管 facade(C API)建设;本文管 app 侧把对 engine C++ 类的直接调用 换成 facade 调用。facade 已就位且全绿,R5 不需要再新建 C API 族。

工作分支:c-abi-migration。每完成一个文件/小步立即提交。


0. 当前已验证状态(接手先复核,不要采信转述)

cmake --build cmake-build-debug -j$(nproc)          # 必须 0 error
cd cmake-build-debug && ctest --output-on-failure -j$(nproc)   # 必须 44/44 绿
nm -D cmake-build-debug/app/oak-editor | grep -c " U _ZN5olive"   # 当前 395
  • 测试:44/44 全绿R1–R4 facade 扩容已修复并通过)。
  • 符号:395nm 实测,按类统计见 §3)。
  • facade 族已全部存在且带测试:node / timeline / project / footage / preview / audio / color / config / disk / encoding / events / gizmo / lut / plugin / proxy / serializer / sync / task / traverse / undo / videoparams / viewer / worker / playback / renderer / app。
  • 事件 ID 已分配到 143,新事件从 144 起。

1. 关于"必须全文件集群重写、无法增量"——论断不成立

DS 报告称 R5 的符号"因 Q_OBJECT + 虚函数链 + 信号/槽耦合,必须全文件集群 同时重写,无法增量"。这是错误的,证据:

  1. 前一次战役就是增量完成的:同一套符号从 557 → 162,按 B1–B11a 分批、 每批独立闭环。被回滚抹掉的是 app 调用点,不是方法。
  2. 本阶段已经证明可增量:符号已从回滚后的高点降到 395,全部是逐文件 替换得来的,没有一次"集群重写"。
  3. facade 已就位:R5 需要的 C API 族全部存在。剩下的工作是把 app 里的 olive::X::method() 调用换成 oakengine_x_method()不重写任何 engine 类,自然不存在"虚函数链重写"。
  4. "信号/槽耦合"已被事件机制解决oakengine_event_subscribe + app/engineeventbridge.{h,cpp} 就是替代 connect(engineObj, &Engine::sig, ...) 的标准通道(SOP 见 roadmap 附 D)。不需要为消信号而重写类。

结论:R5 是机械的、可逐文件验证的调用点替换,按 §4 的顺序增量推进。 "集群重写"是停工的借口,不是技术结论。

2. 增量方法(每个文件的标准动作)

对每一个 app 文件,按这个顺序做,做完立即提交:

  1. grep 该文件的 engine 直接引用olive::X::connect( 到 engine 对象、 new SomeCommand(engine 命令类)、engine 头 include。
  2. 逐点替换为 facade
    • 方法调用 → 对应 oakengine_* 函数(§4 表)。
    • connect(engineObj, &Engine::sig, this, ...)bridge->subscribe(handle, OAKENGINE_EVENT_*) + 连 bridge 的 Qt 信号 (没有 bridge 成员就 new EngineEventBridge(this))。
    • new EngineCommand(...) 进 undo 栈 → facade 的 undoable 原语 (多数已有;确实缺的按 §5.2 补 facade,不要新建 app 侧命令类)。
    • undo 压栈一律 oakengine_undo_push(command, name)2 个参数,全局栈, 不传栈句柄;Core::undo_stack() 返回的 void* 只作事件订阅 handle)。
  3. 删不再需要的 engine 头 include;加对应 oakengine/*.h
  4. 构建 + 该文件相关测试 + nm 双度量,符号应净减。
  5. 立即提交,提交信息写清文件与消掉的符号数。

禁做:为消符号新建 app 侧 engine 命令子类、给 facade 加"stub 实现" (见 §6 红线)、为省事把 engine 源码再编进 app。

3. 剩余符号分布(395,nm 实测,按类)

执行顺序按"依赖最少、符号最密集、facade 最现成"排。每行:数量 类 — 主战场文件 — 用哪个 facade 族

第一批:工具/服务类(快、独立,先清 ~60)

数量 主战场 facade 族
9 QtUtils 各 widget 纯搬 appB10 模式,app/common/hidden visibility,见 §6-R1
9 EncodingParams dialog/export/*、speedduration oakengine/encoding.h(已含 OakEngineEncodingParams 全字段)
7 ExportFormat dialog/export/*、sequence 对话框 oakengine/encoding.hformat/codec 元数据)
11 AudioManager viewer、core、preferencesaudiotab oakengine/audio.h + 事件 140
7 PreviewAutoCacher / 7 RenderTicketWatcher / 5 RenderTicket / 3 RenderManager viewer.cpp、timeruler oakengine/preview.hcacher + OakEnginePreviewRequest
7 plugin pluginSupport oakengine/plugin.h + 事件
5 ProxyManager proxydialog、projectexplorer、timelinewidget oakengine/proxy.h
5 ProjectSerializer keyframeview、seekablewidget、timelinewidget、nodeview、nodeparamview、main oakengine/serializer.hOakEngineClipboard
5 FileFunctions / 4 ColorCoding / 4 qHash / 2 Html / 1 xml_read / 1 debug_handler / 2 operator<<>> 各 widget 纯搬 appB10 模式)
3 LUTLibrary colordialog、nodeparamviewwidgetbridge、preferencesluttab oakengine/lut.h
2 Config preferences、mainmenu、core oakengine/config.h + app/common/configwrapper.h

第二批:项目/素材/序列数据类(~90)

数量 主战场 facade 族
10 Project / 7 Folder / 10 Footage / 5 Sequence / 5 TrackList projectexplorer、projectproperties、footageproperties、projectviewmodel oakengine/project.h + footage.h + timeline.hfolder 族、oakengine_folder_move_child
17 EngineCore core.cpp、mainwindow、各 panel oakengine/app.hCore 已组合转发)
9 ViewerOutput viewer、footageviewer、各 panel oakengine/viewer.hworkarea、playhead、params
15 ColorManager / 5 ColorProcessor / 1 ManagedColor manageddisplay、colordialog、colorbutton、scopebase、colorvalueswidget、projectproperties oakengine/color.h + app/widget/manageddisplay/colorprocessorhandle.h
9 NodeGroup / 6 MultiCamNode nodeview、multicam 面板 oakengine/node.hgroup passthrough、multicam 族)

第三批:时间线/标记/命令类(~70

数量 主战场 facade 族
13 Track / 12 ClipBlock / 8 TimelineWorkArea / 6 TimelineMarker timelinewidget、timeruler、seekablewidget、trackview、timelineview oakengine/timeline.htrack/clip/marker/workarea 全族)
6 Task / 6 NodeTraverser taskview、export、viewer、nodeparamview oakengine/task.htraverse.h
5+ UndoStack / 各 Marker/Node 命令类(MarkerAdd/ChangeColor/ChangeName/ChangeTime/Remove、NodeAdd/EdgeAdd/EdgeRemove/Rename/OverrideColor/ParamSetStandardValue、FolderAddChild、TimelineAddTrack、TrackListRippleToolCommand historywidget、nodeview、timelinewidget、nodeparamview、projectviewmodel oakengine/undo.h + 各 undoable 原语;不要新建 app 侧命令类(TrackListRippleToolCommand 是遗留评估点,最后单独定)

第四批:Node 大族(~55

数量 主战场 facade 族
40 Node / 7 NodeKeyframe / 3 NodeFactory nodeview、nodeparamview、curvewidget、keyframeview、nodetableview、nodevaluetree、multicam、nodecombobox oakengine/node.h(~60 函数:输入元数据、值读写、关键帧、dragger、undoable 批量、context、group、multicam
7 TextGizmo / 3 DraggableGizmo viewerdisplay oakengine/gizmo.hPOD
各 12 CrossDissolveTransition / SubtitleBlock / TransitionBlock / VolumeNode / TransformDistortNode / SolidGenerator / TextGeneratorV3 / ShapeNode timeline 工具、nodeview oakengine_node_create_undoable + input id getterB4c 模式)

第五批:GPU/帧路径(~15,R6 收口)

数量 主战场 facade 族
5 Frame / 3 Renderer / 2 OpenGLRenderer / 1 DynamicRenderer / 2 Texture / 1 RenderManager viewerdisplay、manageddisplay oakengine/renderer.htexture/frame 句柄,R6 已完成 B7 桥移除)

4. 每批闭环(不可省)

nm 基线 → 逐文件替换(§2)→ 构建 0 error → 全量 ctest 44/44 绿
→ nm 双度量(族符号 + 总数净减)→ 立即提交 → roadmap 附 C 补记

全量 ctest 不绿不得进入下一批。 已知 flakyoak_cli_transcodeoakengine_export_testolive-gtest 偶发 SEGFAULT)单独重跑两次仍败才算真失败。

5. 缺的 facade 怎么办

绝大多数调用点已被现有族覆盖。确实缺的时候:

  1. 先查:该功能是否已被某族覆盖(grep oakengine_* 头)。多数"缺"是没找到现成函数。
  2. 能搬 app 的纯工具Qt 类型、纯函数、纯数据)→ 搬 app/common/ 对该源文件加 -fvisibility=hidden(§6-R1),不新增 C API。
  3. 必须跨边界的 → 最小 facade 族(只包 app 实际用到的成员), 头文件规则同现有族(纯 C 类型、buf/size、owned/borrowed 注释、错误码)。 新 C 函数必须配单元测试(注册 make_oakengine_test)。
  4. undoable 编辑 → 照 engine/src/capi/node.cpppush_or_run 模式; 用户语义上的单次操作必须单条 undo(oakengine_folder_move_child 是样板)。

6. 红线(本阶段修过的真实 bug,不得再犯)

  • R1ODR/符号介入)app 侧严禁用与 engine 相同限定名定义非 inline 符号。 确需同名本地副本,必须对该源文件 -fvisibility=hiddenapp/CMakeLists.txt 有样板;#pragma GCC visibility 对已被 engine 头以 default 声明过的符号无效)。
  • R2(禁 no-op stubfacade 函数不许返回假成功(oakengine_export_render_ with_params 曾是 stuboakengine_clip_set_media_in 曾是直接写不可撤销)。 不可撤销的改图操作就是 bug——set_media_in 不入栈导致 project_undo 误删 clip。
  • R3undo 语义)oakengine_undo_push(command, name) 只 2 参。删除任何 push 必须同步接上命令执行路径(命令不压栈 = 静默不执行 + 泄漏)。
  • R4(单位与索引)facade 的 time_tstoggle/has/closest/dragger/ get_input_at_time 等);keyframe 的 track/track_for_time1-based set_*_many 的 tracks 是 0-based;序列/clip 的 ts 用 timestamp_to_time 换算,不许硬编码 /30
  • R5POD 构造)VideoParams 用带参构造(四参 w/h/format/channelsdepth=1 默认构造 depth=0 会让 Vulkan 上传 0 字节纯黑);Rational 分子是 32 位 int 哨兵值用 INT_MAXRATIONAL_MAX),不许 INT64_MAX(溢出成负数)。
  • R6engine 语义边界)Track::is_range_free 排除 GapBlockprobe 句柄 oakengine_footage_probe)不带项目节点,import-only 族必须返回 E_INVALID oakengine_sequence_add_sequence_clip 必须查间接循环嵌套(上游依赖图含目标 序列即拒绝),否则真成环导致 invalidate_cache 数万帧递归栈溢出。
  • R7buf/size 约定)string_to_buf 传 NULL 也返回长度;返回 >0 的 长度查询不得对 NULL buf 特判返回 0(group_add_input_passthrough 曾犯)。
  • R8(接手验证):任何交接后先全量构建 + 全量 ctest + nm 复核,再动手; 不采信上一手的完成声明(包括本文 §0,以你实测为准)。

7. 里程碑

  1. 第一批(工具/服务)清零 → 总数应跌破 ~330。
  2. 第二批(项目/素材/序列)清零 → ~240。
  3. 第三批(时间线/命令)清零 → ~170。
  4. 第四批(Node 大族)清零 → ~115。
  5. 第五批(GPU+ B11c/B11d 收口 → 只剩豁免清单(AudioProcessor 4 + Block/Track::staticMetaObject = 6)→ nm -D --defined-only liboakengine.so | grep -c " T _Z" = 0 → 全量终验。