refactor(undo): de-Qt oakundo and wrap it in a pure C ABI
- strip QAbstractItemModel/QAction/signals-slots from UndoStack (UI concerns belong to the app layer), index_changed becomes a std::function callback - decouple UndoCommand from Project via an optional modified-flag callback pair - add pure C ABI in include/undo + src/undo/c_api (oakundo_ prefix, vtable-based command wrapper, OAKUNDO_E_* error codes) - fix three engine-side defects: jump(0) infinite loop, MultiUndoCommand child leak, push_pre_executed not undoable - add gtest suites (22 cases) and a standalone build driver
This commit is contained in:
@@ -103,3 +103,69 @@ Project* 本来就只是作为不透明身份被使用(修改标记归属)
|
||||
调用方知道影响,直接读状态)。
|
||||
- 往返测试:C API 与适配类各做一遍 push-undo-redo,状态一致。
|
||||
- `oakundo_debug_alive_count()`:init/free 配对无泄漏。
|
||||
|
||||
## 实施现状(2026-08-05)
|
||||
|
||||
M2 已落地并可独立构建、测试全绿。以下为与上文计划的实际差异。
|
||||
|
||||
### 最终目录结构
|
||||
|
||||
- `src/undo/src/` — 去 Qt 化 C++ 实现(`olive::` 命名空间,类名
|
||||
`UndoCommand`/`MultiUndoCommand`/`UndoStack` 不变),target
|
||||
`oakundo`(SHARED)。
|
||||
- `src/undo/c_api/` — 纯 C ABI 包装(`undocommand.cpp`、`undostack.cpp`
|
||||
+ 内部共享头 `commandhandle.h`),通过 `target_sources` 合并进
|
||||
`oakundo`,不单独成库。
|
||||
- `src/undo/tests/` — gtest,target `oakundo-gtest`,
|
||||
`gtest_discover_tests`。
|
||||
- `include/undo/`(仓库根)— 公共 C 头:`error.h`、`undocommand.h`、
|
||||
`undostack.h`。
|
||||
- `src/undo/standalone/CMakeLists.txt` — 独立构建 driver(见下)。
|
||||
|
||||
### 独立构建与测试
|
||||
|
||||
```sh
|
||||
cmake -S src/undo/standalone -B build-oakundo
|
||||
cmake --build build-oakundo -j
|
||||
ctest --test-dir build-oakundo --output-on-failure
|
||||
```
|
||||
|
||||
### 实际依赖
|
||||
|
||||
- Oak 内部:仅 oakcommon 的头文件宏(`define.h` 的
|
||||
`DISABLE_COPY_MOVE`),纯头文件,按 include 路径引用
|
||||
(`${OAK_REPO_ROOT}/src/common/src`),**不链接** oakcommon;
|
||||
不依赖 olivecore/ffmpeg_bridge。
|
||||
- 第三方:GTest(仅测试)。无 Qt。
|
||||
|
||||
### 与计划的主要差异
|
||||
|
||||
- 接口未按 §2 冻结清单逐条实现,而是对齐 oakcommon 的既有契约:
|
||||
C ABI 头放在仓库根 `include/undo/`,命名 `oakundo_<族>_<动词>`;
|
||||
查询类函数返回 int 错误码 + out 参数(计划中的
|
||||
`int64_t oakundo_undostack_count(...)` 直接返回值形式改为
|
||||
`int ...(OakUndoStack *, int64_t *out)`);字符串两段式 buffer 约定。
|
||||
- 句柄族名:`OakUndoCommand`/`OakUndoStack`;init/free 语义与
|
||||
oakcommon 一致(init 失败返回 NULL 且内部 try/catch 兜底,
|
||||
free(NULL) 为 no-op)。
|
||||
- §2.2 的 `OakUndoObjectParent`(QObject 挂载点)、
|
||||
`oakundo_undostack_update_actions`、`oakundo_undostack_undo_action/
|
||||
redo_action`(QAction 句柄)**未实现**:QAction/model 属 app UI 层,
|
||||
已从 UndoStack 剥离(见 notes.md「oakundo 去Qt化的删除与语义
|
||||
变更」)。
|
||||
- `index_changed` 事件:C ABI 无订阅接口(与 §2.2 修订一致);C++ 侧
|
||||
保留了 `set_index_changed_callback(std::function<void(int)>)`
|
||||
作为 Qt signal 的替代。
|
||||
- §3 切割点的处理与计划不同:`get_relevant_project()` 未改成返回
|
||||
`void *`,而是整体删除,修改标记语义改为
|
||||
`UndoCommand::set_modified_callbacks(is_modified, set_modified)`
|
||||
回调对(oakundo 完全不认识 Project;M3 node 适配层绑定
|
||||
`Project::is_modified/set_modified`)。
|
||||
- 未实现 `oakundo_debug_alive_count()` 泄漏计数。
|
||||
- 额外行为修复:`UndoStack` 析构不再经 `clear()`(原实现在析构时会
|
||||
再 push 一个 EmptyCommand 造成泄漏),改为直接删除持有命令;
|
||||
`jump` 增加 `can_undo/can_redo` 守卫(原版 `jump(0)` 死循环);
|
||||
`MultiUndoCommand` 析构删除子命令(原版泄漏);
|
||||
`push_pre_executed` 通过新增的 `UndoCommand::set_done()` 置完成
|
||||
标记(原版入栈后 undo 为空操作)。详见 notes.md。
|
||||
- 测试结果:22 个用例全部通过(独立构建 `build-oakundo`)。
|
||||
|
||||
@@ -80,3 +80,49 @@ oakcommon;后续若确认无用途可直接删除。
|
||||
- `FFmpegUtils`:新增类内常量 `k_rgb_channel_count/k_rgba_channel_count`
|
||||
(3/4)取代对 `VideoParams` 同名常量的引用,消除 common→render
|
||||
反向依赖。
|
||||
|
||||
## oakundo 去Qt化的删除与语义变更(2026-08-05)
|
||||
|
||||
`src/undo/`(olive::UndoCommand / MultiUndoCommand / UndoStack)去Qt化
|
||||
过程中以下删除与语义变化,迁移调用方时需注意:
|
||||
|
||||
- `UndoStack` 不再继承 `QAbstractItemModel`(删除 `Q_OBJECT`、
|
||||
`columnCount/data/index/parent/rowCount/headerData/hasChildren` 全部
|
||||
model overrides 及 `begin/end*Rows` 通知)。历史面板(QTreeView 等)
|
||||
属 app UI 层,迁移时应以 facade 的行式查询
|
||||
(command_count/done_count/command_name/command_is_done)自行实现
|
||||
model。
|
||||
- `UndoStack::GetUndoAction/GetRedoAction/update_actions`(QAction 成员
|
||||
及其文本/使能维护)删除——QAction 属 app 菜单层,app 应自行创建
|
||||
action 并连接 undo/redo;`index_changed(int)` signal 改为 C++ 侧
|
||||
`std::function<void(int)>`(`set_index_changed_callback`),C ABI
|
||||
不暴露事件,调用方在变更命令后读 `oakundo_undostack_index`。
|
||||
- `UndoCommand` 与 `olive::Project` 解耦:删除纯虚
|
||||
`get_relevant_project()`(含 MultiUndoCommand/EmptyCommand 的
|
||||
override)与 `project_` 成员;`redo_and_set_modified/
|
||||
undo_and_set_modified` 的修改标记语义改为可选回调对
|
||||
`set_modified_callbacks(is_modified, set_modified)`:redo 时记录当前
|
||||
修改标记并置 true,undo 时恢复记录值;不挂回调则退化为纯
|
||||
redo/undo。读写顺序与 Qt 版一致(redo_now() 之后读取并置位),
|
||||
行为对齐。Project 侧迁移时在适配层把 `Project::is_modified/
|
||||
set_modified` 绑到这对回调即可。
|
||||
- `UndoStack` 析构不再调用 `clear()`(Qt 版在析构里 clear 会再 push
|
||||
一个 EmptyCommand 造成泄漏),改为直接删除所有持有命令。
|
||||
- 顺带修复三处 engine 原版就存在的缺陷(迁移调用方无需适配,但
|
||||
行为与旧版不同,特此记录):
|
||||
- `jump(0)` 死循环:底部 EmptyCommand 不可 undo,`jump` 现以
|
||||
`can_undo()/can_redo()` 为守卫,跳到 0 时停在 index 1;
|
||||
- `MultiUndoCommand` 原来不拥有子命令(析构泄漏),现析构删除
|
||||
全部 children;
|
||||
- `push_pre_executed` 原来不置 `done_` 标记,导致入栈后 undo
|
||||
为空操作(facade undo-group 的组撤销实际不生效),现入栈前
|
||||
`set_done(true)`(UndoCommand 新增 `set_done()`)。
|
||||
- `clear()` 推入的占位命令名固定为字面量 "New/Open Project"(原
|
||||
`tr()` 翻译移除,翻译由 app 层负责)。
|
||||
- `push/push_pre_executed` 的命令名由 `QString` 改为 `std::string`;
|
||||
C ABI 中 NULL name 视同空串。
|
||||
- C ABI 句柄语义:`oakundo_command_init/init_multi` 返回的句柄拥有
|
||||
底层命令;push 或 `oakundo_command_multi_add_child` 成功后所有权
|
||||
转移、wrapper 被消费(不可再用/再 free);
|
||||
`oakundo_command_multi_child` 返回 borrowed wrapper(free 只释放
|
||||
wrapper)。
|
||||
|
||||
Reference in New Issue
Block a user