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).
12 KiB
R5 终局计划:181 → 豁免清单(≤6)
面向执行者(DeepSeek Flash),自包含。工作分支:
c-abi-migration。 前置文档:facade-migration-roadmap.md(批次记录)、r5-app-migration-guide.md(R5 总指引)、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)。已完成:F3(Task/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、每批立即提交。
新增:
- 禁止 inline 化 engine 实现刷符号。把 engine 的
.cpp实现搬进头文件 (如本次 WIP 对ManagedColor、TimelineWorkArea做的那样)不会让 依赖消失,只是让 app 内联编译 engine 代码——C ABI 边界被架空,nm 数字 是假的。判定方法:engine 目录下的任何.h出现新的非平凡函数体即违规。 - 禁止 no-op stub。
load()返回 true、save()空体、// apply ...空循环这类"假成功"是最严重违规(本次 WIP 的TimelineWorkArea::load/save即为例:项目文件的工作区会全部丢失)。 任何行为变更必须在提交信息里写明并接受 review。 - 禁止 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.h、engine/render/managedcolor.cpp、engine/render/CMakeLists.txt(inline 化,红线 1)engine/timeline/timelineworkarea.h、engine/timeline/timelineworkarea.cpp、engine/timeline/CMakeLists.txt(inline 化 + 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.cpp、
keyframeview.cpp、resizabletimelinescrollbar.{h,cpp}。修三处:
nodeview.cpp::copy_selected:char buf[65536]固定缓冲会截断大节点图 的 XML。改两段式:先oakengine_clipboard_save_to_xml(cb, nullptr, 0)取所需长度,再QByteArray(len+1, '\0')分配写入。全仓库同类 buf/size 调用(oakengine_project_filename等 512/256 定长)一并不再 扩大,仅本处改两段式(XML 体积无上限,文件名有)。resizabletimelinescrollbar.cpp::connect_work_area:已改为oakengine_event_subscribe(方向对,解决了直连 engine 信号),但裸 订阅的 userdata 是this,必须在析构里oakengine_event_unsubscribe两个 id,否则 widget 先死、workarea 后发事件即悬垂回调。nodeparamview.cpp::paste里的空循环// apply position from map: 原代码同样是建了PositionMap未使用(上游 Olive 遗留死代码),不算 回归,但既然碰了就删掉这个死块,别留占位注释。
闭环后提交(提交信息注明:clipboard 迁移 + 三处修复 + engine 回退)。
4. 第 1 批:已记录缺陷修复(review 累积清单,决策已写死)
按序修,一个提交;每项都给出现象与定死的修法:
- NodeParamView::DeleteSelected 重连静默失败(严重)。
现码先
oakengine_node_connect后oakengine_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。 - ProjectViewModel 切项目丢事件(严重)。
set_project重建bridge_后没重连 4 个 folder 信号。修法:把构造里的 4 个connect(bridge_, ...)抽成私有connect_bridge_signals(), 构造函数与set_project重建后都调。 - NodeParamViewWidgetBridge dragger 泄漏。类无析构,
oakengine_dragger_create的句柄永不 free。修法:加析构调oakengine_dragger_free(dragger_)。 - TimeBasedWidget 旧订阅泄漏。
connect_viewer_node只disconnect(bridge_, nullptr, this, nullptr),engine 侧订阅不释放。 修法:EngineEventBridge加unsubscribe_all()(对subscriptions_逐个 unsubscribe 并清空),在 disconnect 旁调用。 - 边-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。 - seekablewidget marker 订阅泄漏。
set_markers建 3 个订阅 (ADDED/REMOVED/MODIFIED)只存 1 个 id。修法:QVector<int64_t> marker_subs_存全 3 个,重设/析构全解(对齐resizabletimelinescrollbar.cpp::connect_markers的正确模式)。 - core_params 0x1 陷阱 + core.h 死代码。
app/core.cpp:1509Core::core_params()解引用0x1;app/core.h:337残留EngineCore *engine_core_+#include "coreengine.h"+ 5 个空操作 handler setter。修法:全删(core_params 无调用方,直接删方法)。 - 小项打包:
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.cppimage_sequence_check_box_changed的裸{ }块缩进 乱——clang-format 归位。
5. 第 2+ 批:符号收尾(按簇,难度从低到高)
每批做法相同:grep 定位引用源 → 按既有模式迁移(facade 函数 /
cliphandle.h 式 app 侧 inline 适配头 / CustomUndoCommand 回调 /
EngineEventBridge 订阅)→ 闭环提交。禁止走 §2 两条红线的捷径。
- 批 F1 撤销命令族(~30,25 个类):多数已有 facade 等价物直接换
(
NodeEdgeAddCommand→oakengine_node_connect,NodeEdgeRemoveCommand→oakengine_node_disconnect_ex,Marker 五命令 →oakengine_marker_*族)。无等价物的按a86cb6c99的oakengine_undo_command_create回调模式迁移。TrackListRippleToolCommand按 v3 §176 行处理:先尝试现有 timeline 原语组合,不行设计oakengine_tracklist_ripple_*小族,再不行写理由进豁免清单。 - 批 F2 Track/ClipBlock/NodeGroup/NodeKeyframe(30):属性访问走
trackhandle.h/cliphandle.h模式扩展;信号走 EngineEventBridge。 - 批 F3 Task/Project/ViewerOutput/VideoParams(18):剩余多为
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 渲染族(~25):Renderer/PlaybackCache/DynamicRenderer/
DraggableGizmo/OpenGLRenderer/Texture/Frame/ColorProcessor/
AudioWaveformSync/AudioSynchronizer。多数在 viewerdisplay、
manageddisplay、audiomonitor——playback/preview facade 族已存在
(B9a-B9c),先查
oakengine/playback.h、preview.h、renderer.h有无现成函数。AudioProcessor(5) 目标压到 4 后整体进豁免清单 (实时回调边界,v3 已预批)。 - 批 F6 长尾(~35):1-symbol 类逐个过。
*Task(3)、RenderManager(2)、Sequence(2)、TimelineMarker(2) 等,多为 static_cast 或构造调用,facade 已有创建函数的直接换。
6. 验收(全部满足才算 R5 完成)
nm -D cmake-build-debug/app/oak-editor | grep -c " U _ZN5olive"≤ 6, 且每个剩余符号都在c-abi-migration-handoff.md§6.4 豁免清单里、 各带一句理由。nm -D检查oak-render-worker为 0。- 全量构建 0 error;全量
ctest --output-on-failure绿(已知 flaky 规则:单独重跑一次,连续两次失败才算回归)。 - 反作弊审计:
git diff <R5起点>..HEAD --stat -- engine/逐文件过一遍,确认无 inline 化、无 stub(§2 两条红线的全量复核);- grep engine 头文件无新增非平凡函数体。
- 更新
facade-migration-roadmap.md批次记录与c-abi-migration-handoff.md状态节;本文标注"已完成"。
7. 协作分工
- 第 0/1 批(WIP 处置 + 缺陷修复)由 Kimi 执行——这些是语义陷阱, 需要逐行判断。
- 批 F1-F6 由 DeepSeek 执行,Kimi 每批只读 review(不构建不运行), 记录问题,批间统一修。
- 任何"消不掉"的符号:先写清尝试过的方案,再按 §6.4 格式进豁免清单, 由 Kimi 复核理由是否成立。