docs(riir): design inter-module C ABI interfaces and split out oakstorage
- 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
This commit is contained in:
+102
-7
@@ -147,11 +147,11 @@
|
|||||||
│
|
│
|
||||||
liboakengine-facade(壳:capi + 事件 + init)
|
liboakengine-facade(壳:capi + 事件 + init)
|
||||||
│
|
│
|
||||||
┌────────┬────────┼─────────┬──────────┐
|
┌────────┬────────┼─────────┬──────────┬──────────────┐
|
||||||
oaktask oakrender oakplugin oakaudio oakserialize
|
oaktask oakrender oakplugin oakaudio oakserialize oakstorage
|
||||||
│ │ │ │ │
|
│ │ │ │ │ (工程文件读写,
|
||||||
└────────┴────┬───┴──────────┴──────────┘
|
└────────┴────┬───┴──────────┴──────────┘ 独立模块,未来
|
||||||
│
|
│ 替换为数据库)
|
||||||
oakmodel(节点图 + 项目模型 + 时间线模型)
|
oakmodel(节点图 + 项目模型 + 时间线模型)
|
||||||
│
|
│
|
||||||
┌────────┼─────────┐
|
┌────────┼─────────┐
|
||||||
@@ -160,6 +160,99 @@
|
|||||||
ffmpeg_bridge(已是 C ABI .so)
|
ffmpeg_bridge(已是 C ABI .so)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**oakstorage 单列说明(本计划对原模块图的唯一结构性修改)**:
|
||||||
|
工程文件的读写(`node/project/serializer` 的落盘路径 + `task/project/`
|
||||||
|
load/save/loadotio/saveotio 的文件 IO)从 oakserialize / oaktask 中**单独拆出**为
|
||||||
|
oakstorage 模块。它对上只暴露**存储后端无关**的 C ABI(打开/保存/探测工程,
|
||||||
|
URI 寻址),当前唯一后端是 XML .ove 文件;**未来替换为数据库时只新增一个
|
||||||
|
后端实现,上层(oaktask/facade)零改动**。剪贴板序列化(copy/paste)不属于
|
||||||
|
存储,仍留在 oakserialize。详细接口设计见 `riir/04-interfaces.md` 与
|
||||||
|
`riir/M10-oakstorage.md`。
|
||||||
|
|
||||||
|
### 3.1.1 模块数据流图(Mermaid)
|
||||||
|
|
||||||
|
下图描述终态各模块之间的调用与数据流向。**实线 = 命令调用(上层→下层,
|
||||||
|
内部 C ABI `oak<mod>_*`,调用方知道影响);虚线 = 仅两种允许的反向通知:
|
||||||
|
facade→app 的 `oakengine_event` 通道,以及异步任务(oaktask 任务 /
|
||||||
|
渲染 ticket / GPU 帧完成)的完成回调。下层对上层没有事件订阅。**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph 消费侧["消费侧(不感知实现语言)"]
|
||||||
|
APP["oak-editor (app)"]
|
||||||
|
CLI["oak-cli"]
|
||||||
|
WRK["oak-render-worker"]
|
||||||
|
end
|
||||||
|
|
||||||
|
FACADE["liboakengine-facade<br/>壳:oakengine_* 公共 C ABI(冻结)<br/>+ 事件注册表 + init/shutdown"]
|
||||||
|
|
||||||
|
subgraph 引擎模块["引擎模块(facade 之下,只经内部 C ABI 互调)"]
|
||||||
|
TASK["oaktask<br/>任务编排(Task/TaskManager)"]
|
||||||
|
RENDER["oakrender<br/>渲染管线/缓存/色彩"]
|
||||||
|
PLUGIN["oakplugin<br/>OpenFX 宿主"]
|
||||||
|
AUDIO["oakaudio<br/>音频 DSP/输出/电平"]
|
||||||
|
SERIAL["oakserialize<br/>节点图 XML 序列化<br/>(剪贴板 copy/paste,不落盘)"]
|
||||||
|
STORAGE["oakstorage<br/>工程持久化(URI 打开/保存/探测)<br/>后端可插拔:ove-xml 文件|未来 oakdb 数据库"]
|
||||||
|
UNDO["oakundo<br/>UndoStack/UndoCommand"]
|
||||||
|
MODEL["oakmodel<br/>Node 类型簇 + Project/Sequence/Track/Block<br/>(最大不可拆分类型簇)"]
|
||||||
|
CODEC["oakcodec<br/>decoder/encoder/conform/proxy"]
|
||||||
|
CORE["liboakcore<br/>rational/timecode/bezier/samplebuffer"]
|
||||||
|
BACKEND["oakbackend<br/>GPU 插件:oakgl / oakvulkan / 未来 Rust(wgpu)"]
|
||||||
|
FFMPEG["ffmpeg_bridge<br/>FFmpeg 的 C ABI 桥(现成 .so)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
APP -->|"oakengine_* 调用"| FACADE
|
||||||
|
CLI --> FACADE
|
||||||
|
WRK --> FACADE
|
||||||
|
FACADE -.->|"oakengine_event 变更通知<br/>(发射线程同步回调)"| APP
|
||||||
|
|
||||||
|
FACADE -->|"任务工厂/进度"| TASK
|
||||||
|
FACADE -->|"渲染请求/帧句柄"| RENDER
|
||||||
|
FACADE --> AUDIO
|
||||||
|
FACADE --> PLUGIN
|
||||||
|
FACADE -->|"工程打开/保存(URI)"| STORAGE
|
||||||
|
FACADE --> UNDO
|
||||||
|
|
||||||
|
TASK -->|"load/save 任务委托<br/>(Project 句柄 + XML 字节流)"| STORAGE
|
||||||
|
TASK -->|"import/conform/proxy 任务"| CODEC
|
||||||
|
TASK -->|"导出任务"| RENDER
|
||||||
|
|
||||||
|
RENDER -->|"遍历节点图求值"| MODEL
|
||||||
|
RENDER -->|"解码帧请求"| CODEC
|
||||||
|
RENDER -->|"上传纹理/绘制"| BACKEND
|
||||||
|
RENDER -->|"OCIO 色彩变换"| CORE
|
||||||
|
|
||||||
|
STORAGE -->|"反序列化建图 / 序列化取图(同步命令)"| MODEL
|
||||||
|
SERIAL --> MODEL
|
||||||
|
|
||||||
|
MODEL -->|"读取媒体参数"| CODEC
|
||||||
|
MODEL --> CORE
|
||||||
|
UNDO -->|"命令持有 Node/Project 句柄"| MODEL
|
||||||
|
AUDIO --> CORE
|
||||||
|
CODEC --> FFMPEG
|
||||||
|
CODEC --> CORE
|
||||||
|
BACKEND -.->|"帧完成(异步任务回调,ticket 返回通道)"| RENDER
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据流要点**:
|
||||||
|
|
||||||
|
1. **命令流(自上而下)**:app 的每个动作 → facade `oakengine_*` → 对应
|
||||||
|
模块内部 C ABI。facade 是唯一入口,模块不允许被 app 直接链接。
|
||||||
|
2. **工程 IO 流**:`oaktask` 创建 load/save 任务 → 委托 `oakstorage`;
|
||||||
|
oakstorage 按 URI scheme 选后端(`file://*.ove` → ove-xml 后端,
|
||||||
|
未来 `oakdb://` → 数据库后端),序列化/反序列化时对 `oakmodel` 建图
|
||||||
|
取图。**替换为数据库只发生在 oakstorage 内部。**
|
||||||
|
3. **帧数据流**:`oakcodec` 经 `ffmpeg_bridge` 解码 → `oakrender` 遍历
|
||||||
|
`oakmodel` 节点图求值 → `oakbackend`(GPU 插件)上屏/导出;帧以不透明
|
||||||
|
句柄 + buf/size 约定跨边界,不传递 C++ 对象。
|
||||||
|
4. **通知流(仅两种反向通道,虚线)**:模块间没有事件订阅——上层调
|
||||||
|
下层只有命令,调用方知道影响;变更通知由命令发起层(通常是 facade)
|
||||||
|
经 `oakengine_event` 同步回调给 app,Rust 化后只是发射端换语言,
|
||||||
|
通道不变(§6.1)。另一例外是异步任务的完成回调(oaktask 任务、
|
||||||
|
渲染 ticket、GPU 帧完成)——回调即该异步命令的返回通道。
|
||||||
|
5. **undo 流**:所有可撤销编辑(无论来自 facade 还是模块内部)都包装成
|
||||||
|
`OakUndoCommand` 进入 `oakundo` 栈,命令体内只持 oakmodel 句柄。
|
||||||
|
|
||||||
**关键架构事实(拆分顺序的依据)**:
|
**关键架构事实(拆分顺序的依据)**:
|
||||||
- `Node` 及其子类簇(Project/Folder/Footage/Sequence/Block/Track/Clip/Gap/
|
- `Node` 及其子类簇(Project/Folder/Footage/Sequence/Block/Track/Clip/Gap/
|
||||||
Transition/Subtitle/各效果节点)是 C++ 继承绑死的**不可拆分类型簇**——
|
Transition/Subtitle/各效果节点)是 C++ 继承绑死的**不可拆分类型簇**——
|
||||||
@@ -177,7 +270,8 @@
|
|||||||
| M0 | **oakcore** | liboakcore 整体(rational/timecode/bezier/samplebuffer/audioparams,Qt-free) | 无 | 极低;工具链试金石 |
|
| M0 | **oakcore** | liboakcore 整体(rational/timecode/bezier/samplebuffer/audioparams,Qt-free) | 无 | 极低;工具链试金石 |
|
||||||
| M1 | **oakaudio** | AudioProcessor、AudioSynchronizer、AudioLevelMeter、波形计算 | oakcore | 低;顺带消掉 AudioProcessor 豁免项 |
|
| M1 | **oakaudio** | AudioProcessor、AudioSynchronizer、AudioLevelMeter、波形计算 | oakcore | 低;顺带消掉 AudioProcessor 豁免项 |
|
||||||
| M2 | **oakcodec** | decoder/encoder/conform/proxy | ffmpeg_bridge | 中;FFmpeg 行为复刻 |
|
| M2 | **oakcodec** | decoder/encoder/conform/proxy | ffmpeg_bridge | 中;FFmpeg 行为复刻 |
|
||||||
| M3 | **oakserialize** | node/project/serializer/*(XML 项目文件) | oakmodel(经 facade node/project 族) | 中;round-trip 必须字节一致 |
|
| M3a | **oakstorage** | node/project/serializer 落盘路径 + task/project/{load,save,loadotio,saveotio} 文件 IO(工程持久化,后端可插拔:当前 XML 文件,未来数据库) | oakmodel(经 facade node/project 族) | 中;round-trip 必须字节一致;后端接口一次冻结 |
|
||||||
|
| M3b | **oakserialize** | node/project/serializer 的剪贴板/节点图 XML 序列化(copy/paste,不落盘) | oakmodel(经 facade node/project 族) | 中;round-trip 必须字节一致 |
|
||||||
| M4 | **oakundo** | UndoCommand/UndoStack/MultiUndoCommand | oakmodel(经 facade) | 中;全局调用点多 |
|
| M4 | **oakundo** | UndoCommand/UndoStack/MultiUndoCommand | oakmodel(经 facade) | 中;全局调用点多 |
|
||||||
| M5 | **oakrender** | RenderManager/ticket/watcher/cache/PreviewAutoCacher/ColorProcessor | oakmodel、oakcodec | 高;线程与 OCIO |
|
| M5 | **oakrender** | RenderManager/ticket/watcher/cache/PreviewAutoCacher/ColorProcessor | oakmodel、oakcodec | 高;线程与 OCIO |
|
||||||
| M6 | **oakmodel** | Node/NodeInput/keyframe/traverser/factory/Project/Folder/Footage/Sequence/Block/Track/效果节点 | oakcore、oakcodec | 最高;最大类型簇 |
|
| M6 | **oakmodel** | Node/NodeInput/keyframe/traverser/factory/Project/Folder/Footage/Sequence/Block/Track/效果节点 | oakcore、oakcodec | 最高;最大类型簇 |
|
||||||
@@ -312,7 +406,8 @@ timeline.h,本就是为外部消费设计的)充当模块间缝,缝的质
|
|||||||
|
|
||||||
1. **S1 完成**:Rust 工具链 + 门禁脚本进 CI;M0(liboakcore)G6 退役。
|
1. **S1 完成**:Rust 工具链 + 门禁脚本进 CI;M0(liboakcore)G6 退役。
|
||||||
2. **M1–M2 完成**:音频 DSP 与编解码 Rust 化;AudioProcessor 豁免项消除。
|
2. **M1–M2 完成**:音频 DSP 与编解码 Rust 化;AudioProcessor 豁免项消除。
|
||||||
3. **M3–M4 完成**:序列化与 undo Rust 化;项目文件 round-trip 金标准常青。
|
3. **M3a–M4 完成**:工程存储(oakstorage,含后端可插拔接口冻结)、剪贴板
|
||||||
|
序列化与 undo Rust 化;项目文件 round-trip 金标准常青。
|
||||||
4. **M5 完成**:渲染管线 Rust 化(OCIO 孤岛与否已裁决并记录)。
|
4. **M5 完成**:渲染管线 Rust 化(OCIO 孤岛与否已裁决并记录)。
|
||||||
5. **M6 完成**:oakmodel Rust 化——**最大里程碑**,此后 liboakengine 主体为 Rust。
|
5. **M6 完成**:oakmodel Rust 化——**最大里程碑**,此后 liboakengine 主体为 Rust。
|
||||||
6. **M7–M8 完成**:任务系统与 facade 壳 Rust 化;liboakengine.so(C++ 版)正式退役。
|
6. **M7–M8 完成**:任务系统与 facade 壳 Rust 化;liboakengine.so(C++ 版)正式退役。
|
||||||
|
|||||||
@@ -6,9 +6,10 @@
|
|||||||
> 并稳定后,才逐模块用 Rust 重写(届时模块的 C ABI 原样保留,Rust
|
> 并稳定后,才逐模块用 Rust 重写(届时模块的 C ABI 原样保留,Rust
|
||||||
> 实现替换 C++ 实现对调用方透明)。
|
> 实现替换 C++ 实现对调用方透明)。
|
||||||
>
|
>
|
||||||
> 阅读顺序:`00`(本文)→ `01-adapter-pattern.md`(适配器规范,
|
> 阅读顺序:`00`(本文)→ `01-adapter-pattern.md`(适配器规范 + §0
|
||||||
> 所有模块共用)→ `02-modules-and-order.md`(模块清单、依赖矩阵、
|
> 接口铁律,所有模块共用)→ `02-modules-and-order.md`(模块清单、依赖矩阵、
|
||||||
> 拆分顺序)→ `03-testing.md`(测试规范)→ `M1`…`M9`(逐模块执行
|
> 拆分顺序)→ `03-testing.md`(测试规范)→ `04-interfaces.md`(模块间
|
||||||
|
> 接口 provides/consumes 全表)→ `M1`…`M10`(逐模块执行
|
||||||
> 手册,**C API 已在各手册中冻结**)。
|
> 手册,**C API 已在各手册中冻结**)。
|
||||||
|
|
||||||
## 目标与判据
|
## 目标与判据
|
||||||
@@ -18,8 +19,10 @@
|
|||||||
```
|
```
|
||||||
oakcore(已有,不动)
|
oakcore(已有,不动)
|
||||||
oakcommon ─ oakundo ─ oaknode ─ oaktimeline ─ oakcodec ─ oakrender ─ oaktask ─ oakplugin
|
oakcommon ─ oakundo ─ oaknode ─ oaktimeline ─ oakcodec ─ oakrender ─ oaktask ─ oakplugin
|
||||||
│
|
│ │
|
||||||
oakaudio ───────────────────┤
|
└────────────── oakstorage(工程持久化, ┘
|
||||||
|
后端可插拔:文件→数据库)
|
||||||
|
oakaudio ─────────────┐
|
||||||
▼
|
▼
|
||||||
liboakengine(= facade + coreengine,纯装配层)
|
liboakengine(= facade + coreengine,纯装配层)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,6 +4,32 @@
|
|||||||
> C API 设计与适配类实现都必须照此执行。命名、内存所有权、错误码、
|
> C API 设计与适配类实现都必须照此执行。命名、内存所有权、错误码、
|
||||||
> 线程与信号的处理在此**冻结**。
|
> 线程与信号的处理在此**冻结**。
|
||||||
|
|
||||||
|
## 0. 接口铁律(2026-08 修订,优先级高于本文件其余各节)
|
||||||
|
|
||||||
|
模块边界上的接口必须是**纯 C 的、面向对象的**:
|
||||||
|
|
||||||
|
1. **对象 = 不透明句柄**。每个跨界类型是一个不透明结构体指针:
|
||||||
|
`typedef struct OakModClazz OakModClazz;`(**手写 struct 标签的不透明
|
||||||
|
指针**,禁止用 `void *` 充当对象——现有代码里 `void *parent_qobject`、
|
||||||
|
`void *oaktask_import_take_command()` 这类用法是反面教材,新接口一律
|
||||||
|
禁止,旧接口在所属模块拆分时顺手改为有类型句柄)。
|
||||||
|
2. **构造/析构 = init/free 函数对**:`oakmod_clazz_init*()` /
|
||||||
|
`oakmod_clazz_free()`。free 对 NULL 是 no-op。
|
||||||
|
3. **成员函数 = 首参为 self 句柄的普通函数**:
|
||||||
|
`oakmod_clazz_<func>(OakModClazz *self, ...)`;静态成员函数无 self
|
||||||
|
(`_s` 后缀)。
|
||||||
|
4. **多态 = 手工虚函数表**。确需"基类句柄 + 多种实现"(如存储后端、
|
||||||
|
undo 命令、渲染后端插件)时,在公共头里定义纯 C 函数指针表
|
||||||
|
(`typedef struct { ...; int (*save)(...); ... } OakModClazzVTable;`),
|
||||||
|
提供侧填充、消费侧经表调用,**不得让 C++ vtable 跨界**。模板见
|
||||||
|
`M10-oakstorage.md` §2.3 的 `OakStorageBackend`。
|
||||||
|
5. **禁止 C++ 对象跨越动态库边界**:边界上只出现 C 类型(整数、double、
|
||||||
|
指针、`const char *`、纯 C POD、不透明句柄)。C++ 类实例、引用、
|
||||||
|
`std::` 类型、Qt 类型一律不得出现在任何模块的 `include/` 公共头里。
|
||||||
|
6. **禁止跨界调用 C++ 成员函数**:消费侧对提供侧对象的一切操作必须经
|
||||||
|
该对象的 C ABI 函数;拿到句柄后 `reinterpret_cast` 回 C++ 指针再调
|
||||||
|
成员函数视为违规(nm 审计 + 代码评审双保险)。
|
||||||
|
|
||||||
## 1. 提供侧:C API 层
|
## 1. 提供侧:C API 层
|
||||||
|
|
||||||
对被消费的每个 C++ 类 `Clazz`,模块在 `include/oak<mod>/clazz.h`
|
对被消费的每个 C++ 类 `Clazz`,模块在 `include/oak<mod>/clazz.h`
|
||||||
@@ -32,7 +58,7 @@ OAKMOD_API <ret> oakmod_clazz_<func>_s(/* 参数 */);
|
|||||||
3. 多个构造重载用后缀区分:`oakmod_clazz_init`(默认)、
|
3. 多个构造重载用后缀区分:`oakmod_clazz_init`(默认)、
|
||||||
`oakmod_clazz_init_from_file`、`oakmod_clazz_init_copy` 等。
|
`oakmod_clazz_init_from_file`、`oakmod_clazz_init_copy` 等。
|
||||||
4. 命名全小写,模块前缀 `oak<mod>_`(oakundo/oaknode/oaktimeline/
|
4. 命名全小写,模块前缀 `oak<mod>_`(oakundo/oaknode/oaktimeline/
|
||||||
oakcodec/oakrender/oaktask/oakaudio/oakplugin/oakcommon)。
|
oakcodec/oakrender/oaktask/oakaudio/oakplugin/oakcommon/oakstorage)。
|
||||||
5. 导出宏 `OAKMOD_API` 照 `oakengine/export.h` 样式
|
5. 导出宏 `OAKMOD_API` 照 `oakengine/export.h` 样式
|
||||||
(`__attribute__((visibility("default")))`),模块编译加
|
(`__attribute__((visibility("default")))`),模块编译加
|
||||||
`-fvisibility=hidden`——每个模块**出生即 visibility 干净**,
|
`-fvisibility=hidden`——每个模块**出生即 visibility 干净**,
|
||||||
@@ -99,22 +125,26 @@ C ABI 上只允许:整数、`double`、`int64_t`、指针、`const char *`、
|
|||||||
| `Qt::enum`/内部枚举 | `int`(取值表写进手册,两侧枚举**序数一致性**用 static_assert 或测试钉死) |
|
| `Qt::enum`/内部枚举 | `int`(取值表写进手册,两侧枚举**序数一致性**用 static_assert 或测试钉死) |
|
||||||
| `std::shared_ptr<T>` | 不透明句柄 + retain/free(协议见 display.h R7-A 的先例) |
|
| `std::shared_ptr<T>` | 不透明句柄 + retain/free(协议见 display.h R7-A 的先例) |
|
||||||
|
|
||||||
## 4. 信号、回调与线程
|
## 4. 信号、回调与线程(2026-08 修订:上层对下层只有命令)
|
||||||
|
|
||||||
Qt 信号不许跨模块。处理优先级:
|
Qt 信号不许跨模块;**下层对上层也不许持有回调**——上层调用下层时
|
||||||
|
必须知道其影响(改了什么全在返回值/出参里),变更通知由**调用方所在
|
||||||
|
层**发出,不经下层反向通知。各模块 C ABI 因此一律不含
|
||||||
|
subscribe/unsubscribe 类函数。处理规则:
|
||||||
|
|
||||||
1. **回调注册**:`oakmod_clazz_set_<event>_cb(self, fn, userdata)`,
|
1. **同步命令**:提供侧把结果放在返回值/出参;消费侧适配类在调用后
|
||||||
提供侧在发信号处调 `fn(event_pod, userdata)`。userdata 所有权归
|
自行发 Qt 信号(适配类知道刚执行了什么命令,见 §7 例)。原
|
||||||
注册方,适配类析构时先 `set_*_cb(self, NULL, NULL)` 反注册。
|
`connect()` 到适配类信号的 widget 代码零改动。
|
||||||
2. **事件总线**:模块级通知(非单对象)用
|
2. **唯一例外——异步任务**:后台执行单元(oaktask 任务、oakrender
|
||||||
`oakmod_subscribe(event_id, fn, userdata)` → 返回订阅 id,
|
渲染 ticket)提交时拿不到结果,允许进度/完成回调作为该命令的
|
||||||
`oakmod_unsubscribe(id)`——照 `oakengine/events.h` 的现成模式。
|
返回通道:`oak<mod>_<async>_start(handle, done_fn, userdata)` 形式,
|
||||||
3. 线程语义照现状:提供侧在发射线程同步调回调(DirectConnection
|
一次性语义,完成后自动失效。
|
||||||
|
3. 线程语义照现状:异步回调在发射线程同步调用(DirectConnection
|
||||||
等价),需要跨线程排队是消费侧适配类自己的事(它可以用
|
等价),需要跨线程排队是消费侧适配类自己的事(它可以用
|
||||||
`QMetaObject::invokeMethod(..., Qt::QueuedConnection)`)。
|
`QMetaObject::invokeMethod(..., Qt::QueuedConnection)`)。
|
||||||
4. **适配类可以把 C 回调再转回 Qt 信号**:适配类继承 QObject、
|
4. **facade→app 的 `oakengine_event` 通道不在此列**:那是引擎对最外层
|
||||||
静态 trampoline 里 `emit` 同名信号——消费侧原有 `connect()` 全部
|
的唯一通知机制(riir.md §6.1),事件由 facade 在命令完成后发射,
|
||||||
零改动。这是大多数 widget 侧适配的默认做法。
|
不由下层模块直接发射。
|
||||||
|
|
||||||
## 5. 继承与虚函数
|
## 5. 继承与虚函数
|
||||||
|
|
||||||
@@ -139,40 +169,35 @@ Qt 信号不许跨模块。处理优先级:
|
|||||||
```c
|
```c
|
||||||
/* oakundo/include/oakundo/undostack.h */
|
/* oakundo/include/oakundo/undostack.h */
|
||||||
typedef struct OakUndoStack OakUndoStack;
|
typedef struct OakUndoStack OakUndoStack;
|
||||||
OAKUNDO_API OakUndoStack *oakundo_undostack_init(void *parent_qobject);
|
typedef struct OakUndoObjectParent OakUndoObjectParent; /* borrowed QObject 挂载点 */
|
||||||
|
OAKUNDO_API OakUndoStack *oakundo_undostack_init(
|
||||||
|
const OakUndoObjectParent *parent);
|
||||||
OAKUNDO_API void oakundo_undostack_free(OakUndoStack *self);
|
OAKUNDO_API void oakundo_undostack_free(OakUndoStack *self);
|
||||||
OAKUNDO_API void oakundo_undostack_push(OakUndoStack *self,
|
OAKUNDO_API void oakundo_undostack_push(OakUndoStack *self,
|
||||||
OakUndoCommand *cmd, const char *name);
|
OakUndoCommand *cmd, const char *name);
|
||||||
OAKUNDO_API int oakundo_undostack_can_undo(const OakUndoStack *self);
|
OAKUNDO_API int oakundo_undostack_can_undo(const OakUndoStack *self);
|
||||||
/* ... 完整表见 M2 手册 ... */
|
OAKUNDO_API int64_t oakundo_undostack_index(const OakUndoStack *self);
|
||||||
OAKUNDO_API int64_t oakundo_undostack_subscribe(OakUndoStack *self,
|
/* ... 完整表见 M2 手册(纯命令接口,无任何 subscribe) ... */
|
||||||
int event_id, oakundo_event_fn fn, void *userdata);
|
|
||||||
OAKUNDO_API void oakundo_unsubscribe(int64_t id);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 消费侧 adapter/undostack.h —— app/other 模块里 connect() 零改动
|
// 消费侧 adapter/undostack.h —— app/other 模块里 connect() 零改动。
|
||||||
|
// 通知规则(§4):适配类发了变更命令,它知道影响,信号由适配类自己 emit。
|
||||||
class UndoStack : public QObject {
|
class UndoStack : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit UndoStack(QObject *p = nullptr)
|
explicit UndoStack(QObject *p = nullptr)
|
||||||
: QObject(p), h_(oakundo_undostack_init(p)) {
|
: QObject(p), h_(oakundo_undostack_init(
|
||||||
sub_ = oakundo_undostack_subscribe(h_, OAKUNDO_EVENT_INDEX_CHANGED,
|
reinterpret_cast<const OakUndoObjectParent *>(p))) {}
|
||||||
&UndoStack::tramp, this);
|
~UndoStack() override { oakundo_undostack_free(h_); }
|
||||||
}
|
|
||||||
~UndoStack() override { oakundo_unsubscribe(sub_); oakundo_undostack_free(h_); }
|
|
||||||
void push(UndoCommand *c, const QString &n) {
|
void push(UndoCommand *c, const QString &n) {
|
||||||
oakundo_undostack_push(h_, c->handle(), n.toUtf8().constData());
|
oakundo_undostack_push(h_, c->handle(), n.toUtf8().constData());
|
||||||
|
emit index_changed(int(oakundo_undostack_index(h_))); // 命令后自发通知
|
||||||
}
|
}
|
||||||
bool canUndo() const { return oakundo_undostack_can_undo(h_) != 0; }
|
bool canUndo() const { return oakundo_undostack_can_undo(h_) != 0; }
|
||||||
signals:
|
signals:
|
||||||
void index_changed(int);
|
void index_changed(int);
|
||||||
private:
|
private:
|
||||||
static void tramp(int event_id, int64_t a, int64_t b, void *ud) {
|
|
||||||
if (event_id == OAKUNDO_EVENT_INDEX_CHANGED)
|
|
||||||
emit static_cast<UndoStack *>(ud)->index_changed(int(a));
|
|
||||||
}
|
|
||||||
OakUndoStack *h_;
|
OakUndoStack *h_;
|
||||||
int64_t sub_;
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -23,6 +23,11 @@
|
|||||||
已知分层违规 1 处:`render/` 引用了 `src/capi/displayinternal.h`
|
已知分层违规 1 处:`render/` 引用了 `src/capi/displayinternal.h`
|
||||||
(R7-A 重做 display.h 时一并消除)。
|
(R7-A 重做 display.h 时一并消除)。
|
||||||
|
|
||||||
|
> **2026-08 增补(oakstorage 拆出)**:矩阵扫描早于 oakstorage 单列。
|
||||||
|
> 其依赖关系为:oakstorage → node(serializer 落盘路径,含版本化
|
||||||
|
> serializerXXXXXX 族)+ common;oaktask → oakstorage(load/save/otio
|
||||||
|
> 委托);facade → oakstorage。详细契约见 04 §2.10 与 M10。
|
||||||
|
|
||||||
## 2. 模块定义与拆分顺序
|
## 2. 模块定义与拆分顺序
|
||||||
|
|
||||||
顺序原则:叶子先、根后;每步只引入"已拆模块的 C ABI",不引入
|
顺序原则:叶子先、根后;每步只引入"已拆模块的 C ABI",不引入
|
||||||
@@ -34,11 +39,12 @@
|
|||||||
| M1 | oakcommon | `common/`(41 文件工具集)+ `config/` | common→render/node/undo/codec/plugin 的 12 次反向 include(清单见 M1 §3) |
|
| M1 | oakcommon | `common/`(41 文件工具集)+ `config/` | common→render/node/undo/codec/plugin 的 12 次反向 include(清单见 M1 §3) |
|
||||||
| M2 | oakundo | `undo/`(undocommand/undostack) | undo→node/project.h 1 处(M2 §3) |
|
| M2 | oakundo | `undo/`(undocommand/undostack) | undo→node/project.h 1 处(M2 §3) |
|
||||||
| M3 | oaknode | `node/`(图、工厂、keyframe、nodeundo、traverser) | node→render 47、node→codec 8、node→timeline 5、node→audio 4、node→undo 4(M3 §3,最大的活) |
|
| M3 | oaknode | `node/`(图、工厂、keyframe、nodeundo、traverser) | node→render 47、node→codec 8、node→timeline 5、node→audio 4、node→undo 4(M3 §3,最大的活) |
|
||||||
|
| M3a | **oakstorage** | `node/project/serializer` 落盘路径 + `task/project/{load,save,loadotio,saveotio}` 文件 IO(**工程持久化单列**,后端可插拔:当前 XML 文件,未来数据库) | storage→node(序列化建图取图,经 oaknode C ABI);手册 M10 |
|
||||||
| M4 | oaktimeline | `timeline/`(marker/workarea/timeline undo 命令族/timelinecommon) | timeline→node 32(经 oaknode C ABI + 适配类) |
|
| M4 | oaktimeline | `timeline/`(marker/workarea/timeline undo 命令族/timelinecommon) | timeline→node 32(经 oaknode C ABI + 适配类) |
|
||||||
| M5 | oakcodec | `codec/`(decoder/encoder/frame/proxy/conform) | codec→render 11(videoparams 等随 M3.5 下沉)、codec→task 5、codec→node 3 |
|
| M5 | oakcodec | `codec/`(decoder/encoder/frame/proxy/conform) | codec→render 11(videoparams 等随 M3.5 下沉)、codec→task 5、codec→node 3 |
|
||||||
| M6 | oakaudio | `audio/`(AudioManager/AudioProcessor/输出) | audio→render 2、audio→codec 1 |
|
| M6 | oakaudio | `audio/`(AudioManager/AudioProcessor/输出) | audio→render 2、audio→codec 1 |
|
||||||
| M7 | oakrender | `render/`(Renderer/PlaybackCache/ColorManager/帧缓存/job) | render→node 38、render→codec 9、render→task 2、render→undo 1、render→src 1(违规) |
|
| M7 | oakrender | `render/`(Renderer/PlaybackCache/ColorManager/帧缓存/job) | render→node 38、render→codec 9、render→task 2、render→undo 1、render→src 1(违规) |
|
||||||
| M8 | oaktask | `task/`(Task/TaskManager/项目 load/save/import/OTIO) | task→node 38、task→codec 4、task→render 4 |
|
| M8 | oaktask | `task/`(Task/TaskManager/项目任务编排/cache 任务;**工程文件 IO 已划给 oakstorage,M3a**) | task→node 38、task→codec 4、task→render 4、task→storage(load/save 委托) |
|
||||||
| M9 | oakplugin | `pluginSupport/`(OpenFX host) | plugin→node 6、plugin→render 6、plugin→undo 2、plugin→coreengine 2 |
|
| M9 | oakplugin | `pluginSupport/`(OpenFX host) | plugin→node 6、plugin→render 6、plugin→undo 2、plugin→coreengine 2 |
|
||||||
| — | liboakengine | `src/capi` + `coreengine` + `tool/` + `ui/` 残余 | 纯装配层:facade 内部调用改经各模块 C ABI(或保持现状直接链,见 M9 §4 裁决) |
|
| — | liboakengine | `src/capi` + `coreengine` + `tool/` + `ui/` 残余 | 纯装配层:facade 内部调用改经各模块 C ABI(或保持现状直接链,见 M9 §4 裁决) |
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,10 @@
|
|||||||
4. **枚举序数一致性**:C 侧 POD/枚举与 C++ 侧枚举的映射(01 §3 表)
|
4. **枚举序数一致性**:C 侧 POD/枚举与 C++ 侧枚举的映射(01 §3 表)
|
||||||
每个映射 1 个 TEST(如 `oakundo` 的 movement mode 0-3 ⇄
|
每个映射 1 个 TEST(如 `oakundo` 的 movement mode 0-3 ⇄
|
||||||
`Timeline::MovementMode`)。
|
`Timeline::MovementMode`)。
|
||||||
5. **事件/回调**:每个 `set_*_cb`/subscribe 至少 1 个 TEST:触发后
|
5. **回调(仅异步任务)**:模块间 C ABI 无 subscribe 类接口(04 §3);
|
||||||
断言回调被调、payload 正确;反注册后断言不再被调。
|
仅异步命令(任务/渲染 ticket)的进度/完成回调需要测试:触发后
|
||||||
|
断言回调被调、payload 正确、FINISHED 后自动失效。同步命令的测试
|
||||||
|
改为"调用后直接读状态断言生效"。
|
||||||
6. **所有权**:borrowed 句柄(文档注释标了 `/* borrowed */` 的)
|
6. **所有权**:borrowed 句柄(文档注释标了 `/* borrowed */` 的)
|
||||||
free 后原对象仍存活,1 个 TEST。
|
free 后原对象仍存活,1 个 TEST。
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
# 04 · 模块间接口设计(provides / consumes 全表)
|
||||||
|
|
||||||
|
> 本文是各模块手册(M1–M10)C API 的**汇总视图**:每个模块对外提供哪些
|
||||||
|
> 接口族、消费哪些模块的哪些接口族、边界上流什么数据。逐函数签名以各
|
||||||
|
> M 手册的"冻结 C API"节为准;本文管"模块与模块之间的契约关系",管不到
|
||||||
|
> 逐函数细节。
|
||||||
|
>
|
||||||
|
> 全部接口遵守 `01-adapter-pattern.md` §0 的铁律:**纯 C、面向对象**——
|
||||||
|
> 有类型的不透明句柄(禁止 `void *` 当对象)、`init_*`/`free_*` 构造析构、
|
||||||
|
> 首参 self 的普通函数当成员函数、多态用手工函数指针表、C++ 对象与成员
|
||||||
|
> 函数一律不跨动态库边界。
|
||||||
|
|
||||||
|
## 1. 接口关系总表
|
||||||
|
|
||||||
|
行 = 消费方,列 = 提供方;单元格 = 消费的接口族(详见各提供方手册)。
|
||||||
|
|
||||||
|
| 消费 ↓ \ 提供 → | oakcommon | oakundo | oaknode | oaktimeline | oakcodec | oakaudio | oakrender | oakstorage | oaktask | oakplugin | oakcore |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| **facade**(src/capi) | 工具/类型 | undo 全族 | node/project/footage/serializer(clipboard) 族 | timeline 全族 | decoder/frame/proxy 族 | audio 全族 | renderer/playback/preview 族 | open/save/probe 族 | task/manager 全族 | plugin 全族 | rational/timecode |
|
||||||
|
| **oaktask** | 工具 | command 句柄 | footage/project/sequence/folder 族 | — | conform/proxy 族 | — | 导出用 render 族 | **load/save/otio 委托** | — | — | — |
|
||||||
|
| **oakrender** | videoparams/colortransform(M3.5 下沉) | command 句柄 | node 遍历/取值、project、colormanager | — | decoder/frame 族 | 音频参数 | — | — | — | plugin 实例句柄 | — |
|
||||||
|
| **oakplugin** | 工具 | command 句柄 | node 参数读写 | — | — | — | 帧缓冲/纹理句柄 | — | — | — | — |
|
||||||
|
| **oakaudio** | 工具 | — | — | — | frame/解码(1 处) | — | videoparams(下沉后消失) | — | — | — | samplebuffer |
|
||||||
|
| **oakcodec** | videoparams/subtitleparams(下沉) | — | footage 流信息(3 处) | — | — | — | — | — | — | — | rational |
|
||||||
|
| **oaktimeline** | timelinecommon 枚举(下沉) | command 句柄 | track/block/sequence 族(32 处) | — | — | — | — | — | — | — | — |
|
||||||
|
| **oaknode** | 枚举/常量/工具 | undocommand.h(4 处) | — | marker/workarea(2 处,M4 反向) | decoder/frame/proxy(8 处,M5 反向) | audio 参数(4 处,M6 反向) | colorprocessor/rendermanager/job(M7 反向,02 §4 裁决 A) | — | — | — | rational/bezier |
|
||||||
|
| **oakundo** | 工具 | — | — | — | — | — | — | — | — | — | — |
|
||||||
|
| **oakstorage** | 工具 | — | **project/root/序列化建图取图** | — | — | — | — | — | — | — | — |
|
||||||
|
| **oakcommon** | — | — | — | — | — | — | — | — | — | — | — |
|
||||||
|
|
||||||
|
(空格 = 无依赖。"N 处"数据来自 02 的 include 扫描。oakcore 与
|
||||||
|
ffmpeg_bridge 为现成独立库,不参与拆分顺序。)
|
||||||
|
|
||||||
|
## 2. 逐模块接口契约
|
||||||
|
|
||||||
|
### 2.1 oakcommon(M1)— 纯下沉,无业务对象
|
||||||
|
|
||||||
|
- **提供**:`include/oakcommon/types.h` 的全模块共用 POD(时间戳/区间/
|
||||||
|
枚举常量,含 M3.5 下沉的 `OakVideoParams`/`OakSubtitleParams`/
|
||||||
|
`OakColorTransform`);工具函数(全 `_s` 静态式,无句柄)。
|
||||||
|
- **消费**:无(叶子)。
|
||||||
|
- **边界数据**:纯 POD 值,无所有权问题。
|
||||||
|
|
||||||
|
### 2.2 oakundo(M2)— 手工虚表的第一个用户
|
||||||
|
|
||||||
|
- **提供**:`OakUndoCommand` / `OakUndoStack` 两类句柄。命令的多态
|
||||||
|
(redo/undo 行为)经 **回调函数指针** 实现
|
||||||
|
(`oakundo_command_create(name, redo, undo, free_fn, userdata)`),
|
||||||
|
即铁律 §0.4 的手工虚表;消费侧**不构造 C++ 子类**。
|
||||||
|
- **消费**:oakcommon。
|
||||||
|
- **边界数据**:命令句柄(owned)。无事件——push/undo/redo 的调用方
|
||||||
|
知道栈索引变化,通知由调用方(facade 适配层)发出(见 §3)。
|
||||||
|
|
||||||
|
### 2.3 oaknode(M3)— 最大提供方
|
||||||
|
|
||||||
|
- **提供**:`OakNodeNode/NodeGroup/NodeKeyframe/NodeFactory/NodeTraverser/
|
||||||
|
Project/Folder/Sequence/Track/TrackList/Block/Footage/ColorManager`
|
||||||
|
句柄族。逐族清单见 M3 §2。无订阅接口——所有修改经命令函数完成,
|
||||||
|
调用方知道影响(§3)。
|
||||||
|
- **消费**:oakcommon、oakundo;对 render/codec/audio/timeline 的引用按
|
||||||
|
02 §3/§4 的反向切割表在各模块就位后改经其 C ABI。
|
||||||
|
- **边界数据**:节点句柄(borrowed 为主,工程拥有节点)、
|
||||||
|
`oak_node_value` POD、id 字符串(buf/size)。
|
||||||
|
|
||||||
|
### 2.4 oaktimeline(M4)
|
||||||
|
|
||||||
|
- **提供**:marker/workarea/timeline 编辑原语句柄族(`OakTimelineMarker`
|
||||||
|
等),timeline 专用 undo 命令**经 oakundo 的回调式命令**注册,不自带
|
||||||
|
命令子类。
|
||||||
|
- **消费**:oaknode(32 处,全部经句柄族)、oakundo、oakcommon。
|
||||||
|
|
||||||
|
### 2.5 oakcodec(M5)
|
||||||
|
|
||||||
|
- **提供**:`OakCodecDecoder/Encoder/Frame/ProxyManager` 句柄族;
|
||||||
|
帧以 `OakCodecFrame *` 不透明句柄跨边界(owned,配对 free),
|
||||||
|
像素数据经 `oakcodec_frame_data(frame, plane, &linesize)` 取出指针
|
||||||
|
(borrowed,生命周期随 frame)。
|
||||||
|
- **消费**:oakcommon、oaknode(footage 流信息)、oakcore、ffmpeg_bridge。
|
||||||
|
|
||||||
|
### 2.6 oakaudio(M6)
|
||||||
|
|
||||||
|
- **提供**:`OakAudioManager/Processor/Synchronizer` 句柄族;波形/电平
|
||||||
|
数据以 POD 数组 + count 出参。
|
||||||
|
- **消费**:oakcore(samplebuffer)、oakcodec(1 处)。
|
||||||
|
|
||||||
|
### 2.7 oakrender(M7)
|
||||||
|
|
||||||
|
- **提供**:`OakRenderRenderer/Ticket/Cache/ColorProcessor` 句柄族;
|
||||||
|
渲染结果帧为 owned 句柄;渲染 ticket 是**异步命令**(后台线程),
|
||||||
|
进度/完成回调是它的返回通道——这是 §3 允许回调的唯一情形
|
||||||
|
(线程语义按 riir.md §6.2 钉死)。
|
||||||
|
- **消费**:oaknode、oakcodec、oakcommon、oakundo(1 处)、oakbackend
|
||||||
|
(GPU 插件,经 `renderbackend_c.h` 手工虚表——现有先例)。
|
||||||
|
|
||||||
|
### 2.8 oaktask(M8)— 编排者
|
||||||
|
|
||||||
|
- **提供**:`OakTaskTask` 句柄 + 任务工厂族 + TaskManager 查询函数。
|
||||||
|
任务是**异步命令**:进度/完成回调即其返回通道(§3 唯一例外),
|
||||||
|
无其他事件。load/save/import/otio 任务工厂保留,但**实现改为委托
|
||||||
|
oakstorage**(见 M10 §4);任务结果(Project、Footage 列表)以
|
||||||
|
**有类型句柄**返回(`oaktask_import_take_command` 的 `void *` 返回
|
||||||
|
按铁律 §0.1 改为 `OakUndoCommand *`)。
|
||||||
|
- **消费**:oakstorage、oaknode、oakcodec、oakrender、oaktimeline。
|
||||||
|
|
||||||
|
### 2.9 oakplugin(M9)
|
||||||
|
|
||||||
|
- **提供**:OFX 插件加载/实例句柄族(`OakPluginHost/Instance`)。
|
||||||
|
- **消费**:oaknode、oakrender、oakundo。
|
||||||
|
|
||||||
|
### 2.10 oakstorage(M10,新拆)— 工程持久化
|
||||||
|
|
||||||
|
- **提供**:`OakStorageProject`(打开的工程会话)、`OakStorageBackend`
|
||||||
|
(手工虚函数表,后端注册用)两类句柄 + `oakstorage_open/save/probe`
|
||||||
|
静态函数。**URI 寻址**:`file://…/*.ove` 走内建 ove-xml 后端;未来
|
||||||
|
`oakdb://` 走数据库后端——替换数据库 = 新增一个后端实现并注册,
|
||||||
|
消费侧零改动。
|
||||||
|
- **消费**:oaknode(反序列化建图 / 序列化取图)、oakcommon。
|
||||||
|
- **边界数据**:工程句柄(owned)、XML 字节流(buf/size)、后端表。
|
||||||
|
无事件——open/save 是同步命令,成败与结果全在返回值里,调用方
|
||||||
|
(oaktask/facade)知道影响,由它发通知(§3)。
|
||||||
|
- 详见 `M10-oakstorage.md`。
|
||||||
|
|
||||||
|
## 3. 通知的统一约定(2026-08 修订:上层对下层只有命令)
|
||||||
|
|
||||||
|
**铁律:上层调下层只发命令,不调订阅。** 上层调用下层时必须知道该调用的
|
||||||
|
影响——改了什么、结果是什么,全部由返回值/出参告知,调用方自己决定后续
|
||||||
|
动作。因此:
|
||||||
|
|
||||||
|
- **模块间 C ABI 一律不提供 subscribe/unsubscribe 类接口。** 下层不持有
|
||||||
|
上层的函数指针,不反向通知。各模块手册里原有的 `oak<mod>_subscribe`
|
||||||
|
设计全部作废(M2/M3/M10 已改)。
|
||||||
|
- **变更通知由命令发起方负责。** 例:app 经 facade 调 `undo` 命令后,
|
||||||
|
facade 知道栈索引变了,由 facade 经既有 `oakengine_event` 通道通知
|
||||||
|
app 侧——通知的起点永远是最靠近调用者的那一层。
|
||||||
|
- **唯一例外:异步任务。** 后台执行的单元(oaktask 的任务、oakrender 的
|
||||||
|
渲染 ticket)本质上是"提交时拿不到结果"的命令,允许进度/完成回调——
|
||||||
|
回调即该命令的返回通道,线程语义按 01 §4 / riir.md §6.2 钉死。
|
||||||
|
同步接口不得配回调。
|
||||||
|
|
||||||
|
## 4. 错误与所有权(横向统一)
|
||||||
|
|
||||||
|
- 返回码:`0 = OK`,负值 `OAK<MOD>_E_*`(值与 oakengine 现有对齐);
|
||||||
|
细节经 `oak<mod>_last_error(buf, size)`(线程局部)。
|
||||||
|
- 所有权注释三档:`/* owned */`(init/take 返回,必须配对 free)、
|
||||||
|
`/* borrowed */`(访问器返回,禁 free)、`/* retained */`(register
|
||||||
|
类,配对 unregister)。句柄默认 owned。
|
||||||
|
- 每个模块提供 `oak<mod>_debug_alive_count()`(测试专用,钉死泄漏)。
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
# M10 · oakstorage 拆分手册(工程持久化,数据库替换预备)
|
||||||
|
|
||||||
|
> 内容:工程文件的读写——`engine/node/project/serializer/` 的**落盘路径**
|
||||||
|
> (Load/Save/SaveData/LoadData 的文件分支 + 版本化 serializerXXXXXX 族)
|
||||||
|
> 与 `engine/task/project/{load,save,loadotio,saveotio}` 的**文件 IO 部分**。
|
||||||
|
> **不含**剪贴板序列化(copy/paste 的节点图 XML 留在 oaknode 的
|
||||||
|
> serializer 族,见 M3)。
|
||||||
|
> 依赖:oaknode(project/root/序列化建图取图)、oakcommon。
|
||||||
|
> 被依赖:oaktask(load/save 任务委托)、facade。
|
||||||
|
> 拆分顺序:M3a(oaknode 之后、oakserialize 同批)。
|
||||||
|
>
|
||||||
|
> **本模块存在的理由**:把"工程从哪来、存到哪去"收敛成唯一模块、唯一
|
||||||
|
> 接口,当前后端是 XML .ove 文件,**未来整体替换为数据库**。替换时新增
|
||||||
|
> 一个后端实现并注册即可,oaktask / facade / app 零改动。因此本模块的
|
||||||
|
> 接口按"存储后端无关"设计:URI 寻址、手工虚函数表、字节流 + 句柄,
|
||||||
|
> 接口里**不出现任何文件路径特有的概念以外的存储语义**(事务、连接串
|
||||||
|
> 等都藏在后端内部)。
|
||||||
|
|
||||||
|
## 1. 目标形态
|
||||||
|
|
||||||
|
```
|
||||||
|
oakstorage/
|
||||||
|
include/oakstorage/{storage.h, backend.h, types.h, export.h}
|
||||||
|
src/
|
||||||
|
capi_storage.cpp # storage.h 实现:URI 分发 + 会话
|
||||||
|
backends/ove_xml/ # 内建后端:XML .ove(现有 serializer 落盘路径迁入)
|
||||||
|
backends/... # 未来:oakdb(数据库后端)
|
||||||
|
tests/ # oakstorage_gtest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 冻结 C API
|
||||||
|
|
||||||
|
遵守 01 §0 铁律:纯 C、有类型不透明句柄、init/free、self 首参、
|
||||||
|
多态用手工虚函数表。
|
||||||
|
|
||||||
|
### 2.1 `oakstorage/types.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* 错误码(0=OK,负值错误;版本/格式探测用正值信息码) */
|
||||||
|
#define OAKSTORAGE_OK 0
|
||||||
|
#define OAKSTORAGE_TOO_OLD 1 /* 工程版本过旧(信息码) */
|
||||||
|
#define OAKSTORAGE_TOO_NEW 2 /* 工程版本过新 */
|
||||||
|
#define OAKSTORAGE_UNKNOWN_VERSION 3
|
||||||
|
#define OAKSTORAGE_E_INVALID -1
|
||||||
|
#define OAKSTORAGE_E_STATE -2
|
||||||
|
#define OAKSTORAGE_E_NOT_FOUND -3
|
||||||
|
#define OAKSTORAGE_E_FAILED -4
|
||||||
|
#define OAKSTORAGE_E_NO_BACKEND -5 /* 无后端认领该 URI */
|
||||||
|
#define OAKSTORAGE_E_FORMAT -6 /* 解析失败(XML/DB 约束) */
|
||||||
|
#define OAKSTORAGE_E_IO -7 /* 读写失败 */
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `oakstorage/storage.h`(消费侧主接口)
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* 打开的工程会话:包裹一个已加载(或待保存)的 oakmodel Project。
|
||||||
|
* owned 句柄,配对 oakstorage_project_free。 */
|
||||||
|
typedef struct OakStorageProject OakStorageProject;
|
||||||
|
|
||||||
|
/* --- 静态函数(无 self,对应"类方法") ---
|
||||||
|
* 全部是同步命令:成败与结果全在返回值/出参里,调用方知道影响,
|
||||||
|
* 由调用方(oaktask/facade)负责对外通知;本模块无任何回调/事件。 */
|
||||||
|
|
||||||
|
/* 探测 URI:返回认领该 URI 的后端名(buf/size,先 NULL 查长度),
|
||||||
|
* 或负值(OAKSTORAGE_E_NO_BACKEND)。不写盘、不建会话。 */
|
||||||
|
OAKSTORAGE_API int oakstorage_probe(const char *uri, char *buf, int buf_size);
|
||||||
|
|
||||||
|
/* 打开工程(load)。URI scheme 选后端:
|
||||||
|
* file:///path/to/proj.ove → ove-xml 后端
|
||||||
|
* file:///…/proj.otio → otio 后端(import 语义)
|
||||||
|
* oakdb://… → 未来数据库后端
|
||||||
|
* 失败返回 NULL,细节经 oakstorage_last_error。 */
|
||||||
|
OAKSTORAGE_API OakStorageProject *oakstorage_open(
|
||||||
|
const char *uri, int *result_code);
|
||||||
|
|
||||||
|
/* 把 project 保存到 URI(save / save-as)。
|
||||||
|
* `options`:位掩码,OAKSTORAGE_SAVE_COMPRESS 等;后端忽略不识别的位。 */
|
||||||
|
#define OAKSTORAGE_SAVE_COMPRESS 0x1
|
||||||
|
OAKSTORAGE_API int oakstorage_save(OakNodeProject *project,
|
||||||
|
const char *uri, unsigned options);
|
||||||
|
|
||||||
|
/* --- 成员函数(self 首参) --- */
|
||||||
|
|
||||||
|
OAKSTORAGE_API void oakstorage_project_free(OakStorageProject *self);
|
||||||
|
/* 取出工程句柄:所有权转移给调用方(此后 self 为空壳,仍须 free)。
|
||||||
|
* 对应 oakengine/task.h 的 take_project 语义。 */
|
||||||
|
OAKSTORAGE_API OakNodeProject *oakstorage_project_take_project(
|
||||||
|
OakStorageProject *self);
|
||||||
|
/* borrowed:不转移所有权 */
|
||||||
|
OAKSTORAGE_API OakNodeProject *oakstorage_project_project(
|
||||||
|
const OakStorageProject *self);
|
||||||
|
/* 会话来源 URI(buf/size) */
|
||||||
|
OAKSTORAGE_API int oakstorage_project_uri(const OakStorageProject *self,
|
||||||
|
char *buf, int buf_size);
|
||||||
|
|
||||||
|
OAKSTORAGE_API int oakstorage_last_error(char *buf, int buf_size);
|
||||||
|
OAKSTORAGE_API int oakstorage_debug_alive_count(void); /* 测试专用 */
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 `oakstorage/backend.h`(手工虚函数表——后端注册接口)
|
||||||
|
|
||||||
|
多态按 01 §0.4:纯 C 函数指针表,提供侧(后端)填充,oakstorage 核心
|
||||||
|
经表调用。**后端实现不进公共头**;数据库后端未来只是多注册一行。
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* 存储后端虚表。所有函数必需;返回码用 OAKSTORAGE_*。
|
||||||
|
* 句柄协议:load 成功时 *out_project 收到 owned OakNodeProject*;
|
||||||
|
* 后端可在 vtable 之外持有任意私有状态(连接池、事务句柄等)。 */
|
||||||
|
typedef struct OakStorageBackend {
|
||||||
|
const char *name; /* "ove-xml" / "otio" / "oakdb"(静态字符串) */
|
||||||
|
const char *uri_scheme; /* "file" / "oakdb";同一 scheme 可注册多个
|
||||||
|
* 后端,按 can_handle 顺序裁决 */
|
||||||
|
|
||||||
|
/* 是否认领该 URI(后缀、magic bytes、连接可达性等,后端自决) */
|
||||||
|
int (*can_handle)(const char *uri);
|
||||||
|
|
||||||
|
/* 加载:URI → owned Project 句柄;*result_code 收 OAKSTORAGE_* */
|
||||||
|
OakNodeProject *(*load)(const char *uri, int *result_code,
|
||||||
|
char *err_buf, int err_buf_size);
|
||||||
|
|
||||||
|
/* 保存:Project → URI;options 透传 storage.h 的位掩码 */
|
||||||
|
int (*save)(OakNodeProject *project, const char *uri, unsigned options,
|
||||||
|
char *err_buf, int err_buf_size);
|
||||||
|
} OakStorageBackend;
|
||||||
|
|
||||||
|
/* 注册/注销。oakstorage 核心不拷贝表体——后端必须保证表与 name 字符串
|
||||||
|
* 在 unregister 前存活(内建后端为静态存储期,天然满足)。 */
|
||||||
|
OAKSTORAGE_API int oakstorage_backend_register(const OakStorageBackend *backend);
|
||||||
|
OAKSTORAGE_API int oakstorage_backend_unregister(const char *name);
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据库替换路径(未来的活,接口已预留)**:
|
||||||
|
1. 写 `backends/oakdb/`:`can_handle` 认 `oakdb://`,`load/save` 走 SQL,
|
||||||
|
建表 schema 是后端私事;
|
||||||
|
2. `oakstorage_backend_register(&oakdb_backend);` 一行接入;
|
||||||
|
3. oaktask/facade/app 不动;`file://` 的 .ove 后端继续共存(迁移期
|
||||||
|
双后端并存,经 URI 显式选择)。
|
||||||
|
**反向约束**:任何"必须改本手册 §2.2 才能接数据库"的需求,说明接口
|
||||||
|
冻结有洞——先改本手册再动手。
|
||||||
|
|
||||||
|
### 2.4 与 oaktask 的边界(任务只是壳)
|
||||||
|
|
||||||
|
M8 的任务工厂保留原签名,实现改为薄委托:
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* oaktask/project.cpp(概念) */
|
||||||
|
OakTaskTask *oaktask_create_project_load(const char *filename) {
|
||||||
|
/* 任务体内:oakstorage_open(uri) → 完成回调里
|
||||||
|
* oakstorage_project_take_project() */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
import/conform/precache 等非工程 IO 任务不经 oakstorage。
|
||||||
|
|
||||||
|
## 3. 切割点
|
||||||
|
|
||||||
|
| 现状 | 处理 |
|
||||||
|
|---|---|
|
||||||
|
| `node/project/serializer/*` 落盘路径在 oaknode 内 | 文件分支 + serializerXXXXXX 版本族迁入 `oakstorage/backends/ove_xml/`;**节点图 XML 生成/解析(SaveData/LoadData 的内存形态)仍属 oaknode**,ove-xml 后端经 oaknode C ABI(`oaknode_serializer_*` 族)取图/建图。剪贴板分支留 oaknode(M3/M3b) |
|
||||||
|
| `task/project/load|save` 含文件 IO | IO 部分下沉 oakstorage;task 保留任务编排(进度、取消、事件) |
|
||||||
|
| `task/project/loadotio|saveotio` | 同上,注册为 "otio" 后端(scheme=file,can_handle 认 .otio) |
|
||||||
|
| serializer 对 Qt 文件对话框/布局的引用 | 布局信息(SerializedLayoutInfo)随保存走 options 的不透明 blob(buf/size),不进入本手册冻结面 |
|
||||||
|
|
||||||
|
## 4. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
|
- **round-trip 字节一致**(金标准):project_with_footage.ove →
|
||||||
|
`oakstorage_open` → `oakstorage_save` 到临时 URI → 两文件字节一致;
|
||||||
|
再 load 后 `oakstorage_project_project()` 非空、root 非空。
|
||||||
|
- probe:.ove(压缩/未压缩各一)、.otio、未知 scheme →
|
||||||
|
E_NO_BACKEND。
|
||||||
|
- 错误路径:不存在文件 open → NULL + last_error 非空;TOO_NEW 版本
|
||||||
|
头 → result_code = OAKSTORAGE_TOO_NEW。
|
||||||
|
- 后端虚表:注册一个内存 mock 后端(`mem://`,load/save 记日志),
|
||||||
|
断言 open/save 全走虚表、unregister 后 probe 报 E_NO_BACKEND——
|
||||||
|
**这条用例就是"数据库可插拔"的接口验证**。
|
||||||
|
- `oakstorage_debug_alive_count()`:open/take/free 配对无泄漏。
|
||||||
@@ -40,7 +40,12 @@ OAKUNDO_API void oakundo_command_free(OakUndoCommand *cmd); /* NULL no-op */
|
|||||||
|
|
||||||
```c
|
```c
|
||||||
typedef struct OakUndoStack OakUndoStack;
|
typedef struct OakUndoStack OakUndoStack;
|
||||||
OAKUNDO_API OakUndoStack *oakundo_undostack_init(void *parent_qobject);
|
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_free(OakUndoStack *self);
|
||||||
|
|
||||||
OAKUNDO_API void oakundo_undostack_push(OakUndoStack *self,
|
OAKUNDO_API void oakundo_undostack_push(OakUndoStack *self,
|
||||||
@@ -65,16 +70,13 @@ OAKUNDO_API void oakundo_undostack_update_actions(OakUndoStack *self);
|
|||||||
/* QAction* 句柄(GUI 菜单绑定用,borrowed) */
|
/* QAction* 句柄(GUI 菜单绑定用,borrowed) */
|
||||||
OAKUNDO_API void *oakundo_undostack_undo_action(OakUndoStack *self);
|
OAKUNDO_API void *oakundo_undostack_undo_action(OakUndoStack *self);
|
||||||
OAKUNDO_API void *oakundo_undostack_redo_action(OakUndoStack *self);
|
OAKUNDO_API void *oakundo_undostack_redo_action(OakUndoStack *self);
|
||||||
|
|
||||||
/* 事件(index_changed) */
|
|
||||||
#define OAKUNDO_EVENT_INDEX_CHANGED 1
|
|
||||||
typedef void (*oakundo_event_fn)(int event_id, int64_t a, int64_t b,
|
|
||||||
void *userdata);
|
|
||||||
OAKUNDO_API int64_t oakundo_undostack_subscribe(OakUndoStack *self,
|
|
||||||
int event_id, oakundo_event_fn fn, void *userdata);
|
|
||||||
OAKUNDO_API void oakundo_unsubscribe(int64_t subscription_id);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**无事件接口(2026-08 修订)**:上层对下层只有命令。push/undo/redo/jump
|
||||||
|
的调用方知道栈索引的变化(`oakundo_undostack_index` 调用后即可读),
|
||||||
|
`index_changed` 通知改由**调用方所在层**(facade 的 undo 适配层)在
|
||||||
|
每次变更命令后自行发出——oakundo 不持有任何上层回调。
|
||||||
|
|
||||||
## 3. 切割点(1 处)
|
## 3. 切割点(1 处)
|
||||||
|
|
||||||
`undocommand.cpp` include `node/project.h`(`get_relevant_project()`
|
`undocommand.cpp` include `node/project.h`(`get_relevant_project()`
|
||||||
@@ -96,7 +98,8 @@ Project* 本来就只是作为不透明身份被使用(修改标记归属)
|
|||||||
|
|
||||||
- 每条 API 正常+错误路径(NULL self、空 push 不入栈——
|
- 每条 API 正常+错误路径(NULL self、空 push 不入栈——
|
||||||
空 MultiUndoCommand 被删除的既有行为必须有 TEST 钉死)。
|
空 MultiUndoCommand 被删除的既有行为必须有 TEST 钉死)。
|
||||||
- push/undo/redo/jump/clear 全序列;command_text/is_done 边界行。
|
- push/undo/redo/jump/clear 全序列;command_text/is_done 边界行;
|
||||||
- 事件:push 后 index_changed 触发且 a=新 index;unsubscribe 后不再触发。
|
每步后 `oakundo_undostack_index` 读数与预期一致(替代原事件断言——
|
||||||
|
调用方知道影响,直接读状态)。
|
||||||
- 往返测试:C API 与适配类各做一遍 push-undo-redo,状态一致。
|
- 往返测试:C API 与适配类各做一遍 push-undo-redo,状态一致。
|
||||||
- `oakundo_debug_alive_count()`:init/free 配对无泄漏。
|
- `oakundo_debug_alive_count()`:init/free 配对无泄漏。
|
||||||
|
|||||||
@@ -40,16 +40,18 @@ project.h、timeline.h 的对应函数就是模板,参数命名前缀换
|
|||||||
| Track / TrackList | timeline | height/mute/lock/index/type、block 增删、split/ripple 原语 |
|
| Track / TrackList | timeline | height/mute/lock/index/type、block 增删、split/ripple 原语 |
|
||||||
| Block / ClipBlock / GapBlock / TransitionBlock | timeline | in/out/length/media_in、speed/reverse/loop、links |
|
| Block / ClipBlock / GapBlock / TransitionBlock | timeline | in/out/length/media_in、speed/reverse/loop、links |
|
||||||
| Footage | task | filename、streams、proxy、duration |
|
| Footage | task | filename、streams、proxy、duration |
|
||||||
| ProjectSerializer | task | save/load/copy/paste(clipboard 族,照 oakengine/serializer.h 模板) |
|
| ProjectSerializer | task / oakstorage | **剪贴板** copy/paste + 节点图 XML 的内存形态(SaveData/LoadData,照 oakengine/serializer.h 模板);**落盘 save/load 迁 oakstorage(M10),不在本模块** |
|
||||||
| ColorManager | render | config、default config、display transform |
|
| ColorManager | render | config、default config、display transform |
|
||||||
|
|
||||||
**特殊约定**:
|
**特殊约定**:
|
||||||
1. undoable 变体与 live 变体成对(`_live` 后缀或 `, void *command`
|
1. undoable 变体与 live 变体成对(`_live` 后缀或
|
||||||
尾参),与 oakengine 现状一致。
|
`, OakUndoCommand *command` 尾参——有类型句柄,禁 void*),
|
||||||
2. 事件:Node 族事件(label/input/keyframe/context 等)经
|
与 oakengine 现状一致。
|
||||||
`oaknode_subscribe(handle, event_id, fn, userdata)`——事件 ID 表
|
2. 无事件订阅接口(2026-08 修订,04 §3):oaknode 的所有修改都经
|
||||||
直接沿用 `oakengine/events.h` 的 70-95 段(值不变,便于
|
命令函数完成,调用方知道影响;Node 族的变更通知(label/input/
|
||||||
EngineEventBridge 逐步换绑)。
|
keyframe/context 等)由 facade 在执行命令后经既有 `oakengine_event`
|
||||||
|
通道发出(事件 id 沿用 `oakengine/events.h` 70-95 段,值不变),
|
||||||
|
oaknode 自身不持有任何上层回调。
|
||||||
3. `Node *`、`Project *` 等句柄即 `OakNodeNode *`/`OakNodeProject *`,
|
3. `Node *`、`Project *` 等句柄即 `OakNodeNode *`/`OakNodeProject *`,
|
||||||
不透明。
|
不透明。
|
||||||
4. 虚函数不出模块(01 §5);具体节点类型经
|
4. 虚函数不出模块(01 §5);具体节点类型经
|
||||||
@@ -73,8 +75,8 @@ M3 阶段判据(放宽版):oaknode 目录就位、C API 实现、oaknode_g
|
|||||||
|
|
||||||
- 重点:Node 增删连边、Project/Folder 层级、Track 属性、
|
- 重点:Node 增删连边、Project/Folder 层级、Track 属性、
|
||||||
keyframe live/undoable 对称、serializer 剪贴板往返、
|
keyframe live/undoable 对称、serializer 剪贴板往返、
|
||||||
factory 枚举与创建。
|
factory 枚举与创建;每个变更命令执行后直接读状态断言生效
|
||||||
- 事件:每族至少 1 个 subscribe/trigger/unsubscribe 用例。
|
(无事件可断言——通知在 facade 层测)。
|
||||||
- 枚举序数:NodeValue::Type ⇄ oak_node_value_type 映射表(已在
|
- 枚举序数:NodeValue::Type ⇄ oak_node_value_type 映射表(已在
|
||||||
nodevaluehandle.h 钉过一次,oaknode 测试再钉一次,防两侧漂移)。
|
nodevaluehandle.h 钉过一次,oaknode 测试再钉一次,防两侧漂移)。
|
||||||
- `oaknode_debug_alive_count()` 泄漏断言。
|
- `oaknode_debug_alive_count()` 泄漏断言。
|
||||||
|
|||||||
@@ -35,17 +35,16 @@ OAKTL_API int oaktimeline_marker_at(const OakTimelineMarkerList *l, int i,
|
|||||||
int64_t *in_ts, int64_t *out_ts, int *color, char *name_buf, int n);
|
int64_t *in_ts, int64_t *out_ts, int *color, char *name_buf, int n);
|
||||||
OAKTL_API int oaktimeline_marker_add(OakTimelineMarkerList *l,
|
OAKTL_API int oaktimeline_marker_add(OakTimelineMarkerList *l,
|
||||||
int64_t in_ts, int64_t out_ts, const char *name, int color,
|
int64_t in_ts, int64_t out_ts, const char *name, int color,
|
||||||
void *command); /* command=NULL 时自行入栈 */
|
OakUndoCommand *command); /* command=NULL 时自行入栈(2026-08:void* → 有类型句柄) */
|
||||||
OAKTL_API int oaktimeline_marker_remove_at(OakTimelineMarkerList *l,
|
OAKTL_API int oaktimeline_marker_remove_at(OakTimelineMarkerList *l,
|
||||||
int i, void *command);
|
int i, OakUndoCommand *command);
|
||||||
OAKTL_API int oaktimeline_marker_set_time(OakTimelineMarkerList *l,
|
OAKTL_API int oaktimeline_marker_set_time(OakTimelineMarkerList *l,
|
||||||
int i, int64_t in_ts, int64_t out_ts, void *command);
|
int i, int64_t in_ts, int64_t out_ts, OakUndoCommand *command);
|
||||||
OAKTL_API int oaktimeline_marker_set_props(OakTimelineMarkerList *l,
|
OAKTL_API int oaktimeline_marker_set_props(OakTimelineMarkerList *l,
|
||||||
int i, int color, const char *name, void *command);
|
int i, int color, const char *name, OakUndoCommand *command);
|
||||||
/* 事件:MARKER_ADDED/REMOVED/MODIFIED(id 沿用 oakengine events 段) */
|
/* 无事件接口(2026-08 修订,04 §3):marker 的增删改都是调用方发的
|
||||||
OAKTL_API int64_t oaktimeline_subscribe(void *handle, int32_t event_id,
|
* 命令,MARKER_ADDED/REMOVED/MODIFIED 通知由调用方所在层(facade,
|
||||||
oaktl_event_fn fn, void *userdata);
|
* id 沿用 oakengine events 段)在命令后发出。 */
|
||||||
OAKTL_API void oaktimeline_unsubscribe(int64_t id);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.2 `oaktimeline/workarea.h`
|
### 2.2 `oaktimeline/workarea.h`
|
||||||
@@ -58,9 +57,9 @@ OAKTL_API int oaktimeline_workarea_set_range(OakTimelineWorkarea *w,
|
|||||||
int64_t in_ts, int64_t out_ts); /* live */
|
int64_t in_ts, int64_t out_ts); /* live */
|
||||||
OAKTL_API int oaktimeline_workarea_set_range_undoable(
|
OAKTL_API int oaktimeline_workarea_set_range_undoable(
|
||||||
OakTimelineWorkarea *w, int64_t in_ts, int64_t out_ts,
|
OakTimelineWorkarea *w, int64_t in_ts, int64_t out_ts,
|
||||||
int64_t old_in_ts, int64_t old_out_ts, void *command);
|
int64_t old_in_ts, int64_t old_out_ts, OakUndoCommand *command);
|
||||||
OAKTL_API int oaktimeline_workarea_set_enabled_undoable(
|
OAKTL_API int oaktimeline_workarea_set_enabled_undoable(
|
||||||
OakTimelineWorkarea *w, int enabled, void *command);
|
OakTimelineWorkarea *w, int enabled, OakUndoCommand *command);
|
||||||
OAKTL_API void oaktimeline_workarea_reset(int64_t *in_ts, int64_t *out_ts);
|
OAKTL_API void oaktimeline_workarea_reset(int64_t *in_ts, int64_t *out_ts);
|
||||||
/* load/save 经 oakcommon_xml 句柄在 oaknode 序列化路径调用 */
|
/* load/save 经 oakcommon_xml 句柄在 oaknode 序列化路径调用 */
|
||||||
OAKTL_API int oaktimeline_workarea_load(OakTimelineWorkarea *w,
|
OAKTL_API int oaktimeline_workarea_load(OakTimelineWorkarea *w,
|
||||||
@@ -107,7 +106,8 @@ OAKTL_API int64_t oaktimeline_nearest_block_ts(OakNodeTrack *track,
|
|||||||
|
|
||||||
## 4. 测试(映射 03 §2/§3)
|
## 4. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
- marker:增删改查、undo 往返(push 后 undo 恢复)、事件三件套。
|
- marker:增删改查、undo 往返(push 后 undo 恢复);每命令后读
|
||||||
|
count/at 断言生效(无事件——通知在 facade 层测)。
|
||||||
- workarea:set/get、undoable 旧值恢复、reset 哨兵、xml 往返。
|
- workarea:set/get、undoable 旧值恢复、reset 哨兵、xml 往返。
|
||||||
- edit:每个 `_command` 工厂 1 个"构造→入栈→undo 还原"用例
|
- edit:每个 `_command` 工厂 1 个"构造→入栈→undo 还原"用例
|
||||||
(track 增删、place/replace/trim/split/ripple/slide)。
|
(track 增删、place/replace/trim/split/ripple/slide)。
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ OAKAU_API int oakaudio_manager_push(const float *samples,
|
|||||||
int frame_count, double speed);
|
int frame_count, double speed);
|
||||||
OAKAU_API int oakaudio_manager_is_playing(void);
|
OAKAU_API int oakaudio_manager_is_playing(void);
|
||||||
OAKAU_API void oakaudio_manager_stop(void);
|
OAKAU_API void oakaudio_manager_stop(void);
|
||||||
/* 输出参数变化事件 */
|
/* 无事件接口(2026-08 修订,04 §3):输出参数的修改是调用方发的
|
||||||
OAKAU_API int64_t oakaudio_manager_subscribe_params_changed(
|
* 命令(set_params),params_changed 通知由调用方所在层发出。 */
|
||||||
oakaudio_event_fn fn, void *userdata);
|
|
||||||
OAKAU_API void oakaudio_unsubscribe(int64_t id);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. 切割点
|
## 3. 切割点
|
||||||
@@ -63,5 +61,5 @@ OAKAU_API void oakaudio_unsubscribe(int64_t id);
|
|||||||
- processor:open/convert/close 全链(44.1k stereo → 48k stereo,
|
- processor:open/convert/close 全链(44.1k stereo → 48k stereo,
|
||||||
帧数换算正确、无爆音断言用能量差阈值)、速度 1.5x。
|
帧数换算正确、无爆音断言用能量差阈值)、速度 1.5x。
|
||||||
- manager:无音频设备环境用 null backend 初始化(现有后端探测
|
- manager:无音频设备环境用 null backend 初始化(现有后端探测
|
||||||
模式),params set/get 往返、事件触发。
|
模式),params set/get 往返(set 后 get 读数即生效——无事件)。
|
||||||
- `oakaudio_debug_alive_count()` 泄漏断言。
|
- `oakaudio_debug_alive_count()` 泄漏断言。
|
||||||
|
|||||||
@@ -51,9 +51,10 @@ OAKRD_API int oakrender_frame_cache_load(OakRenderCache *c,
|
|||||||
OakCodecFrame **out_frame);
|
OakCodecFrame **out_frame);
|
||||||
OAKRD_API void oakrender_frame_cache_save(OakRenderCache *c,
|
OAKRD_API void oakrender_frame_cache_save(OakRenderCache *c,
|
||||||
const char *path, const char *uuid, const OakCodecFrame *f);
|
const char *path, const char *uuid, const OakCodecFrame *f);
|
||||||
/* 缓存事件(playback invalidated/validated、frame invalidated) */
|
/* 无缓存事件(2026-08 修订,04 §3):缓存的 invalidate/validate 由
|
||||||
OAKRD_API int64_t oakrender_cache_subscribe(OakRenderCache *c,
|
* 编辑命令的调用方触发并知情,通知由 facade 在命令后发出;
|
||||||
int32_t event_id, oakrender_event_fn fn, void *userdata);
|
* oakrender 不持有上层回调。渲染 ticket 的完成回调属异步命令
|
||||||
|
* 返回通道,不在此限(见 renderer.h 族)。 */
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.3 `oakrender/color.h`
|
### 2.3 `oakrender/color.h`
|
||||||
@@ -107,7 +108,8 @@ OAKRD_API int oakrender_disk_cache_clear(void);
|
|||||||
|
|
||||||
## 4. 测试(映射 03 §2/§3)
|
## 4. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
- cache:invalidate/validate 状态机、帧缓存存取往返、事件触发。
|
- cache:invalidate/validate 状态机、帧缓存存取往返(调用方触发后
|
||||||
|
读状态断言——无事件)。
|
||||||
- color:默认 config 建置、processor convert 已知值(sRGB→Linear
|
- color:默认 config 建置、processor convert 已知值(sRGB→Linear
|
||||||
抽样点数值断言,容差 1e-3)。
|
抽样点数值断言,容差 1e-3)。
|
||||||
- manager:request_frame 对 demo.mp4 + 最小 sequence 出帧非空
|
- manager:request_frame 对 demo.mp4 + 最小 sequence 出帧非空
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# M8 · oaktask 拆分手册
|
# M8 · oaktask 拆分手册
|
||||||
|
|
||||||
> 内容:`engine/task/`(Task 基类、TaskManager、project/
|
> 内容:`engine/task/`(Task 基类、TaskManager、任务编排、cache 任务;
|
||||||
> load/save/import/loadotio/saveotio、cache 任务)。
|
> **工程文件 IO——project/load/save/loadotio/saveotio 的落盘部分——
|
||||||
|
> 已划给 oakstorage(M10),本模块只做任务壳**)。
|
||||||
> 依赖:node 38、codec 4、render 4、common 4、config 2、timeline 1、
|
> 依赖:node 38、codec 4、render 4、common 4、config 2、timeline 1、
|
||||||
> coreengine 2。
|
> coreengine 2。
|
||||||
> 拆分顺序第 8 位。
|
> 拆分顺序第 8 位。
|
||||||
@@ -30,7 +31,8 @@ OAKTK_API int oaktask_task_succeeded(const OakTaskTask *t);
|
|||||||
OAKTK_API int oaktask_task_progress(const OakTaskTask *t, double *out);
|
OAKTK_API int oaktask_task_progress(const OakTaskTask *t, double *out);
|
||||||
OAKTK_API int oaktask_task_title(OakTaskTask *t, char *buf, int n);
|
OAKTK_API int oaktask_task_title(OakTaskTask *t, char *buf, int n);
|
||||||
OAKTK_API int oaktask_task_error(OakTaskTask *t, char *buf, int n);
|
OAKTK_API int oaktask_task_error(OakTaskTask *t, char *buf, int n);
|
||||||
/* 事件:STARTED/PROGRESS/FINISHED(id 沿用 oakengine events 段) */
|
/* 进度/完成回调:任务是异步命令,回调即其返回通道——04 §3 唯一
|
||||||
|
* 例外情形(一次性语义,FINISHED 后自动失效) */
|
||||||
OAKTK_API int64_t oaktask_task_subscribe(OakTaskTask *t, int32_t event_id,
|
OAKTK_API int64_t oaktask_task_subscribe(OakTaskTask *t, int32_t event_id,
|
||||||
oaktask_event_fn fn, void *userdata);
|
oaktask_event_fn fn, void *userdata);
|
||||||
```
|
```
|
||||||
@@ -42,6 +44,9 @@ OAKTK_API OakTaskTask *oaktask_create_project_load(const char *filename);
|
|||||||
OAKTK_API OakTaskTask *oaktask_create_project_save(OakNodeProject *p,
|
OAKTK_API OakTaskTask *oaktask_create_project_save(OakNodeProject *p,
|
||||||
const char *filename_or_NULL, int use_compression,
|
const char *filename_or_NULL, int use_compression,
|
||||||
const void *layout_or_NULL);
|
const void *layout_or_NULL);
|
||||||
|
/* 注:以上两个工厂是薄壳——文件 IO 全部委托 oakstorage(M10 §2.4),
|
||||||
|
* 任务只保留进度/取消/事件编排;`use_compression` 等打包进
|
||||||
|
* oakstorage_save 的 options 位掩码。 */
|
||||||
OAKTK_API OakTaskTask *oaktask_create_project_import(OakNodeNode *folder,
|
OAKTK_API OakTaskTask *oaktask_create_project_import(OakNodeNode *folder,
|
||||||
const char *const *urls, int url_count);
|
const char *const *urls, int url_count);
|
||||||
OAKTK_API OakTaskTask *oaktask_create_project_load_otio(
|
OAKTK_API OakTaskTask *oaktask_create_project_load_otio(
|
||||||
@@ -50,7 +55,7 @@ OAKTK_API OakTaskTask *oaktask_create_project_save_otio(
|
|||||||
OakNodeProject *p, const char *filename, const int *sequence_indexes,
|
OakNodeProject *p, const char *filename, const int *sequence_indexes,
|
||||||
int count);
|
int count);
|
||||||
/* import 结果(task 成功后读,borrowed) */
|
/* import 结果(task 成功后读,borrowed) */
|
||||||
OAKTK_API void *oaktask_import_take_command(OakTaskTask *t); /* 所有权转移 */
|
OAKTK_API OakUndoCommand *oaktask_import_take_command(OakTaskTask *t); /* 所有权转移(2026-08 修订:按 01 §0.1 由 void* 改为有类型句柄) */
|
||||||
OAKTK_API int oaktask_import_footage_count(OakTaskTask *t);
|
OAKTK_API int oaktask_import_footage_count(OakTaskTask *t);
|
||||||
OAKTK_API OakNodeNode *oaktask_import_footage_at(OakTaskTask *t, int i);
|
OAKTK_API OakNodeNode *oaktask_import_footage_at(OakTaskTask *t, int i);
|
||||||
OAKTK_API int oaktask_import_invalid_count(OakTaskTask *t);
|
OAKTK_API int oaktask_import_invalid_count(OakTaskTask *t);
|
||||||
@@ -66,10 +71,9 @@ OAKTK_API OakNodeProject *oaktask_load_take_project(OakTaskTask *t);
|
|||||||
OAKTK_API int oaktask_manager_count(void);
|
OAKTK_API int oaktask_manager_count(void);
|
||||||
OAKTK_API OakTaskTask *oaktask_manager_at(int i); /* borrowed */
|
OAKTK_API OakTaskTask *oaktask_manager_at(int i); /* borrowed */
|
||||||
OAKTK_API void oaktask_manager_delete_finished(void);
|
OAKTK_API void oaktask_manager_delete_finished(void);
|
||||||
/* 事件:TASK_ADDED/REMOVED/FAILED/LIST_CHANGED */
|
/* 无 manager 事件(2026-08 修订,04 §3):任务的创建/删除都由调用方
|
||||||
OAKTK_API int64_t oaktask_manager_subscribe(int32_t event_id,
|
* 发起(facade 建任务即知 ADDED;delete_finished 的调用方知 REMOVED),
|
||||||
oaktask_event_fn fn, void *userdata);
|
* LIST_CHANGED 通知由调用方所在层发出。 */
|
||||||
OAKTK_API void oaktask_unsubscribe(int64_t id);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. 切割点
|
## 3. 切割点
|
||||||
@@ -77,6 +81,7 @@ OAKTK_API void oaktask_unsubscribe(int64_t id);
|
|||||||
| 现状 | 处理 |
|
| 现状 | 处理 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| task → node/ 38(footage 6、project 5、sequence 3、serializer/layout 3、colormanager 2 等) | 全部经 oaknode C ABI + 适配类(M3 已就位)——本手册工作量主体 |
|
| task → node/ 38(footage 6、project 5、sequence 3、serializer/layout 3、colormanager 2 等) | 全部经 oaknode C ABI + 适配类(M3 已就位)——本手册工作量主体 |
|
||||||
|
| task → 工程文件 IO(load/save/loadotio/saveotio 落盘) | 划给 **oakstorage**(M3a/M10):任务工厂保留签名,实现改为委托 `oakstorage_open/save` |
|
||||||
| task → codec/ 4 | 经 oakcodec C ABI(M5) |
|
| task → codec/ 4 | 经 oakcodec C ABI(M5) |
|
||||||
| task → render/ 4 | 经 oakrender C ABI(M7) |
|
| task → render/ 4 | 经 oakrender C ABI(M7) |
|
||||||
| task → timeline/ 1 | 经 oaktimeline C ABI(M4) |
|
| task → timeline/ 1 | 经 oaktimeline C ABI(M4) |
|
||||||
@@ -88,7 +93,7 @@ OAKTK_API void oaktask_unsubscribe(int64_t id);
|
|||||||
入栈)、save/load 往返(临时目录 .ove,load 后 project 非空、
|
入栈)、save/load 往返(临时目录 .ove,load 后 project 非空、
|
||||||
root 非空)。
|
root 非空)。
|
||||||
- start_sync 成功/失败路径(不存在文件 → failed + error 非空)。
|
- start_sync 成功/失败路径(不存在文件 → failed + error 非空)。
|
||||||
- 事件:STARTED→PROGRESS→FINISHED 序列(导入任务断言至少一次
|
- 事件(异步任务例外):STARTED→PROGRESS→FINISHED 序列(导入任务
|
||||||
PROGRESS 且 FINISHED.succeeded==1)。
|
断言至少一次 PROGRESS 且 FINISHED.succeeded==1)。
|
||||||
- manager:添加/删除/list_changed 事件。
|
- manager:count/at/delete_finished 行为(无事件,直接读状态断言)。
|
||||||
- `oaktask_debug_alive_count()` 泄漏断言。
|
- `oaktask_debug_alive_count()` 泄漏断言。
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ OAKPL_API int oakplugin_instance_get_param(OakPluginInstance *i,
|
|||||||
const char *param_id, oak_node_value *out);
|
const char *param_id, oak_node_value *out);
|
||||||
OAKPL_API int oakplugin_instance_render(OakPluginInstance *i,
|
OAKPL_API int oakplugin_instance_render(OakPluginInstance *i,
|
||||||
OakCodecFrame *dst, const OakCodecFrame *src, int64_t ts);
|
OakCodecFrame *dst, const OakCodecFrame *src, int64_t ts);
|
||||||
/* 进度事件(R6 已把 cancelled 信号改成 C 回调,沿用该机制) */
|
/* 进度回调:instance_render 是异步命令(渲染中进行),进度/取消回调
|
||||||
|
* 即其返回通道——04 §3 唯一例外情形(R6 已把 cancelled 信号改成 C
|
||||||
|
* 回调,沿用该机制)。非异步接口一律不配回调。 */
|
||||||
OAKPL_API void oakplugin_instance_set_progress_cb(OakPluginInstance *i,
|
OAKPL_API void oakplugin_instance_set_progress_cb(OakPluginInstance *i,
|
||||||
oakplugin_progress_fn fn, void *userdata);
|
oakplugin_progress_fn fn, void *userdata);
|
||||||
OAKPL_API void oakplugin_instance_cancel(OakPluginInstance *i);
|
OAKPL_API void oakplugin_instance_cancel(OakPluginInstance *i);
|
||||||
|
|||||||
Reference in New Issue
Block a user