refactor(node): de-Qt oaknode and wrap it in a pure C ABI

- copy engine/node (188 files) to src/node/src, de-Qt in waves:
  core infra (Node/Param/Value/Variant/mathtypes), project/serializer,
  block/output, color, effect leaves, generator, gizmo/plugins
- strip QObject/signals/slots: notifications move to the facade's
  oakengine_event channel, ownership becomes explicit (unique_ptr,
  add_keyframe/add_gizmo), sender() replaced by current_gizmo
- QVariant replaced by olive::Variant, Qt math types by POD mathtypes,
  QXmlStreamReader/Writer by oakcommon's expat-based classes
- sink VideoParams/SubtitleParams/LoopMode/ColorTransform to oakcommon
  (M3.5); polygon/text rasterization behind backend hooks
- pure C ABI in include/node + src/node/c_api (oaknode_ prefix,
  OAKNODE_E_* codes, undoable variants take OakUndoCommand out-params)
- fix Project::clear() root_ reset + disconnect assert, Sequence
  TrackList leak
- 96 gtest cases green in standalone build (build-oaknode)
- docs: signal/slot handling strategy + M3 implementation status
This commit is contained in:
2026-08-05 23:55:54 +08:00
parent c50017127b
commit d77348ad9f
375 changed files with 59682 additions and 1 deletions
+92
View File
@@ -80,3 +80,95 @@ M3 阶段判据(放宽版):oaknode 目录就位、C API 实现、oaknode_g
- 枚举序数:NodeValue::Type ⇄ oak_node_value_type 映射表(已在
nodevaluehandle.h 钉过一次,oaknode 测试再钉一次,防两侧漂移)。
- `oaknode_debug_alive_count()` 泄漏断言。
## 实施现状(2026-08-05)
M3 已落地并可独立构建、测试全绿(96 个用例全部通过,无 skip)。
以下为与上文计划的实际差异。
### 最终目录结构
- `src/node/src/` — 去 Qt 化 C++ 实现(`olive::` 命名空间),target
`oaknode`(SHARED);平铺结构,`src/node/src` 为 include 根,
模块内 include 无前缀(`"value.h"`、`"block/block.h"`)。
- `src/node/c_api/` — 纯 C ABI 包装(node/group/keyframe/factory/
traverser/project/folder/footage/serializer/block/track/sequence/
colormanager 共 13 个 .cpp + 内部头 `valueconvert.h`/`alivecount.h`),
通过 `target_sources` 合并进 `oaknode`,不单独成库。
- `src/node/tests/` — gtest,单一 target `oaknode-gtest`(13 个
_test.cpp + 共享夹具 `testnode.h`),`gtest_discover_tests`
(`DISCOVERY_MODE PRE_TEST`)。
- `include/node/`(仓库根)— 公共 C 头:`error.h` + node/group/
keyframe/factory/traverser/project/folder/footage/serializer/block/
track/sequence/colormanager.h。
- `src/node/standalone/CMakeLists.txt` — 独立构建 driver(见下)。
- `src/node/DEQT.md` — 去Qt化替换约定与逐波次裁决记录。
- `src/node/transition/` — 过渡 stub 头(见「实际依赖」)。
### 独立构建与测试
```sh
cmake -S src/node/standalone -B build-oaknode
cmake --build build-oaknode -j
ctest --test-dir build-oaknode --output-on-failure
```
driver 照 src/common/standalone 模式:EXPAT/OpenColorIO/OpenImageIO
用 Homebrew 的 config 包(`find_package(... CONFIG)`)并映射到
`${OCIO_LIBRARIES}` 等变量;`add_subdirectory` 引入真实 in-repo
target(core→olivecore、ffmpeg_bridge、src/undo→oakundo、
src/common→oakcommon,各自 BUILD_TESTS 关闭),不再链接预构建
dylib;禁用 OpenTimelineIO(`/opt/otio` 的 `@loader_path` 问题,
oaknode 不需要)。
### 实际依赖
- Oak 内部:oakcommon(XML/Current/工具)、oakundo(UndoCommand/
UndoStack)、olivecore(`olive::core::Rational/Color/Bezier` 等
C ABI 包装,真实符号)、ffmpeg_bridge(经 oakcommon 间接)。
- 第三方:EXPAT、OpenColorIO、OpenImageIO、Imath(头)、FFmpeg
(经 ffmpeg_bridge 间接)、GTest(仅测试)。
- **transition stub 机制**(裁决 A):对尚未拆分的
render/codec/timeline/audio/pluginSupport 模块的引用允许悬空——
头文件由 `src/node/transition/` 的过渡 stub/转发头提供(engine 头
仍是 Qt 版),符号经 `-undefined dynamic_lookup`(macOS)留到
运行时解析。测试进程启动时必须能解析这些符号:oaknode-gtest
链接真实 target(olivecore/oakcommon/oakundo + OCIO/OIIO/Imath
dylib)并 `-Wl,-force_load` 预构建的
`build/third_party/openfx/HostSupport/libOfxHost.a`(OFX 符号与
typeinfo,否则二进制启动即崩,PRE_TEST 发现模式也会挂;路径用
`find_library` 定位,可由 `OAKNODE_OFX_HOST_ARCHIVE` 覆盖)。
- ColorManager 构造时需要有效 OCIO 配置(`:/ocioconf` qrc 提取是
Qt 资源遗留、必然失败):ctest 经 `ENVIRONMENT OCIO=...` 指向
`engine/render/ocioconf/config.ocio`,colormanager_test 另用
`OAK_OCIO_TEST_CONFIG` 编译定义兜底。
### 与冻结 C API 的主要差异
- 函数族命名与约定照 oakcommon/oakundo 既有契约:`oaknode_<族>_<动词>`,
int 错误码 + out 参数,字符串两段式 buffer;undoable 变体成对
(`_undoable` 后缀,部分经 `OakUndoCommand *` 尾参)。
- §2 冻结表中**跳过/未实现**的函数族:
- Sequence 的 workarea/markers 族(timeline 边界类型
TimelineMarker/TimelineWorkArea 未拆,留 M4);
- ProjectSerializer 的落盘 save/load(按计划迁 oakstorage M10),
本模块只实现内存形态 `save_to_xml`/`load_from_xml` + SaveData/
LoadData 句柄;
- Node 的 `oak_node_value` 句柄族未单独成族,输入值经
`oaknode_node_get/set_input[_string][_undoable]` 直接收发;
- 无任何事件订阅接口(与 §2 特殊约定 2 一致)。
- `oaknode_debug_alive_count()` 泄漏断言已实现并在测试中使用。
### 已知问题
- `NodeGroupAddInputPassthrough` 的上游 bug 原样保留(未修,待裁决)。
- polygon/text 光栅化后端钩子(PathFillBackend/TextMeasureBackend/
TextRenderBackend)未安装前输出空白;footage 离线警示帧丢失文字
叠层;track 默认高度固化为 13px——均为被迫行为差异,见
`notes.md`「oaknode 去Qt化的删除与语义变更」。
- 去Qt化顺带修了三个上游 bug(行为与旧版不同):
`Project::clear()` 重置 `root_`(可重新 initialize)、clear 删除
顺序修正(不再触发 disconnect_edge 的 parent assert)、
`Sequence` 析构删除三个 `TrackList`(原泄漏)。详见 notes.md。
- 库本体对 transition stub 模块的符号悬空在 M7/M9 复核前是预期
状态;禁止新增对 render/codec/timeline/audio/pluginSupport 的引用。
+87
View File
@@ -126,3 +126,90 @@ oakcommon;后续若确认无用途可直接删除。
转移、wrapper 被消费(不可再用/再 free);
`oakundo_command_multi_child` 返回 borrowed wrapper(free 只释放
wrapper)。
## oaknode 去Qt化的删除与语义变更(2026-08-05)
`src/node/`(Node 族、Project 族、Sequence/Track/Block 族、serializer、
ColorManager)去Qt化过程中的删除与语义变化,迁移调用方时需注意
(详细约定见 `src/node/DEQT.md` §4/§7):
- 信号槽整组删除:Node 的 28 个信号(label_changed、input_connected、
keyframe_added、added_to_graph 等)、NodeKeyframe 的 5 个、
Project/Folder/Sequence/Footage 的全部信号(name_changed、
modified_changed、track_added、begin/end_insert_item 等),变更通知
统一由 facade 经 `oakengine_event` 发出,oaknode 不持有上层回调。
- QObject 父子生命周期改显式所有权:Project 持有全部节点
(`add_node`/`remove_node`,`clear()`/析构删除);nodeundo 用
`std::unique_ptr<Node>` 替代 `memory_manager_`;keyframe 经
`Node::add_keyframe()/remove_keyframe()` 显式注册(自动 set_parent),
gizmo 经 `Node::add_gizmo()/remove_gizmo()` 显式注册(构造不再自
注册);`NodeInputImmediate::delete_all_keyframes(reclaimed)` 替代
"reparent 续命"。
- polygon/text 的 `generate_frame()` 光栅化(QPainter/QTextDocument)
改后端钩子:`PathFillBackend`(geometry.h)、`TextMeasureBackend`/
`TextRenderBackend`(textbackend.h),facade 未安装前输出空白
(被迫行为差异)。
- gizmo 绘制与 hit-test 归 app 层:各 gizmo 的 `draw(QPainter*)`、
`PointGizmo::get_clicking_rect()/get_drawing_rect()` 等删除;
`DraggableGizmo` 的拖拽信号改直调 `parent_node()->gizmo_drag_*()`;
drag 回调里的 `sender()` 由 `Node::current_gizmo()` 替代。
- footage 离线媒体警示帧:QImage/QPainter 改纯像素循环(深红底+斜纹),
"Media Offline" 文字叠层与抗锯齿丢失(被迫行为差异)。
- `:/ocioconf` Qt 资源路径不可用:ColorManager 默认配置的 qrc 提取必然
失败,需 `OCIO` 环境变量指向磁盘 config.ocio(测试用
`engine/render/ocioconf/config.ocio`)。
- `NodeGroupAddInputPassthrough` 的上游 bug 原样保留(去Qt化不改行为
逻辑,待单独裁决修复)。
- track.h 的 `QFontMetrics` 13px 常量:轨道默认高度原依赖
`QFontMetrics(qApp->font()).height()`,去Qt后固化为 13px 字面量,
换字体/DPI 不再自适应。
- `Footage::check_footage()` 的 QTimer 周期回调删除,函数本体保留为
public 由 facade 周期调用;`qApp->activeWindow()` 门槛移到 app 层。
- 序列化 XML 元素/属性名与读写顺序逐字节不变;`XmlStreamWriter` 输出
紧凑 XML(无声明无缩进);OVEC 压缩段保持 qCompress 兼容
(zlib + 4 字节大端长度)。
- 修复三个上游真实 bug(行为因此与旧版不同,特此记录):
`Project::clear()` 现重置 `root_`(clear 后可再次 initialize);
clear 不再先 `set_parent(nullptr)` 再 delete(否则 ~Node 的
disconnect_edge 触发 parent 不等的 assert);
`Sequence` 新增析构删除三个 `TrackList`(原来构造 new 后泄漏)。
- `sender()` 的其他替代:原 keyframe signal→Node 的 5 条失效通知链
(invalidate_from_keyframe_*)函数本体保留为 public 成员,带
`NodeKeyframe *key` 显式参数,待 facade 接线。
## 信号与槽的处理策略(2026-08-05)
去Qt化对 Qt 信号槽的统一处理模式,M1–M3 一致执行,后续模块照此:
1. **模块内通知改显式回调/直调(std::function)**:通知双方都在同一
模块内时,去掉 connect,改为 `std::function` 回调注册或成员直调。
例:oakundo `UndoStack::index_changed(int)` signal →
`set_index_changed_callback(std::function<void(int)>)`;oaknode
`Node::current_gizmo()/set_current_gizmo()` 机制替代 gizmo drag
回调里的 `sender()`;`DraggableGizmo` 的 handle_start/handle_movement
信号改直调 `parent_node()->gizmo_drag_start()/gizmo_drag_move()`;
TrackList 原 track_list_changed/length_changed 发射点直调
`sequence->update_track_cache()/verify_length()`。
2. **跨层/跨界通知删除,由 facade 经 oakengine_event 统一发出**:
一切指向 app/UI/其他模块的信号整组删除(Node 28 个、NodeKeyframe
5 个、Project/Folder/Sequence/Footage 全部),oaknode 自身不持有
任何上层回调;facade 在执行命令后经既有 `oakengine_event` 通道发
通知(事件 id 沿用 `oakengine/events.h` 70-95 段,值不变)。依据:
oaknode 的所有修改都经命令函数完成,调用方知道影响(M3 §2 特殊
约定 2,04 §3)。
3. **QObject 父子生命周期改显式所有权**:`setParent`/childEvent 机制
删除,所有权用显式容器与注册函数表达。例:Project 持有节点
(`add_node`/`remove_node`/`clear()`);`ColorManager` 由
`std::unique_ptr` 持有;nodeundo 用 `std::unique_ptr<Node>` 替代
`memory_manager_`;keyframe/gizmo 分别经 `add_keyframe()`/
`add_gizmo()` 显式注册;UndoCommand 树由 MultiUndoCommand 析构
删除 children。
4. **sender() 的替代**:两种形态——拖曳场景用
`Node::current_gizmo()`(DraggableGizmo 直调回调前后
`set_current_gizmo(this/nullptr)` 包一层);通知链场景改显式参数,
如 `invalidate_from_keyframe_*(NodeKeyframe *key)`。
5. **原 slot 函数本体保留为 public 成员,待 facade 接线**:signal 删了
但 slot 承载的业务逻辑不删,改为 public 成员函数由上层直调。清单
见 `src/node/DEQT.md` §7(如 `Sequence::update_track_cache()`、
`Footage::check_footage()/default_color_space_changed()/
proxy_ready()/proxy_finished()`、5 条 invalidate_from_keyframe_*)。