Files
oak-editor/docs/zh/plans/completed/r5-phase3-final-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 终局计划:181 → 豁免清单(≤6)

面向执行者(DeepSeek Flash),自包含。工作分支:c-abi-migration。 前置文档:facade-migration-roadmap.md(批次记录)、 r5-app-migration-guide.mdR5 总指引)、c-abi-migration-handoff.md (v3,§6.4 豁免清单格式)。本文是 R5 的最后一个阶段: 处置当前 WIP → 修完已记录缺陷 → 把剩余 181 个 olive:: 符号收到豁免清单。 每批闭环:全量构建 0 error + 全量 ctest 绿 + nm 复核 + 立即提交。

状态:第 0 批(§3)与第 1 批(§4)已由 Kimi 完成。期间新增两处 计划外修复:ProjectViewModel 的 sender() 崩溃(bridge 迁移后槽函数 仍用 sender() 取 Folder,拿到的是 bridge 指针,段错误)与 oakengine_folder_move_child 语义修正(原来只加不删,"移动"后节点 同时存在于两个文件夹;已改为 删旧+加新,并新增批量版 oakengine_folder_move_children 供拖放一次移动多项)。 剩余:批 F1-F6(§5)。


1. 现状(2026-07-24 实测,GLM-5.2 交接)

nm -D cmake-build-debug/app/oak-editor | grep -c " U _ZN5olive"   # 88

GLM-5.2 从 131 消除至 88-43)。已完成:F3Task/TimelineWorkArea/ ViewerOutput/Project 全部)、F4 方法调用(14 符号 + 7 新 facade 函数)、 F6 部分长尾(7 构造器 + 5 静态字符串)。

剩余 88 符号分布:

说明
Node 信号+staticMetaObject 26 22 信号 + staticMetaObject + link + set_standard_value + set_value_at_time
AudioProcessor 5 豁免候选(实时音频回调边界)
plugin::PluginProgressReporter 4 豁免候选(MOC 四件套)
渲染族 ~25 Renderer/PlaybackCache/Frame/DynamicRenderer/DraggableGizmo/OpenGLRenderer/Texture/ColorProcessor/AudioWaveformSync/AudioSynchronizer 等
长尾 ~28 NodeValue/ManagedColor/VideoParams/UndoCommand 等 1-4 符号类

下一任主攻:Node 信号连接迁移(23 符号,事件 ID 70-95 已全部分配, EngineEventBridge 信号已存在,需为 9 个类添加 bridge 成员)。

2. 红线(新增两条,违反即返工)

R5 既有红线不变:不改 oakengine_* 已发布签名、不删测试、不用 git checkout --/restore/clean/reset --hard/stash、每批立即提交。 新增:

  1. 禁止 inline 化 engine 实现刷符号。把 engine 的 .cpp 实现搬进头文件 (如本次 WIP 对 ManagedColorTimelineWorkArea 做的那样)不会让 依赖消失,只是让 app 内联编译 engine 代码——C ABI 边界被架空,nm 数字 是假的。判定方法:engine 目录下的任何 .h 出现新的非平凡函数体即违规。
  2. 禁止 no-op stubload() 返回 true、save() 空体、// apply ... 空循环这类"假成功"是最严重违规(本次 WIP 的 TimelineWorkArea::load/save 即为例:项目文件的工作区会全部丢失)。 任何行为变更必须在提交信息里写明并接受 review。
  3. 禁止 dlsym/GetProcAddress 等运行时解析 engine C++ 符号DS 在 app/common/nodefactorywrapper.cpp 用过,nm 统计不到 ≠ 依赖不存在)。 facade 只能在 engine/src/capi/ 实现、engine/include/oakengine/ 声明。详见 c-abi-migration-handoff-v5.md §2。

3. 第 0 批:当前 WIP 处置(最先做,单独一个提交)

工作区现有 DS 停手时留下的未提交改动,分两类:

3.1 回退(engine 被改坏的部分,4 个文件 + 2 个 CMake)

git show HEAD:<path> > <path> 恢复(禁止 git checkout --):

  • engine/render/managedcolor.hengine/render/managedcolor.cppengine/render/CMakeLists.txtinline 化,红线 1
  • engine/timeline/timelineworkarea.hengine/timeline/timelineworkarea.cppengine/timeline/CMakeLists.txtinline 化 + load/save stub,红线 1+2

TimelineWorkArea 的 3 个符号(enabled/range 信号与 staticMetaObject 按 §5 批 F4 的正路处理。

3.2 保留并修复(app 侧 ProjectSerializer → clipboard facade 迁移,方向正确)

涉及:timelinewidget.{h,cpp}nodeparamview.{h,cpp}nodeview.cppkeyframeview.cppresizabletimelinescrollbar.{h,cpp}。修三处:

  1. nodeview.cpp::copy_selectedchar buf[65536] 固定缓冲会截断大节点图 的 XML。改两段式:先 oakengine_clipboard_save_to_xml(cb, nullptr, 0) 取所需长度,再 QByteArray(len+1, '\0') 分配写入。全仓库同类 buf/size 调用(oakengine_project_filename 等 512/256 定长)一并不再 扩大,仅本处改两段式(XML 体积无上限,文件名有)。
  2. resizabletimelinescrollbar.cpp::connect_work_area:已改为 oakengine_event_subscribe(方向对,解决了直连 engine 信号),但裸 订阅的 userdata 是 this必须在析构里 oakengine_event_unsubscribe 两个 id,否则 widget 先死、workarea 后发事件即悬垂回调。
  3. nodeparamview.cpp::paste 里的空循环 // apply position from map 原代码同样是建了 PositionMap 未使用(上游 Olive 遗留死代码),不算 回归,但既然碰了就删掉这个死块,别留占位注释。

闭环后提交(提交信息注明:clipboard 迁移 + 三处修复 + engine 回退)。

4. 第 1 批:已记录缺陷修复(review 累积清单,决策已写死)

按序修,一个提交;每项都给出现象与定死的修法:

  1. NodeParamView::DeleteSelected 重连静默失败(严重)。 现码先 oakengine_node_connectoakengine_nodes_delete_many, 而契约规定输入已占用时 connect 返回 E_STATE——重连必然失败。 修法:facade 新增 int oakengine_nodes_delete_many_ex(nodes, contexts, node_count, edge_outputs, edge_input_nodes, edge_input_ids, edge_input_elements, edge_count, reconnect_outputs, reconnect_input_nodes, reconnect_input_ids, reconnect_input_elements, reconnect_count) ——engine 内部在同一条 NodeViewDeleteCommand 里先删后连 redo 序:delete → reconnect),undo 序反向。NodeParamView 只收集 重连边传入,不自己 connect。oakengine_nodes_delete_many 保留, 等价于 _ex 传 reconnect_count=0。
  2. ProjectViewModel 切项目丢事件(严重)。set_project 重建 bridge_ 后没重连 4 个 folder 信号。修法:把构造里的 4 个 connect(bridge_, ...) 抽成私有 connect_bridge_signals() 构造函数与 set_project 重建后都调。
  3. NodeParamViewWidgetBridge dragger 泄漏。类无析构, oakengine_dragger_create 的句柄永不 free。修法:加析构调 oakengine_dragger_free(dragger_)
  4. TimeBasedWidget 旧订阅泄漏connect_viewer_nodedisconnect(bridge_, nullptr, this, nullptr)engine 侧订阅不释放。 修法:EngineEventBridgeunsubscribe_all()(对 subscriptions_ 逐个 unsubscribe 并清空),在 disconnect 旁调用。
  5. 边-only 删除 undo 拆分NodeView::delete_selected 纯边分支逐边 oakengine_node_disconnect_ex,N 条边 N 条 undo。修法:放宽 oakengine_nodes_delete_many 契约允许 node_count==0 && edge_count>0 (纯边删除,报错条件改为两者同时为 0),边-only 分支改走 delete_many。
  6. seekablewidget marker 订阅泄漏set_markers 建 3 个订阅 ADDED/REMOVED/MODIFIED)只存 1 个 id。修法: QVector<int64_t> marker_subs_ 存全 3 个,重设/析构全解(对齐 resizabletimelinescrollbar.cpp::connect_markers 的正确模式)。
  7. core_params 0x1 陷阱 + core.h 死代码app/core.cpp:1509 Core::core_params() 解引用 0x1app/core.h:337 残留 EngineCore *engine_core_ + #include "coreengine.h" + 5 个空操作 handler setter。修法:全删(core_params 无调用方,直接删方法)。
  8. 小项打包
    • projectviewmodel.cpp::connect_item 死参数 subscribe(无 false 调用点)——删参数。
    • vieweroutpututils.h:50 死声明 viewer_output_video_params_from_oak——删。
    • curveview.cpp 两处 (type == 0) ? 1 : 0 魔法数——facade 加 int oakengine_keyframe_opposing_bezier_type(int type),调用替换; (opposing_type == 0) ? 0 : 1 恒等式一并简化。
    • export.cpp image_sequence_check_box_changed 的裸 { } 块缩进 乱——clang-format 归位。

5. 第 2+ 批:符号收尾(按簇,难度从低到高)

每批做法相同:grep 定位引用源 → 按既有模式迁移(facade 函数 / cliphandle.h 式 app 侧 inline 适配头 / CustomUndoCommand 回调 / EngineEventBridge 订阅)→ 闭环提交。禁止走 §2 两条红线的捷径。

  • 批 F1 撤销命令族(~30,25 个类):多数已有 facade 等价物直接换 (NodeEdgeAddCommandoakengine_node_connect NodeEdgeRemoveCommandoakengine_node_disconnect_exMarker 五命令 →oakengine_marker_* 族)。无等价物的按 a86cb6c99oakengine_undo_command_create 回调模式迁移。TrackListRippleToolCommand 按 v3 §176 行处理:先尝试现有 timeline 原语组合,不行设计 oakengine_tracklist_ripple_* 小族,再不行写理由进豁免清单。
  • 批 F2 Track/ClipBlock/NodeGroup/NodeKeyframe30:属性访问走 trackhandle.h/cliphandle.h 模式扩展;信号走 EngineEventBridge。
  • 批 F3 Task/Project/ViewerOutput/VideoParams18:剩余多为 VideoParams 构造重载(vieweroutpututils 已收口一半)与 Task 信号 (已有 task 事件族,照 taskviewitem 先例)。
  • 批 F4 Node 大族(39,最难,放靠后):逐个符号 grep 定位。预期构成: qobject_cast<Node*>(改 oakengine_node_type_id 比较 + static_cast)、 staticMetaObject(改字符串式 connect 或事件订阅,消不掉按 §6.4 豁免)、 inline 方法拉的 vtable/typeinfo(把调用点换 facade)。 TimelineWorkArea(3)、UndoCommand(3)、Block(3)、NodeGroup 残余 与本批同法。
  • 批 F5 渲染族(~25Renderer/PlaybackCache/DynamicRenderer/ DraggableGizmo/OpenGLRenderer/Texture/Frame/ColorProcessor/ AudioWaveformSync/AudioSynchronizer。多数在 viewerdisplay、 manageddisplay、audiomonitor——playback/preview facade 族已存在 B9a-B9c),先查 oakengine/playback.hpreview.hrenderer.h 有无现成函数。AudioProcessor(5) 目标压到 4 后整体进豁免清单 (实时回调边界,v3 已预批)。
  • 批 F6 长尾(~351-symbol 类逐个过。*Task(3)、 RenderManager(2)、Sequence(2)、TimelineMarker(2) 等,多为 static_cast 或构造调用,facade 已有创建函数的直接换。

6. 验收(全部满足才算 R5 完成)

  1. nm -D cmake-build-debug/app/oak-editor | grep -c " U _ZN5olive" ≤ 6 且每个剩余符号都在 c-abi-migration-handoff.md §6.4 豁免清单里、 各带一句理由。
  2. nm -D 检查 oak-render-worker 为 0。
  3. 全量构建 0 error;全量 ctest --output-on-failure 绿(已知 flaky 规则:单独重跑一次,连续两次失败才算回归)。
  4. 反作弊审计:
    • git diff <R5起点>..HEAD --stat -- engine/ 逐文件过一遍,确认无 inline 化、无 stub(§2 两条红线的全量复核);
    • grep engine 头文件无新增非平凡函数体。
  5. 更新 facade-migration-roadmap.md 批次记录与 c-abi-migration-handoff.md 状态节;本文标注"已完成"。

7. 协作分工

  • 第 0/1 批(WIP 处置 + 缺陷修复)由 Kimi 执行——这些是语义陷阱, 需要逐行判断。
  • 批 F1-F6 由 DeepSeek 执行,Kimi 每批只读 review(不构建不运行), 记录问题,批间统一修。
  • 任何"消不掉"的符号:先写清尝试过的方案,再按 §6.4 格式进豁免清单, 由 Kimi 复核理由是否成立。