- riir.md: split project file read/write into standalone oakstorage module (pluggable backend, DB-replaceable); add Mermaid data flow diagram of all modules; adjust batch table and milestones - riir/04-interfaces.md: new provides/consumes contract matrix for all modules - riir/M10-oakstorage.md: new manual with frozen C API (typed opaque handles, manual vtable for storage backends, URI addressing) - riir/01: codify pure-C OO interface rules (typed handles instead of void*, init/free pairs, manual vtables, no C++ objects or member calls across shared library boundaries); upper layers issue commands only, no module-to-module event subscriptions (async task callbacks excepted) - sync 00/02/03 and M2-M9 manuals: remove subscribe APIs, replace void* with typed handles, update tests accordingly
106 lines
4.8 KiB
Markdown
106 lines
4.8 KiB
Markdown
# M2 · oakundo 拆分手册
|
||
|
||
> 内容:`engine/undo/`(UndoCommand、MultiUndoCommand、UndoStack)。
|
||
> 依赖:common 2、node/project.h **1 处**(undocommand.cpp)。
|
||
> 被依赖:node 4、timeline 1、render 1、plugin 2、capi 14。
|
||
> 拆分顺序第 2 位(近叶子)。
|
||
|
||
## 1. 目标形态
|
||
|
||
```
|
||
oakundo/
|
||
include/oakundo/{undocommand.h, undostack.h, types.h, export.h}
|
||
src/
|
||
tests/ # oakundo_gtest
|
||
```
|
||
|
||
## 2. 冻结 C API
|
||
|
||
### 2.1 `oakundo/undocommand.h`
|
||
|
||
```c
|
||
typedef struct OakUndoCommand OakUndoCommand;
|
||
|
||
/* 回调式命令(app 与跨模块命令的统一载体) */
|
||
typedef void (*oakundo_command_fn)(void *userdata);
|
||
OAKUNDO_API OakUndoCommand *oakundo_command_create(
|
||
const char *name, oakundo_command_fn redo, oakundo_command_fn undo,
|
||
oakundo_command_fn free_fn, void *userdata);
|
||
OAKUNDO_API OakUndoCommand *oakundo_command_create_multi(void);
|
||
OAKUNDO_API int oakundo_command_multi_add_child(OakUndoCommand *multi,
|
||
OakUndoCommand *child);
|
||
OAKUNDO_API int oakundo_command_multi_child_count(
|
||
const OakUndoCommand *multi);
|
||
OAKUNDO_API void oakundo_command_redo_now(OakUndoCommand *cmd);
|
||
OAKUNDO_API void oakundo_command_undo_now(OakUndoCommand *cmd);
|
||
OAKUNDO_API void oakundo_command_free(OakUndoCommand *cmd); /* NULL no-op */
|
||
```
|
||
|
||
### 2.2 `oakundo/undostack.h`
|
||
|
||
```c
|
||
typedef struct OakUndoStack OakUndoStack;
|
||
typedef struct OakUndoObjectParent OakUndoObjectParent; /* borrowed QObject 挂载点 */
|
||
OAKUNDO_API OakUndoStack *oakundo_undostack_init(
|
||
const OakUndoObjectParent *parent);
|
||
/* OakUndoObjectParent:QObject 父子树挂载点的 borrowed 不透明句柄
|
||
* (2026-08 修订:按 01 §0.1 由 void* 改为有类型句柄;
|
||
* 提供侧内部即 QObject*,消费侧不可解引用;可传 NULL 表无父) */
|
||
OAKUNDO_API void oakundo_undostack_free(OakUndoStack *self);
|
||
|
||
OAKUNDO_API void oakundo_undostack_push(OakUndoStack *self,
|
||
OakUndoCommand *cmd, const char *name);
|
||
/* push_pre_executed:子命令已 redo 过,入栈不重复 redo(undo 分组用) */
|
||
OAKUNDO_API void oakundo_undostack_push_pre_executed(OakUndoStack *self,
|
||
OakUndoCommand *cmd, const char *name);
|
||
|
||
OAKUNDO_API int oakundo_undostack_can_undo(const OakUndoStack *self);
|
||
OAKUNDO_API int oakundo_undostack_can_redo(const OakUndoStack *self);
|
||
OAKUNDO_API void oakundo_undostack_undo(OakUndoStack *self);
|
||
OAKUNDO_API void oakundo_undostack_redo(OakUndoStack *self);
|
||
OAKUNDO_API void oakundo_undostack_jump(OakUndoStack *self, int64_t index);
|
||
OAKUNDO_API void oakundo_undostack_clear(OakUndoStack *self);
|
||
OAKUNDO_API int64_t oakundo_undostack_count(const OakUndoStack *self);
|
||
OAKUNDO_API int64_t oakundo_undostack_index(const OakUndoStack *self);
|
||
OAKUNDO_API int oakundo_undostack_command_text(OakUndoStack *self,
|
||
int64_t row, char *buf, int buf_size);
|
||
OAKUNDO_API int oakundo_undostack_command_is_done(OakUndoStack *self,
|
||
int64_t row);
|
||
OAKUNDO_API void oakundo_undostack_update_actions(OakUndoStack *self);
|
||
/* QAction* 句柄(GUI 菜单绑定用,borrowed) */
|
||
OAKUNDO_API void *oakundo_undostack_undo_action(OakUndoStack *self);
|
||
OAKUNDO_API void *oakundo_undostack_redo_action(OakUndoStack *self);
|
||
```
|
||
|
||
**无事件接口(2026-08 修订)**:上层对下层只有命令。push/undo/redo/jump
|
||
的调用方知道栈索引的变化(`oakundo_undostack_index` 调用后即可读),
|
||
`index_changed` 通知改由**调用方所在层**(facade 的 undo 适配层)在
|
||
每次变更命令后自行发出——oakundo 不持有任何上层回调。
|
||
|
||
## 3. 切割点(1 处)
|
||
|
||
`undocommand.cpp` include `node/project.h`(`get_relevant_project()`
|
||
虚函数的 Project 类型)。
|
||
处理:`UndoCommand::get_relevant_project()` 的返回类型在 oakundo
|
||
内部改为不透明 `void *`(oakundo 不解释它);node 侧(M3)在自己的
|
||
适配层把 `void *` 与 `olive::Project *` 互转。**不改语义**——
|
||
Project* 本来就只是作为不透明身份被使用(修改标记归属)。
|
||
|
||
## 4. 消费侧适配(按 01 §2)
|
||
|
||
- `node/`(4 处)、`timeline/`(1)、`render/`(1)、`plugin/`(2):
|
||
各自放 `adapter/undocommand.h`,转发用到的方法子集。
|
||
- `src/capi/undo.cpp`(14 处):facade 的 undo 族实现改为转发
|
||
oakundo C API(或保持 facade 直链 oakundo——facade 是装配层,
|
||
裁决见 M9 §4,默认直链不绕圈)。
|
||
|
||
## 5. 测试(映射 03 §2/§3)
|
||
|
||
- 每条 API 正常+错误路径(NULL self、空 push 不入栈——
|
||
空 MultiUndoCommand 被删除的既有行为必须有 TEST 钉死)。
|
||
- push/undo/redo/jump/clear 全序列;command_text/is_done 边界行;
|
||
每步后 `oakundo_undostack_index` 读数与预期一致(替代原事件断言——
|
||
调用方知道影响,直接读状态)。
|
||
- 往返测试:C API 与适配类各做一遍 push-undo-redo,状态一致。
|
||
- `oakundo_debug_alive_count()`:init/free 配对无泄漏。
|