docs: module-split execution manuals for RIIR stage 1 (plans/riir/)
13 files: 00 overview, 01 two-layer adapter spec (init/free/func + opaque handles + same-name adapter classes, frozen naming/ownership/ error/event rules), 02 dependency matrix + split order M1-M9 (from a 490-file include scan), 03 Google Test spec (per-C-API coverage, roundtrip tests, leak counters), M1-M9 per-module manuals with frozen C APIs (common/undo/node/timeline/codec/audio/render/task/plugin) and the facade assembly-layer verdict.
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
| 文档 | 内容 | 启动前提 |
|
| 文档 | 内容 | 启动前提 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| [`riir.md`](riir.md) | **RIIR 绞杀者模式执行计划**:C ABI 迁移完成后,把 liboakengine 安全拆成若干小模块,再逐个用 Rust 重写;含 API 冻结保证、模块图、六步流程与验证门禁 | C ABI 迁移战役验收完成 |
|
| [`riir.md`](riir.md) | **RIIR 绞杀者模式执行计划**:C ABI 迁移完成后,把 liboakengine 安全拆成若干小模块,再逐个用 Rust 重写;含 API 冻结保证、模块图、六步流程与验证门禁 | C ABI 迁移战役验收完成 |
|
||||||
|
| [`riir/`](riir/) | **模块拆分执行手册**(riir.md 第一阶段落地):00 总览 → 01 双层适配器规范 → 02 依赖矩阵与拆分顺序 → 03 测试规范 → M1-M9 逐模块手册(C API 冻结);只拆分不重写,模块间 C ABI | R7 完成后启动(可与 R7 并行准备) |
|
||||||
| [`ai-agent-design.md`](ai-agent-design.md) | **AI Agent 设计**:多模态 LLM 经 MCP 调用策展工具面自动剪辑,渲染帧回喂形成"编辑→看图→再编辑"视觉闭环;含工具面、回放回路、安全与测试 | RIIR 拆分完成(面对一堆小库) |
|
| [`ai-agent-design.md`](ai-agent-design.md) | **AI Agent 设计**:多模态 LLM 经 MCP 调用策展工具面自动剪辑,渲染帧回喂形成"编辑→看图→再编辑"视觉闭环;含工具面、回放回路、安全与测试 | RIIR 拆分完成(面对一堆小库) |
|
||||||
| [`gtest-migration-guide.md`](gtest-migration-guide.md) | **测试统一到 Google Test**:把 OAK_ADD_TEST 宏框架、纯 C assert、已有 gtest 三套收敛为单一 Google Test,ctest 仅作运行器 | R5 验收完成后启动(可与 UI 改版并行) |
|
| [`gtest-migration-guide.md`](gtest-migration-guide.md) | **测试统一到 Google Test**:把 OAK_ADD_TEST 宏框架、纯 C assert、已有 gtest 三套收敛为单一 Google Test,ctest 仅作运行器 | R5 验收完成后启动(可与 UI 改版并行) |
|
||||||
| [`ui-redesign-plan.md`](ui-redesign-plan.md) | **主界面 UI 改版**:依据 `design/` 三张设计图落地 10 个工作包(工具条、双监看、效果栈检查器、节点编辑器移位、电平条、状态栏等),全部文字精确定义 | R5 验收完成后启动(可与 GTest 迁移并行) |
|
| [`ui-redesign-plan.md`](ui-redesign-plan.md) | **主界面 UI 改版**:依据 `design/` 三张设计图落地 10 个工作包(工具条、双监看、效果栈检查器、节点编辑器移位、电平条、状态栏等),全部文字精确定义 | R5 验收完成后启动(可与 GTest 迁移并行) |
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# RIIR 模块拆分计划 · 00 总览
|
||||||
|
|
||||||
|
> 本目录是 liboakengine 的**模块拆分**执行计划:把单个
|
||||||
|
> liboakengine.so 拆成一组小库,模块之间只经 C ABI 调用。
|
||||||
|
> **只拆分,不重写**——这是 riir.md 绞杀者路线的第一阶段,拆分完成
|
||||||
|
> 并稳定后,才逐模块用 Rust 重写(届时模块的 C ABI 原样保留,Rust
|
||||||
|
> 实现替换 C++ 实现对调用方透明)。
|
||||||
|
>
|
||||||
|
> 阅读顺序:`00`(本文)→ `01-adapter-pattern.md`(适配器规范,
|
||||||
|
> 所有模块共用)→ `02-modules-and-order.md`(模块清单、依赖矩阵、
|
||||||
|
> 拆分顺序)→ `03-testing.md`(测试规范)→ `M1`…`M9`(逐模块执行
|
||||||
|
> 手册,**C API 已在各手册中冻结**)。
|
||||||
|
|
||||||
|
## 目标与判据
|
||||||
|
|
||||||
|
终态:
|
||||||
|
|
||||||
|
```
|
||||||
|
oakcore(已有,不动)
|
||||||
|
oakcommon ─ oakundo ─ oaknode ─ oaktimeline ─ oakcodec ─ oakrender ─ oaktask ─ oakplugin
|
||||||
|
│
|
||||||
|
oakaudio ───────────────────┤
|
||||||
|
▼
|
||||||
|
liboakengine(= facade + coreengine,纯装配层)
|
||||||
|
```
|
||||||
|
|
||||||
|
完成判据(每条都可命令验证):
|
||||||
|
|
||||||
|
1. 每个模块是独立 CMake 目标(`add_library(oak<mod> STATIC|SHARED ...)`),
|
||||||
|
有自己的 `include/` 公共头目录;模块 A 链接模块 B 时**只包含** B 的
|
||||||
|
`include/oak<mod>/` 下的头,不包含 B 的私有头。
|
||||||
|
2. 模块间调用 100% 经 C ABI(`extern "C"`,见 01)。验证:
|
||||||
|
`nm -D --defined-only liboak<mod>.so | grep -c " T _Z"` = 0(不导出
|
||||||
|
C++ 符号),且消费方 `nm -D | grep " U _ZN5olive"` = 0。
|
||||||
|
3. 全量构建 0 error;全量 ctest 绿(45+,随模块测试增加只增不减)。
|
||||||
|
4. 每个模块的每个 C API 有 Google Test 覆盖(规范见 03)。
|
||||||
|
|
||||||
|
## 铁律
|
||||||
|
|
||||||
|
1. **只拆不写**:除边界适配器(01)和必要的反向依赖切割外,不改任何
|
||||||
|
函数实现、不改行为。每个模块拆完,ctest 必须保持全绿。
|
||||||
|
2. **C API 先冻结后实现**:每个模块手册(M1-M9)里的 C API 表就是
|
||||||
|
契约,实现不得偏离;执行中确需调整的,先改手册再改代码,并在手册
|
||||||
|
里标注修订记录。
|
||||||
|
3. **qmake 式渐进**:一次只拆一个模块,闭环(构建+ctest+nm)后提交,
|
||||||
|
再开下一个。顺序见 02,按依赖叶先根后。
|
||||||
|
4. 三条红线沿用(禁 inline 化、禁 stub、禁 dlsym)。
|
||||||
|
5. Qt 依赖:模块可继续用 Qt(拆分不是去 Qt),但 C ABI 边界上不许
|
||||||
|
出现 Qt 类型(QString/QVariant/QList/信号槽),全部 POD 化 + 回调
|
||||||
|
(见 01 §4)。
|
||||||
|
|
||||||
|
## 术语
|
||||||
|
|
||||||
|
- **提供侧(callee)**:被调用的模块,按 01 §1 实现 C API。
|
||||||
|
- **消费侧(caller)**:调用方模块,按 01 §2 用同名 C++ 适配类包住
|
||||||
|
C API,使本模块内原有调用点代码**零改动**。
|
||||||
|
- **跨界类**:被其他模块消费的类(进入该模块的 C API 表面)。
|
||||||
|
- **切割点**:阻碍模块独立的反向/环依赖 include,逐条在模块手册里
|
||||||
|
列出并给出处理方式。
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
# 01 · 双层适配器模式(所有模块共用规范)
|
||||||
|
|
||||||
|
> 本规范是拆分能在"只拆不写"约束下成立的核心机制。任何模块的
|
||||||
|
> C API 设计与适配类实现都必须照此执行。命名、内存所有权、错误码、
|
||||||
|
> 线程与信号的处理在此**冻结**。
|
||||||
|
|
||||||
|
## 1. 提供侧:C API 层
|
||||||
|
|
||||||
|
对被消费的每个 C++ 类 `Clazz`,模块在 `include/oak<mod>/clazz.h`
|
||||||
|
里暴露一组 `extern "C"` 函数:
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* 构造/析构 */
|
||||||
|
OAKMOD_API OakModClazz *oakmod_clazz_init(/* 与某个构造函数对应的参数 */);
|
||||||
|
OAKMOD_API void oakmod_clazz_free(OakModClazz *self);
|
||||||
|
|
||||||
|
/* 普通成员函数:self 为第一参数,其余参数按 §3 POD 化 */
|
||||||
|
OAKMOD_API <ret> oakmod_clazz_<func>(OakModClazz *self, ...);
|
||||||
|
|
||||||
|
/* 静态成员函数:无 self */
|
||||||
|
OAKMOD_API <ret> oakmod_clazz_<func>_s(/* 参数 */);
|
||||||
|
```
|
||||||
|
|
||||||
|
规则:
|
||||||
|
|
||||||
|
1. `OakModClazz` 是**不透明类型**(`typedef struct OakModClazz OakModClazz;`),
|
||||||
|
提供侧实现里它就是 `olive::Clazz*`(`reinterpret_cast`),消费侧
|
||||||
|
永远无法解引用。
|
||||||
|
2. `init` 每个对应一个实际在用的构造函数重载;返回 NULL 表示失败。
|
||||||
|
`free` 对 NULL 是 no-op。**所有权:init 创建的对象归调用方,必须
|
||||||
|
配对 free**;借用指针(不转移所有权)在文档注释里写 `/* borrowed */`。
|
||||||
|
3. 多个构造重载用后缀区分:`oakmod_clazz_init`(默认)、
|
||||||
|
`oakmod_clazz_init_from_file`、`oakmod_clazz_init_copy` 等。
|
||||||
|
4. 命名全小写,模块前缀 `oak<mod>_`(oakundo/oaknode/oaktimeline/
|
||||||
|
oakcodec/oakrender/oaktask/oakaudio/oakplugin/oakcommon)。
|
||||||
|
5. 导出宏 `OAKMOD_API` 照 `oakengine/export.h` 样式
|
||||||
|
(`__attribute__((visibility("default")))`),模块编译加
|
||||||
|
`-fvisibility=hidden`——每个模块**出生即 visibility 干净**,
|
||||||
|
R7-B 的全局收口变成顺水推舟。
|
||||||
|
|
||||||
|
## 2. 消费侧:同名 C++ 适配类
|
||||||
|
|
||||||
|
消费模块里放一个与原始类**同名**的适配类(放在消费侧私有头
|
||||||
|
`<mod>/adapter/clazz.h`,namespace 保持 `olive`):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace olive {
|
||||||
|
|
||||||
|
class Clazz {
|
||||||
|
public:
|
||||||
|
Clazz(/* 原构造签名 */)
|
||||||
|
: h_(oakmod_clazz_init(/* 相同参数 */)) {}
|
||||||
|
~Clazz() { oakmod_clazz_free(h_); }
|
||||||
|
|
||||||
|
// 原成员函数签名原样保留,体内转发一行
|
||||||
|
Ret func(Arg a) { return oakmod_clazz_func(h_, a); }
|
||||||
|
|
||||||
|
// 句柄逃生口(确实需要混用时的过渡手段,注释标注)
|
||||||
|
OakModClazz *handle() const { return h_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
OakModClazz *h_;
|
||||||
|
// 拷贝语义与原类一致:原类不可拷贝就 = delete;
|
||||||
|
// 原类可拷贝则 init_copy。
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
效果:消费模块里所有 `clazz.func(a)`、`new Clazz(...)`、
|
||||||
|
`clazz->func(a)` 调用点**零改动**——原来 include 提供侧 C++ 头的
|
||||||
|
地方,改 include 本模块的适配头即可。这就是"双层适配器":
|
||||||
|
**C++ 调用点 → 同名适配类 → C ABI → 提供侧 C++ 实现**。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- 适配类是**逐模块私有的**:oakrender 消费的 Node 适配类和
|
||||||
|
oaktimeline 消费的 Node 适配类是两份独立的小头文件,各自只有
|
||||||
|
自己用到的方法子集。允许重复,禁止共享(共享就重新耦合了)。
|
||||||
|
- 适配类只转发**本模块实际用到**的方法(按 02 的依赖矩阵逐个
|
||||||
|
grep 确认清单,写在模块手册里)。
|
||||||
|
- 原类有继承体系的(如 UndoCommand 子类族),见 §5。
|
||||||
|
|
||||||
|
## 3. 参数与返回值的 POD 化
|
||||||
|
|
||||||
|
C ABI 上只允许:整数、`double`、`int64_t`、指针、`const char *`、
|
||||||
|
以及 `include/oak<mod>/types.h` 里定义的纯 C POD(无构造/析构/
|
||||||
|
方法)。Qt/C++ 类型按下表映射:
|
||||||
|
|
||||||
|
| C++ 类型 | C ABI 形态 |
|
||||||
|
|---|---|
|
||||||
|
| `QString` | `const char *`(入,UTF-8);出:buf/size 两段式(先 NULL 查长度) |
|
||||||
|
| `olive::core::Rational` | 两个 `int64_t`(num, den);时间戳用 `int64_t` 帧戳 |
|
||||||
|
| `TimeRange` | 两个 `int64_t`(in_ts, out_ts) |
|
||||||
|
| `QVariant`/`NodeValue` | `oak_node_value` POD(oakengine/node.h 已有,各模块复用该定义或复制同构 POD) |
|
||||||
|
| `VideoParams`/`AudioParams` | `oak_video_params`/POD 字段拍平 |
|
||||||
|
| `QList<T>`/`QVector<T>` | 指针 + count;返回集合用 `count` + `at(i)` 访问器对 |
|
||||||
|
| `Color` | 4 × double |
|
||||||
|
| `Qt::enum`/内部枚举 | `int`(取值表写进手册,两侧枚举**序数一致性**用 static_assert 或测试钉死) |
|
||||||
|
| `std::shared_ptr<T>` | 不透明句柄 + retain/free(协议见 display.h R7-A 的先例) |
|
||||||
|
|
||||||
|
## 4. 信号、回调与线程
|
||||||
|
|
||||||
|
Qt 信号不许跨模块。处理优先级:
|
||||||
|
|
||||||
|
1. **回调注册**:`oakmod_clazz_set_<event>_cb(self, fn, userdata)`,
|
||||||
|
提供侧在发信号处调 `fn(event_pod, userdata)`。userdata 所有权归
|
||||||
|
注册方,适配类析构时先 `set_*_cb(self, NULL, NULL)` 反注册。
|
||||||
|
2. **事件总线**:模块级通知(非单对象)用
|
||||||
|
`oakmod_subscribe(event_id, fn, userdata)` → 返回订阅 id,
|
||||||
|
`oakmod_unsubscribe(id)`——照 `oakengine/events.h` 的现成模式。
|
||||||
|
3. 线程语义照现状:提供侧在发射线程同步调回调(DirectConnection
|
||||||
|
等价),需要跨线程排队是消费侧适配类自己的事(它可以用
|
||||||
|
`QMetaObject::invokeMethod(..., Qt::QueuedConnection)`)。
|
||||||
|
4. **适配类可以把 C 回调再转回 Qt 信号**:适配类继承 QObject、
|
||||||
|
静态 trampoline 里 `emit` 同名信号——消费侧原有 `connect()` 全部
|
||||||
|
零改动。这是大多数 widget 侧适配的默认做法。
|
||||||
|
|
||||||
|
## 5. 继承与虚函数
|
||||||
|
|
||||||
|
原类的继承体系**不出模块**。跨界时:
|
||||||
|
|
||||||
|
- 基类引用(如 `UndoCommand *`)→ 基类句柄(`OakUndoCommand *`)。
|
||||||
|
- 消费侧不构造具体子类,只经提供侧的工厂函数:
|
||||||
|
`oakmod_clazz_create_<variant>(...)`(返回基类句柄)。
|
||||||
|
- 消费侧确实需要子类行为的(极少数,需逐个人工论证),在提供侧
|
||||||
|
加专用函数,不放虚函数跨界。
|
||||||
|
|
||||||
|
## 6. 错误码
|
||||||
|
|
||||||
|
沿用 facade 约定:`OAKMOD_OK=0`,负值错误
|
||||||
|
(`OAKMOD_E_INVALID/E_STATE/E_NOT_FOUND/E_FAILED`),在各模块
|
||||||
|
`types.h` 里统一定义(值与 oakengine 的现有值对齐)。会失败的
|
||||||
|
`init` 返回 NULL,错误细节经 `oakmod_last_error(buf, size)` 取
|
||||||
|
(线程局部,照 facade 的 `set_error` 模式)。
|
||||||
|
|
||||||
|
## 7. 一个完整例子(UndoStack,M2 照此落地)
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* oakundo/include/oakundo/undostack.h */
|
||||||
|
typedef struct OakUndoStack OakUndoStack;
|
||||||
|
OAKUNDO_API OakUndoStack *oakundo_undostack_init(void *parent_qobject);
|
||||||
|
OAKUNDO_API void oakundo_undostack_free(OakUndoStack *self);
|
||||||
|
OAKUNDO_API void oakundo_undostack_push(OakUndoStack *self,
|
||||||
|
OakUndoCommand *cmd, const char *name);
|
||||||
|
OAKUNDO_API int oakundo_undostack_can_undo(const OakUndoStack *self);
|
||||||
|
/* ... 完整表见 M2 手册 ... */
|
||||||
|
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 id);
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 消费侧 adapter/undostack.h —— app/other 模块里 connect() 零改动
|
||||||
|
class UndoStack : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit UndoStack(QObject *p = nullptr)
|
||||||
|
: QObject(p), h_(oakundo_undostack_init(p)) {
|
||||||
|
sub_ = oakundo_undostack_subscribe(h_, OAKUNDO_EVENT_INDEX_CHANGED,
|
||||||
|
&UndoStack::tramp, this);
|
||||||
|
}
|
||||||
|
~UndoStack() override { oakundo_unsubscribe(sub_); oakundo_undostack_free(h_); }
|
||||||
|
void push(UndoCommand *c, const QString &n) {
|
||||||
|
oakundo_undostack_push(h_, c->handle(), n.toUtf8().constData());
|
||||||
|
}
|
||||||
|
bool canUndo() const { return oakundo_undostack_can_undo(h_) != 0; }
|
||||||
|
signals:
|
||||||
|
void index_changed(int);
|
||||||
|
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_;
|
||||||
|
int64_t sub_;
|
||||||
|
};
|
||||||
|
```
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# 02 · 模块清单、依赖矩阵与拆分顺序
|
||||||
|
|
||||||
|
> 本文基于 2026-07-26 对 engine/ 全量 490 个源文件的 include 扫描
|
||||||
|
> (方法:按 include 目标首段归类计数)。每个模块手册(M1-M9)里的
|
||||||
|
> 切割点清单都出自这张表。
|
||||||
|
|
||||||
|
## 1. 依赖矩阵(include 次数,空格=0)
|
||||||
|
|
||||||
|
| from\to | audio | codec | common | config | node | plugin | render | task | timeline | undo | core |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| audio | | 1 | 5 | 2 | | | 2 | | | | 5 |
|
||||||
|
| codec | | | 12 | 1 | 3 | | 11 | 5 | | | 4 |
|
||||||
|
| common | | 1 | | 1 | 3 | 1 | 7 | | | 1 | 2 |
|
||||||
|
| config | | 1 | 4 | | 1 | | | | 1 | | |
|
||||||
|
| node | 4 | 8 | 31 | 10 | | 3 | 47 | | 5 | 4 | 4 |
|
||||||
|
| pluginSupport | | | 6 | | 6 | | 6 | | | 2 | 2 |
|
||||||
|
| render | 3 | 9 | 24 | 6 | 38 | 5 | | 2 | | 1 | 8 |
|
||||||
|
| task | | 4 | 4 | 2 | 38 | | 4 | | 1 | | |
|
||||||
|
| timeline | | | 4 | 2 | 32 | | | | | 1 | 3 |
|
||||||
|
| undo | | | 2 | | 1 | | | | | | |
|
||||||
|
| src(capi) | 9 | 13 | 1 | 3 | 110 | 3 | 48 | 12 | 16 | 14 | 3 |
|
||||||
|
|
||||||
|
已知分层违规 1 处:`render/` 引用了 `src/capi/displayinternal.h`
|
||||||
|
(R7-A 重做 display.h 时一并消除)。
|
||||||
|
|
||||||
|
## 2. 模块定义与拆分顺序
|
||||||
|
|
||||||
|
顺序原则:叶子先、根后;每步只引入"已拆模块的 C ABI",不引入
|
||||||
|
"未拆模块的 C++ 头"。
|
||||||
|
|
||||||
|
| 序 | 模块 | 内容(engine/ 下目录) | 主要切割点 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| M0 | oakcore | `core/`(已完成,不动) | — |
|
||||||
|
| 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) |
|
||||||
|
| M3 | oaknode | `node/`(图、工厂、keyframe、nodeundo、traverser) | node→render 47、node→codec 8、node→timeline 5、node→audio 4、node→undo 4(M3 §3,最大的活) |
|
||||||
|
| 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 |
|
||||||
|
| 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(违规) |
|
||||||
|
| M8 | oaktask | `task/`(Task/TaskManager/项目 load/save/import/OTIO) | task→node 38、task→codec 4、task→render 4 |
|
||||||
|
| 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 裁决) |
|
||||||
|
|
||||||
|
**M3.5(伴随 M3 的类型下沉)**:`render/videoparams.h`、
|
||||||
|
`render/subtitleparams.h`、`render/colortransform.h` 是纯数据类型,
|
||||||
|
codec/node 都重度引用——下沉到 **oakcommon**(或独立 oakmedia 目录,
|
||||||
|
执行时二选一,默认并 oakcommon),切断 codec→render 的大头。
|
||||||
|
|
||||||
|
## 3. 依赖环处理总表
|
||||||
|
|
||||||
|
| 环 | 数据 | 处理 |
|
||||||
|
|---|---|---|
|
||||||
|
| node ↔ render | 47/38 | M3 时 node 侧 47 次引用经 oakrender **尚未存在**——因此 M3 拆分时 node→render 的引用先经"前向 C ABI"处理:把 node 用到的 render 类(ColorProcessor/RenderManager/footagejob/pluginjob/videoparams)的 C API 定义在 **oaknode 手册里但由 M7 实现**?**否**——正确顺序见 §4 说明 |
|
||||||
|
| node ↔ timeline | 5/32 | node→timeline 5 次(timelinecommon×2、marker/workarea/timelineundogeneral 各1):枚举/常量头下沉 oakcommon,其余经 M4 反向 C ABI |
|
||||||
|
| node ↔ codec | 8/3 | node→codec 8(decoder/frame/encoder/proxymanager):M5 反向 C ABI |
|
||||||
|
| node ↔ audio | 4/4 | M6 反向 C ABI |
|
||||||
|
| render ↔ codec | 9/11 | M3.5 类型下沉后剩 ~3(renderer.h/framemanager.h),M5 时处理 |
|
||||||
|
| codec ↔ task | 5/4 | codec→task 5(taskmanager/conform/proxy 的编排引用):重排归属(proxy/conform 的 task 依赖上移 oaktask),M5/M8 处理 |
|
||||||
|
| common ↔ 各 | 12 | M1 §3 逐条 |
|
||||||
|
|
||||||
|
## 4. 关键顺序裁决:node ↔ render 怎么破
|
||||||
|
|
||||||
|
node→render 的 47 次引用(ColorProcessor 8、videoparams 5、
|
||||||
|
footagejob 4、rendermanager 3、pluginjob 3 等)在 M3 时 oakrender
|
||||||
|
还不存在。两条路:
|
||||||
|
|
||||||
|
- **A(选定)**:M3 阶段不追求 oaknode 立即独立链接,先把 oaknode
|
||||||
|
的**公共 C ABI 头**(node/project/viewer/track/block/footage 等
|
||||||
|
跨界类的 init/free/func)定义并实现出来;node→render 的引用在
|
||||||
|
**M7(oakrender 拆分)时**统一改成经 oakrender C ABI。
|
||||||
|
即:M3 只要求"oaknode 有自己的 include/ + C API + 测试",链接
|
||||||
|
验证推迟到 M7 闭环。
|
||||||
|
- B(否决):先把 render 里被 node 引用的类全搬到下层——伤筋动骨,
|
||||||
|
违反"只拆不写"。
|
||||||
|
|
||||||
|
M3 的完成判据因此放宽为:oaknode C API 实现 + 测试绿 + oaknode 内
|
||||||
|
不再新增对 render 的引用;**链接级独立**在 M7 复核。
|
||||||
|
|
||||||
|
## 5. 每模块通用落地步骤(M 手册都按此节奏)
|
||||||
|
|
||||||
|
1. 建目录:`oak<mod>/include/oak<mod>/`、`oak<mod>/src/`(先软链接/
|
||||||
|
移动源文件,CMake 独立目标)。
|
||||||
|
2. 按手册 C API 表写 `include/oak<mod>/*.h` + `src/capi_*.cpp` 实现
|
||||||
|
(01 §1)。
|
||||||
|
3. 消费侧逐个换:include 换适配头(01 §2),反向切割点逐条处理。
|
||||||
|
4. 按 03 写测试(该模块每个 C API 至少 1 个 TEST)。
|
||||||
|
5. 全量构建 + 全量 ctest + nm 审计(00 §判据 2)+ 提交。
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# 03 · 测试规范(每模块每 C API 必有 Google Test)
|
||||||
|
|
||||||
|
> 适用于 M1-M9 全部模块。拆分阶段的测试回答一个问题:**经过双层
|
||||||
|
> 适配器之后,行为和直连 C++ 时一致**。本文冻结测试结构、覆盖要求
|
||||||
|
> 与 fixture 模式。
|
||||||
|
|
||||||
|
## 1. 结构
|
||||||
|
|
||||||
|
- 每个模块一个 gtest 二进制:`oak<mod>/tests/`,目标名
|
||||||
|
`oak<mod>_gtest`,链接 `oak<mod>` + `GTest::gtest` +
|
||||||
|
`GTest::gtest_main` + Qt(Core/Gui 按需),经
|
||||||
|
`gtest_discover_tests()` 注册进 ctest(项目统一约定:Google Test
|
||||||
|
编写、ctest 运行)。
|
||||||
|
- 共享 `main.cpp`:Qt 初始化照 `tests/gtest/main.cpp` 现有模式
|
||||||
|
(需要 QApplication 的用 offscreen QPA;纯逻辑的不用建)。
|
||||||
|
- 需要 engine 全局状态的(EngineCore 单例、ColorManager 配置),
|
||||||
|
fixture 的 `SetUpTestSuite` 里 `oakengine_init(OAKENGINE_INIT_HEADLESS)`,
|
||||||
|
`TearDownTestSuite` 里 shutdown——照 `engine/tests/` 现有惯例。
|
||||||
|
|
||||||
|
## 2. 覆盖要求(硬指标)
|
||||||
|
|
||||||
|
1. **每个 C API 函数至少 1 个 TEST**。模块手册的 C API 表逐行对应
|
||||||
|
测试用例;模块完成判据包含一张核对表(M 手册附录,打勾)。
|
||||||
|
2. 每个函数至少覆盖:**正常路径 1 个** + **错误路径 1 个**
|
||||||
|
(NULL self / 越界索引 / E_INVALID 参数)。
|
||||||
|
3. **init/free 配对**:每个 init 测试都用泄漏断言收尾(模块级
|
||||||
|
`oak<mod>_debug_alive_count()` 调试计数器——各模块在 capi 实现里
|
||||||
|
顺手暴露,测试用它断言"测试前后存活对象数相等")。
|
||||||
|
4. **枚举序数一致性**:C 侧 POD/枚举与 C++ 侧枚举的映射(01 §3 表)
|
||||||
|
每个映射 1 个 TEST(如 `oakundo` 的 movement mode 0-3 ⇄
|
||||||
|
`Timeline::MovementMode`)。
|
||||||
|
5. **事件/回调**:每个 `set_*_cb`/subscribe 至少 1 个 TEST:触发后
|
||||||
|
断言回调被调、payload 正确;反注册后断言不再被调。
|
||||||
|
6. **所有权**:borrowed 句柄(文档注释标了 `/* borrowed */` 的)
|
||||||
|
free 后原对象仍存活,1 个 TEST。
|
||||||
|
|
||||||
|
## 3. 双层往返测试(每个模块至少一套)
|
||||||
|
|
||||||
|
证明"消费侧适配类 ⇄ C ABI ⇄ 提供侧实现"全链路与原 C++ 行为一致:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class UndoStackRoundtripTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
// 提供侧直接 C++ 操作 + 经 C API 操作,对比可观察状态
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(UndoStackRoundtripTest, PushPopSymmetry) {
|
||||||
|
OakUndoStack *h = oakundo_undostack_init(nullptr);
|
||||||
|
// 经 C API push 两条命令
|
||||||
|
// 断言 can_undo==1、count==2、jump(0) 后 can_undo==0
|
||||||
|
// 再经消费侧适配类 olive::UndoStack 包一层做同样操作,断言一致
|
||||||
|
oakundo_undostack_free(h);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
规则:往返测试操作的是**真实实现**(非 mock);对比点选
|
||||||
|
"可观察状态"(计数、标志位、文本、事件序列),不比较内部指针。
|
||||||
|
|
||||||
|
## 4. 禁止
|
||||||
|
|
||||||
|
- 禁止 mock 提供侧实现来"证明"适配层正确(那是自欺欺人)。
|
||||||
|
- 禁止用 `assert()` 裸断言(项目规则:一律 `EXPECT_*/ASSERT_*`)。
|
||||||
|
- 禁止测试间共享可变全局状态(每个 TEST 自建对象;单例类资源在
|
||||||
|
fixture 里清理)。
|
||||||
|
- GPU/GL 相关用例:沿用 `GTEST_SKIP()` 判定模式(offscreen 不可绘
|
||||||
|
就跳过,不许靠超时区分)。
|
||||||
|
|
||||||
|
## 5. ctest 基线纪律
|
||||||
|
|
||||||
|
- 每模块拆完:`ctest -N` 用例数只增不减;新增模块测试全部绿;
|
||||||
|
既有 45 个不许回归。
|
||||||
|
- flaky 规则沿用(`oak_cli_transcode`/`oakengine_export_test`/
|
||||||
|
`olive-gtest` 单独重跑一次,连续两次失败才算回归)。
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# M1 · oakcommon 拆分手册
|
||||||
|
|
||||||
|
> 内容:`engine/common/`(41 文件通用工具集)+ `engine/config/`。
|
||||||
|
> 依赖:oakcore(2)。被依赖:几乎全员(node 31、render 24、
|
||||||
|
> codec 12、plugin 6、audio 5、task 4、timeline 4、undo 2)。
|
||||||
|
> 拆分顺序第 1 位(叶子)。
|
||||||
|
|
||||||
|
## 1. 目标形态
|
||||||
|
|
||||||
|
```
|
||||||
|
oakcommon/
|
||||||
|
include/oakcommon/ # 公共头(C ABI + 允许直引的纯头工具)
|
||||||
|
src/ # 现有 .cpp 原样迁入
|
||||||
|
tests/ # oakcommon_gtest
|
||||||
|
```
|
||||||
|
|
||||||
|
- **纯头工具**(lerp.h、define.h、decibel.h、tohex.h、digit.h、
|
||||||
|
memorypool.h、threadsafemap.h、range.h 等无 .cpp 的):作为
|
||||||
|
oakcommon 公共头直接提供给其他模块 include——拆分阶段允许
|
||||||
|
(不产生链接依赖)。RIIR 阶段这些会改写成各语言自有实现。
|
||||||
|
- **config/config.h**:单头配置存取,被全模块引用(10+)。
|
||||||
|
它是 Qt 依赖(QSettings 包装),按 §2 冻结 C ABI。
|
||||||
|
|
||||||
|
## 2. 冻结 C API(有 .cpp 实现的函数族)
|
||||||
|
|
||||||
|
命名前缀 `oakcommon_`。以下按头分组(签名机械规则见 01 §3,
|
||||||
|
此处冻结函数清单与特殊约定):
|
||||||
|
|
||||||
|
### 2.1 `oakcommon/config.h`(对应 config/config.h)
|
||||||
|
|
||||||
|
```c
|
||||||
|
void oakcommon_config_set(const char *group, const char *key,
|
||||||
|
const char *value_utf8);
|
||||||
|
int oakcommon_config_get(const char *group, const char *key,
|
||||||
|
char *buf, int buf_size); /* 两段式 */
|
||||||
|
int oakcommon_config_get_int(const char *group, const char *key,
|
||||||
|
int fallback);
|
||||||
|
double oakcommon_config_get_double(const char *group, const char *key,
|
||||||
|
double fallback);
|
||||||
|
void oakcommon_config_set_int(const char *group, const char *key, int v);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `oakcommon/xml.h`(对应 common/xmlutils.h,8 次被引)
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* XMLAttributeLoop/xml_read_next_start_element 的 C 化:
|
||||||
|
* 以迭代器句柄包装 QXmlStreamReader */
|
||||||
|
typedef struct OakCommonXmlReader OakCommonXmlReader;
|
||||||
|
OakCommonXmlReader *oakcommon_xml_reader_init(const char *utf8, int len);
|
||||||
|
void oakcommon_xml_reader_free(OakCommonXmlReader *r);
|
||||||
|
int oakcommon_xml_read_next_start_element(OakCommonXmlReader *r);
|
||||||
|
int oakcommon_xml_reader_name(OakCommonXmlReader *r, char *buf, int n);
|
||||||
|
int oakcommon_xml_reader_attr(OakCommonXmlReader *r, const char *attr,
|
||||||
|
char *buf, int n);
|
||||||
|
int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *r,
|
||||||
|
char *buf, int n);
|
||||||
|
void oakcommon_xml_reader_skip_current(OakCommonXmlReader *r);
|
||||||
|
```
|
||||||
|
|
||||||
|
写出侧 `oakcommon_xml_writer_*`(init_to_string/write_attribute/
|
||||||
|
write_text_element/free 得字符串,两段式)。
|
||||||
|
|
||||||
|
### 2.3 `oakcommon/files.h`(common/filefunctions.h,render 7 次)
|
||||||
|
|
||||||
|
```c
|
||||||
|
int oakcommon_file_exists(const char *path); /* 1/0 */
|
||||||
|
int oakcommon_file_size(const char *path); /* -1 失败 */
|
||||||
|
int oakcommon_file_read_all(const char *path, char *buf, int n); /* 两段式 */
|
||||||
|
int oakcommon_file_write_all(const char *path, const char *data, int n);
|
||||||
|
int oakcommon_dir_mkpath(const char *path);
|
||||||
|
int oakcommon_get_config_path(char *buf, int n);
|
||||||
|
int oakcommon_get_temp_path(char *buf, int n);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 `oakcommon/ocio.h`、`oakcommon/oii.h`、`oakcommon/ffmpeg.h`
|
||||||
|
|
||||||
|
(ocioutils/oiioutils/ffmpegutils,按 01 §3 机械 POD 化;
|
||||||
|
OAK/OIIO/FFmpeg 类型全部句柄化或拍平字段。)
|
||||||
|
|
||||||
|
### 2.5 `oakcommon/misc.h`
|
||||||
|
|
||||||
|
`jobtime`(`double oakcommon_jobtime_now(void)`)、`current`
|
||||||
|
(`oakcommon_current_get/set` 线程局部当前对象句柄)。
|
||||||
|
|
||||||
|
## 3. 切割点(common 的 12 次反向 include,逐条)
|
||||||
|
|
||||||
|
| 现状 | 处理 |
|
||||||
|
|---|---|
|
||||||
|
| common → render/ 7 次(播放钟/自动滚动等引 render 类型) | 涉及文件(playbackaudioclock/autoscroll 等)**上移出 oakcommon**:它们不是底层工具,归 oakrender(M7) |
|
||||||
|
| common → node/ 3 次 | 同上,归 oaknode(M3) |
|
||||||
|
| common → undo/ 1 次 | 同上,归 oakundo(M2) |
|
||||||
|
| common → codec/ 1 次 | 同上,归 oakcodec(M5) |
|
||||||
|
| common → pluginSupport/ 1 次 | 同上,归 oakplugin(M9) |
|
||||||
|
|
||||||
|
判据:切完后 `grep -rn '#include "' oakcommon/src | grep -vE
|
||||||
|
'"(oakcommon|olive/core)'` 为空(只剩 oakcore 与 Qt/系统头)。
|
||||||
|
|
||||||
|
## 4. 测试(映射 03 §2)
|
||||||
|
|
||||||
|
- config:set/get 往返、int/double fallback、两段式 buf。
|
||||||
|
- xml:reader 解析样例串、attr/text 读取、skip、writer 产出解析回读。
|
||||||
|
- files:临时目录建/写/读/尺寸/删除。
|
||||||
|
- 每函数 1 正常 + 1 错误路径;`oakcommon_debug_alive_count()` 泄漏断言。
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# 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;
|
||||||
|
OAKUNDO_API OakUndoStack *oakundo_undostack_init(void *parent_qobject);
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* 事件(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);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 边界行。
|
||||||
|
- 事件:push 后 index_changed 触发且 a=新 index;unsubscribe 后不再触发。
|
||||||
|
- 往返测试:C API 与适配类各做一遍 push-undo-redo,状态一致。
|
||||||
|
- `oakundo_debug_alive_count()`:init/free 配对无泄漏。
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# M3 · oaknode 拆分手册
|
||||||
|
|
||||||
|
> 内容:`engine/node/`(Node/NodeGroup/NodeKeyframe/NodeInput/
|
||||||
|
> NodeValue/NodeFactory/NodeTraverser/NodeGroup/Project/Folder/
|
||||||
|
> Sequence/Track/TrackList/Block/ClipBlock/Footage/Serializer/
|
||||||
|
> ColorManager/各类节点实现)。
|
||||||
|
> 被依赖:timeline 32、task 38、render 38、plugin 6、codec 3、capi 110。
|
||||||
|
> 拆分顺序第 3 位(最大的模块;**链接级独立推迟到 M7 复核**,裁决
|
||||||
|
> 见 02 §4)。
|
||||||
|
|
||||||
|
## 1. 目标形态
|
||||||
|
|
||||||
|
```
|
||||||
|
oaknode/
|
||||||
|
include/oaknode/ # node.h, project.h, track.h, block.h, footage.h,
|
||||||
|
# keyframe.h, factory.h, traverser.h, serializer.h,
|
||||||
|
# colormanager.h, types.h, export.h
|
||||||
|
src/ # 现有 node/ 全量迁入(含全部具体节点实现子目录)
|
||||||
|
tests/ # oaknode_gtest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 冻结 C API(跨界类清单)
|
||||||
|
|
||||||
|
跨界类 = 被其他模块引用的类(来源:02 依赖矩阵的热门头统计)。
|
||||||
|
每类按 01 §1 生成 `init/free/func`,此处冻结**函数族与特殊约定**
|
||||||
|
(逐函数签名照 oakengine 现有 facade 同族对齐——oakengine/node.h、
|
||||||
|
project.h、timeline.h 的对应函数就是模板,参数命名前缀换
|
||||||
|
`oaknode_`):
|
||||||
|
|
||||||
|
| 类 | 主要消费方 | 函数族(每族含 init/free) |
|
||||||
|
|---|---|---|
|
||||||
|
| Node | 全部 | label/color/enabled 存取、input 列举与存取(`oak_node_value`)、connect/disconnect、output_connections 枚举、links、context positions、id() 字符串 |
|
||||||
|
| NodeGroup | nodeparamview 链 | passthrough add/remove/列举、resolve_input |
|
||||||
|
| NodeKeyframe | curve/keyframe | time/value/type/bezier 存取(live + undoable 变体) |
|
||||||
|
| NodeFactory | factorymenu、构造点 | id_count/id_at/name_from_id/create_from_id/node_at |
|
||||||
|
| NodeTraverser | nodevaluetree 等 | traverse db 创建/行枚举/释放 |
|
||||||
|
| Project | task/render | root、name/filename/cache_path、modified、is_modified、add/remove_node |
|
||||||
|
| Folder | projectexplorer | child 增删/列举/move_children |
|
||||||
|
| Sequence | timeline/render | track_list、workarea、markers、video/audio params、playhead |
|
||||||
|
| 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 |
|
||||||
|
| Footage | task | filename、streams、proxy、duration |
|
||||||
|
| ProjectSerializer | task | save/load/copy/paste(clipboard 族,照 oakengine/serializer.h 模板) |
|
||||||
|
| ColorManager | render | config、default config、display transform |
|
||||||
|
|
||||||
|
**特殊约定**:
|
||||||
|
1. undoable 变体与 live 变体成对(`_live` 后缀或 `, void *command`
|
||||||
|
尾参),与 oakengine 现状一致。
|
||||||
|
2. 事件:Node 族事件(label/input/keyframe/context 等)经
|
||||||
|
`oaknode_subscribe(handle, event_id, fn, userdata)`——事件 ID 表
|
||||||
|
直接沿用 `oakengine/events.h` 的 70-95 段(值不变,便于
|
||||||
|
EngineEventBridge 逐步换绑)。
|
||||||
|
3. `Node *`、`Project *` 等句柄即 `OakNodeNode *`/`OakNodeProject *`,
|
||||||
|
不透明。
|
||||||
|
4. 虚函数不出模块(01 §5);具体节点类型经
|
||||||
|
`oaknode_factory_create_from_id` 构造,消费侧不碰子类。
|
||||||
|
|
||||||
|
## 3. 切割点(node 的对下引用)
|
||||||
|
|
||||||
|
| 现状(次数) | 处理 |
|
||||||
|
|---|---|
|
||||||
|
| node → render/ 47(colorprocessor 8、videoparams 5、footagejob 4、rendermanager 3、pluginjob 3 等) | videoparams/colortransform 随 M3.5 下沉 oakcommon;其余 **M7 时**改经 oakrender C ABI(02 §4 裁决 A:M3 暂不断链,禁止新增) |
|
||||||
|
| node → codec/ 8(decoder 4、frame 2、encoder 1、proxymanager 1) | M5 时改经 oakcodec C ABI(M5 手册已含 frame/decoder 家族) |
|
||||||
|
| node → timeline/ 5(timelinecommon 2、marker 1、workarea 1、timelineundogeneral 1) | timelinecommon 的枚举/常量下沉 oakcommon/types.h;marker/workarea 引用(均在 node/project/ 序列化路径)M4 时改经 oaktimeline C ABI |
|
||||||
|
| node → audio/ 4 | M6 时改经 oakaudio C ABI |
|
||||||
|
| node → undo/ 4(undocommand.h) | M2 后改 include oakundo 公共头 + 适配类 |
|
||||||
|
|
||||||
|
M3 阶段判据(放宽版):oaknode 目录就位、C API 实现、oaknode_gtest
|
||||||
|
全绿、对上述各向**无新增引用**(grep 快照对比)。M7 后复核
|
||||||
|
"oaknode 只经 C ABI 调下"。
|
||||||
|
|
||||||
|
## 4. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
|
- 重点:Node 增删连边、Project/Folder 层级、Track 属性、
|
||||||
|
keyframe live/undoable 对称、serializer 剪贴板往返、
|
||||||
|
factory 枚举与创建。
|
||||||
|
- 事件:每族至少 1 个 subscribe/trigger/unsubscribe 用例。
|
||||||
|
- 枚举序数:NodeValue::Type ⇄ oak_node_value_type 映射表(已在
|
||||||
|
nodevaluehandle.h 钉过一次,oaknode 测试再钉一次,防两侧漂移)。
|
||||||
|
- `oaknode_debug_alive_count()` 泄漏断言。
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# M4 · oaktimeline 拆分手册
|
||||||
|
|
||||||
|
> 内容:`engine/timeline/`(TimelineMarker/TimelineMarkerList/
|
||||||
|
> TimelineWorkArea/timelinecommon/timeline undo 命令族:
|
||||||
|
> timelineundogeneral/timelineundopointer/timelineundoripple/
|
||||||
|
> timelineundosplit/timelineundotrack/timelineundoworkarea)。
|
||||||
|
> 依赖:node 32(重)、common 4、config 2、undo 1、core 3。
|
||||||
|
> 拆分顺序第 4 位。
|
||||||
|
|
||||||
|
## 1. 目标形态
|
||||||
|
|
||||||
|
```
|
||||||
|
oaktimeline/
|
||||||
|
include/oaktimeline/{marker.h, workarea.h, edit.h, types.h, export.h}
|
||||||
|
src/
|
||||||
|
tests/ # oaktimeline_gtest
|
||||||
|
```
|
||||||
|
|
||||||
|
timeline 的 undo 命令类**不出模块**(01 §5):消费侧经
|
||||||
|
`edit.h` 的语义函数或 `_command` 工厂拿基类 `OakUndoCommand *`
|
||||||
|
(oakundo 句柄)组进自己的 MultiUndoCommand。
|
||||||
|
|
||||||
|
## 2. 冻结 C API
|
||||||
|
|
||||||
|
### 2.1 `oaktimeline/marker.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakTimelineMarkerList OakTimelineMarkerList;
|
||||||
|
/* list 句柄为 borrowed(属 Sequence/Clip 所有),不需要 free */
|
||||||
|
OAKTL_API OakTimelineMarkerList *oaktimeline_marker_list_of(
|
||||||
|
OakNodeNode *owner /* sequence 或 clip 句柄 */);
|
||||||
|
|
||||||
|
OAKTL_API int oaktimeline_marker_count(const OakTimelineMarkerList *l);
|
||||||
|
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);
|
||||||
|
OAKTL_API int oaktimeline_marker_add(OakTimelineMarkerList *l,
|
||||||
|
int64_t in_ts, int64_t out_ts, const char *name, int color,
|
||||||
|
void *command); /* command=NULL 时自行入栈 */
|
||||||
|
OAKTL_API int oaktimeline_marker_remove_at(OakTimelineMarkerList *l,
|
||||||
|
int i, void *command);
|
||||||
|
OAKTL_API int oaktimeline_marker_set_time(OakTimelineMarkerList *l,
|
||||||
|
int i, int64_t in_ts, int64_t out_ts, void *command);
|
||||||
|
OAKTL_API int oaktimeline_marker_set_props(OakTimelineMarkerList *l,
|
||||||
|
int i, int color, const char *name, void *command);
|
||||||
|
/* 事件:MARKER_ADDED/REMOVED/MODIFIED(id 沿用 oakengine events 段) */
|
||||||
|
OAKTL_API int64_t oaktimeline_subscribe(void *handle, int32_t event_id,
|
||||||
|
oaktl_event_fn fn, void *userdata);
|
||||||
|
OAKTL_API void oaktimeline_unsubscribe(int64_t id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `oaktimeline/workarea.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakTimelineWorkarea OakTimelineWorkarea;
|
||||||
|
OAKTL_API int oaktimeline_workarea_get(const OakTimelineWorkarea *w,
|
||||||
|
int64_t *in_ts, int64_t *out_ts, int *enabled);
|
||||||
|
OAKTL_API int oaktimeline_workarea_set_range(OakTimelineWorkarea *w,
|
||||||
|
int64_t in_ts, int64_t out_ts); /* live */
|
||||||
|
OAKTL_API int oaktimeline_workarea_set_range_undoable(
|
||||||
|
OakTimelineWorkarea *w, int64_t in_ts, int64_t out_ts,
|
||||||
|
int64_t old_in_ts, int64_t old_out_ts, void *command);
|
||||||
|
OAKTL_API int oaktimeline_workarea_set_enabled_undoable(
|
||||||
|
OakTimelineWorkarea *w, int enabled, void *command);
|
||||||
|
OAKTL_API void oaktimeline_workarea_reset(int64_t *in_ts, int64_t *out_ts);
|
||||||
|
/* load/save 经 oakcommon_xml 句柄在 oaknode 序列化路径调用 */
|
||||||
|
OAKTL_API int oaktimeline_workarea_load(OakTimelineWorkarea *w,
|
||||||
|
OakCommonXmlReader *r);
|
||||||
|
OAKTL_API int oaktimeline_workarea_save(const OakTimelineWorkarea *w,
|
||||||
|
OakCommonXmlWriter *x);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 `oaktimeline/edit.h`(timeline undo 命令族的语义入口)
|
||||||
|
|
||||||
|
照 `oakengine/timeline.h` 的 timeline 编辑原语逐一对齐(那本来就是
|
||||||
|
这一族的 facade 版,签名模板直接搬):
|
||||||
|
|
||||||
|
```c
|
||||||
|
OAKTL_API void *oaktimeline_add_track_command(OakNodeTrackList *list);
|
||||||
|
OAKTL_API void *oaktimeline_remove_track_command(OakNodeTrackList *list,
|
||||||
|
int index);
|
||||||
|
OAKTL_API void *oaktimeline_place_block_command(OakNodeTrackList *list,
|
||||||
|
int track_index, OakNodeBlock *block, int64_t in_ts);
|
||||||
|
OAKTL_API void *oaktimeline_replace_block_with_gap_command(
|
||||||
|
OakNodeTrack *track, OakNodeBlock *block, int64_t in_ts);
|
||||||
|
OAKTL_API void *oaktimeline_trim_command(OakNodeTrack *track,
|
||||||
|
OakNodeBlock *block, int64_t point_ts, int trim_in);
|
||||||
|
OAKTL_API void *oaktimeline_split_command(OakNodeBlock *const *blocks,
|
||||||
|
int count, int64_t point_ts); /* preserving links 变体加 _links */
|
||||||
|
OAKTL_API void *oaktimeline_ripple_delete_gaps_command(
|
||||||
|
OakNodeSequence *seq, const int64_t *in_ts, const int64_t *out_ts,
|
||||||
|
const int *track_types, const int *track_indexes, int range_count);
|
||||||
|
OAKTL_API void *oaktimeline_slide_command(OakNodeTrack *track,
|
||||||
|
OakNodeBlock *block, int track_delta, int64_t time_delta_ts);
|
||||||
|
OAKTL_API int64_t oaktimeline_nearest_block_ts(OakNodeTrack *track,
|
||||||
|
int64_t ts, int direction /* -1/0/1 */);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 切割点(timeline → node 32 次)
|
||||||
|
|
||||||
|
全部经 oaknode C ABI + 适配类(01 §2):
|
||||||
|
|
||||||
|
- `node/output/track/track.h`(5)、`tracklist.h`(3):timeline 内部
|
||||||
|
对 Track 的操作换 `OakNodeTrack*` 适配。
|
||||||
|
- `node/block/gap/gap.h`(4)、`transition.h`(4):Gap/Transition 的
|
||||||
|
构造换 `oaknode_factory_create_from_id`。
|
||||||
|
- `node/project.h`(3):Project 适配类。
|
||||||
|
|
||||||
|
## 4. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
|
- marker:增删改查、undo 往返(push 后 undo 恢复)、事件三件套。
|
||||||
|
- workarea:set/get、undoable 旧值恢复、reset 哨兵、xml 往返。
|
||||||
|
- edit:每个 `_command` 工厂 1 个"构造→入栈→undo 还原"用例
|
||||||
|
(track 增删、place/replace/trim/split/ripple/slide)。
|
||||||
|
- `oaktimeline_debug_alive_count()` 泄漏断言。
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# M5 · oakcodec 拆分手册
|
||||||
|
|
||||||
|
> 内容:`engine/codec/`(decoder/encoder/frame/waveform/proxymanager/
|
||||||
|
> conformmanager)。
|
||||||
|
> 依赖:common 12、render 11(M3.5 下沉后剩 ~3)、task 5、node 3、
|
||||||
|
> core 4。
|
||||||
|
> 拆分顺序第 5 位。
|
||||||
|
|
||||||
|
## 1. 目标形态
|
||||||
|
|
||||||
|
```
|
||||||
|
oakcodec/
|
||||||
|
include/oakcodec/{frame.h, decoder.h, encoder.h, waveform.h, proxy.h,
|
||||||
|
types.h, export.h}
|
||||||
|
src/
|
||||||
|
tests/ # oakcodec_gtest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 冻结 C API
|
||||||
|
|
||||||
|
### 2.1 `oakcodec/frame.h`(render 7 次引 codec/frame.h)
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakCodecFrame OakCodecFrame;
|
||||||
|
OAKCD_API OakCodecFrame *oakcodec_frame_init(void);
|
||||||
|
OAKCD_API OakCodecFrame *oakcodec_frame_init_copy(const OakCodecFrame *o);
|
||||||
|
OAKCD_API void oakcodec_frame_free(OakCodecFrame *f);
|
||||||
|
OAKCD_API OakCodecFrame *oakcodec_frame_retain(OakCodecFrame *f); /* +1 */
|
||||||
|
OAKCD_API int oakcodec_frame_set_params(OakCodecFrame *f,
|
||||||
|
const oak_video_params *p);
|
||||||
|
OAKCD_API int oakcodec_frame_get_params(const OakCodecFrame *f,
|
||||||
|
oak_video_params *out);
|
||||||
|
OAKCD_API int oakcodec_frame_allocate(OakCodecFrame *f);
|
||||||
|
OAKCD_API void *oakcodec_frame_data(OakCodecFrame *f);
|
||||||
|
OAKCD_API int oakcodec_frame_linesize(const OakCodecFrame *f);
|
||||||
|
OAKCD_API int64_t oakcodec_frame_timestamp(const OakCodecFrame *f);
|
||||||
|
OAKCD_API void oakcodec_frame_set_timestamp(OakCodecFrame *f, int64_t ts);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `oakcodec/decoder.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakCodecDecoder OakCodecDecoder;
|
||||||
|
OAKCD_API OakCodecDecoder *oakcodec_decoder_init(const char *filename);
|
||||||
|
OAKCD_API void oakcodec_decoder_free(OakCodecDecoder *d);
|
||||||
|
OAKCD_API int oakcodec_decoder_stream_count(const OakCodecDecoder *d,
|
||||||
|
int media_type /* 0=video 1=audio */);
|
||||||
|
OAKCD_API int oakcodec_decoder_get_video_stream(const OakCodecDecoder *d,
|
||||||
|
int index, oak_video_params *out, int64_t *duration_ts);
|
||||||
|
OAKCD_API int oakcodec_decoder_get_audio_stream(const OakCodecDecoder *d,
|
||||||
|
int index, int *sample_rate, uint64_t *layout, int *format);
|
||||||
|
OAKCD_API OakCodecFrame *oakcodec_decoder_decode_video(
|
||||||
|
OakCodecDecoder *d, int stream, int64_t ts); /* NULL=EOF/错误 */
|
||||||
|
OAKCD_API int oakcodec_decoder_decode_audio(OakCodecDecoder *d,
|
||||||
|
int stream, int64_t ts, float *buf, int frame_count);
|
||||||
|
OAKCD_API int oakcodec_decoder_last_error(OakCodecDecoder *d,
|
||||||
|
char *buf, int n);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 `oakcodec/encoder.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakCodecEncoder OakCodecEncoder;
|
||||||
|
OAKCD_API OakCodecEncoder *oakcodec_encoder_init(
|
||||||
|
const oak_encoding_params *params); /* POD 照 oakengine/encoding.h */
|
||||||
|
OAKCD_API void oakcodec_encoder_free(OakCodecEncoder *e);
|
||||||
|
OAKCD_API int oakcodec_encoder_write_video(OakCodecEncoder *e,
|
||||||
|
const OakCodecFrame *f);
|
||||||
|
OAKCD_API int oakcodec_encoder_write_audio(OakCodecEncoder *e,
|
||||||
|
const float *samples, int frame_count);
|
||||||
|
OAKCD_API int oakcodec_encoder_flush(OakCodecEncoder *e);
|
||||||
|
OAKCD_API int oakcodec_encoder_last_error(OakCodecEncoder *e,
|
||||||
|
char *buf, int n);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 `oakcodec/waveform.h`、`oakcodec/proxy.h`
|
||||||
|
|
||||||
|
waveform:`oakcodec_waveform_extract(filename, stream, out_min_max_pairs,
|
||||||
|
progress_cb)`(AudioWaveformCache 的磁盘格式不变)。
|
||||||
|
proxy:照 `oakengine/proxy.h` 模板(get_or_start/state/cancel)。
|
||||||
|
|
||||||
|
## 3. 切割点
|
||||||
|
|
||||||
|
| 现状 | 处理 |
|
||||||
|
|---|---|
|
||||||
|
| codec → render/ 11 | videoparams/subtitleparams/colortransform 已随 M3.5 下沉 oakcommon;剩 renderer.h(2)、framemanager.h(1) → framemanager 是 codec 内部缓存编排,**随 codec 一起走**(从 render/ 移入 oakcodec/src,纯文件移动,它本来就主要服务 codec) |
|
||||||
|
| codec → task/ 5(taskmanager/conform/proxy 编排) | proxy/conform 对 TaskManager 的引用改为 01 §4 回调注册(`oakcodec_set_task_submit_cb`),Task 对象创建上移 oaktask(M8),oakcodec 只调回调 |
|
||||||
|
| codec → node/ 3 | 经 oaknode C ABI(M3 已就位) |
|
||||||
|
|
||||||
|
## 4. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
|
- frame:params 往返、allocate/data/linesize、retain/free 引用计数。
|
||||||
|
- decoder:`tests/demo.mp4` 开流、stream 枚举、decode 首帧非空、
|
||||||
|
错误路径(不存在文件 → E_NOT_FOUND + last_error 非空)。
|
||||||
|
- encoder:写小 mp4(照 oak_cli_transcode 的参数),flush 后文件
|
||||||
|
可再被 decoder 打开(往返)。
|
||||||
|
- waveform:demo.mp4 提取返回非空且长度与时长一致(容差断言)。
|
||||||
|
- proxy:mock 免(03 §4)——用 demo.mp4 低分辨率参数真跑一次或
|
||||||
|
按环境跳过。
|
||||||
|
- `oakcodec_debug_alive_count()` 泄漏断言。
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# M6 · oakaudio 拆分手册
|
||||||
|
|
||||||
|
> 内容:`engine/audio/`(AudioManager、AudioProcessor、输出设备
|
||||||
|
> 抽象、audiovisualwaveform)。
|
||||||
|
> 依赖:common 5、core 5、config 2、render 2、codec 1。拆分顺序第 6 位
|
||||||
|
> (小模块,穿插在 render 前做掉)。
|
||||||
|
|
||||||
|
## 1. 目标形态
|
||||||
|
|
||||||
|
```
|
||||||
|
oakaudio/
|
||||||
|
include/oakaudio/{manager.h, processor.h, types.h, export.h}
|
||||||
|
src/
|
||||||
|
tests/ # oakaudio_gtest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 冻结 C API
|
||||||
|
|
||||||
|
### 2.1 `oakaudio/processor.h`(实时重采样/格式转换,R6 已建过
|
||||||
|
`oakengine_audio_processor_*`,本表即其 oakaudio 版,语义不变)
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakAudioProcessor OakAudioProcessor;
|
||||||
|
OAKAU_API OakAudioProcessor *oakaudio_processor_init(void);
|
||||||
|
OAKAU_API void oakaudio_processor_free(OakAudioProcessor *p);
|
||||||
|
OAKAU_API int oakaudio_processor_open(OakAudioProcessor *p,
|
||||||
|
int in_rate, uint64_t in_layout, int in_format,
|
||||||
|
int out_rate, uint64_t out_layout, int out_format, double speed);
|
||||||
|
OAKAU_API void oakaudio_processor_close(OakAudioProcessor *p);
|
||||||
|
OAKAU_API int oakaudio_processor_convert(OakAudioProcessor *p,
|
||||||
|
float **data, int frame_count);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `oakaudio/manager.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
OAKAU_API int oakaudio_manager_init(int prefer_backend /* -1=auto */);
|
||||||
|
OAKAU_API void oakaudio_manager_shutdown(void);
|
||||||
|
OAKAU_API int oakaudio_manager_set_output_params(int rate,
|
||||||
|
uint64_t layout, int format);
|
||||||
|
OAKAU_API int oakaudio_manager_get_output_params(int *rate,
|
||||||
|
uint64_t *layout, int *format);
|
||||||
|
/* 推流:播放路径逐块喂 samples(float 交错) */
|
||||||
|
OAKAU_API int oakaudio_manager_push(const float *samples,
|
||||||
|
int frame_count, double speed);
|
||||||
|
OAKAU_API int oakaudio_manager_is_playing(void);
|
||||||
|
OAKAU_API void oakaudio_manager_stop(void);
|
||||||
|
/* 输出参数变化事件 */
|
||||||
|
OAKAU_API int64_t oakaudio_manager_subscribe_params_changed(
|
||||||
|
oakaudio_event_fn fn, void *userdata);
|
||||||
|
OAKAU_API void oakaudio_unsubscribe(int64_t id);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 切割点
|
||||||
|
|
||||||
|
| 现状 | 处理 |
|
||||||
|
|---|---|
|
||||||
|
| audio → render/ 2 | audiovisualwaveform 对 render 类型的引用:POD 化(波形 min/max 对数组),残留类型随 M3.5 下沉 |
|
||||||
|
| audio → codec/ 1 | 经 oakcodec C ABI(M5 已就位) |
|
||||||
|
|
||||||
|
## 4. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
|
- processor:open/convert/close 全链(44.1k stereo → 48k stereo,
|
||||||
|
帧数换算正确、无爆音断言用能量差阈值)、速度 1.5x。
|
||||||
|
- manager:无音频设备环境用 null backend 初始化(现有后端探测
|
||||||
|
模式),params set/get 往返、事件触发。
|
||||||
|
- `oakaudio_debug_alive_count()` 泄漏断言。
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
# M7 · oakrender 拆分手册
|
||||||
|
|
||||||
|
> 内容:`engine/render/`(Renderer/OpenGLRenderer/DynamicRenderer/
|
||||||
|
> PlaybackCache/FrameHashCache/AudioWaveformCache/ColorProcessor/
|
||||||
|
> ColorManager/RenderManager/PreviewAutoCacher/DiskManager/job 族/
|
||||||
|
> shaders 加载)。
|
||||||
|
> 依赖:node 38、common 24、codec 9、core 8、config 6、plugin 5、
|
||||||
|
> audio 3、task 2、undo 1、**src 1(分层违规)**。
|
||||||
|
> 拆分顺序第 7 位。M7 完成后 oaknode 的链接级独立复核(02 §4)。
|
||||||
|
|
||||||
|
## 1. 目标形态
|
||||||
|
|
||||||
|
```
|
||||||
|
oakrender/
|
||||||
|
include/oakrender/{renderer.h, cache.h, color.h, manager.h, display.h,
|
||||||
|
types.h, export.h}
|
||||||
|
src/
|
||||||
|
tests/ # oakrender_gtest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 冻结 C API
|
||||||
|
|
||||||
|
### 2.1 `oakrender/renderer.h`(渲染器/纹理/blit)
|
||||||
|
|
||||||
|
签名照 R7-A(`docs/zh/r7-pure-abi-plan.md` §A.2)的 display.h 重写版
|
||||||
|
**原样采用**——R7-A 先做的话,M7 直接把它从 facade 层搬进
|
||||||
|
oakrender 并改前缀 `oakrender_display_*`;本表不重复,以 R7-A 为准。
|
||||||
|
补充后端管理:
|
||||||
|
|
||||||
|
```c
|
||||||
|
OAKRD_API int oakrender_backend_count(void);
|
||||||
|
OAKRD_API int oakrender_backend_id_at(int i, char *buf, int n);
|
||||||
|
OAKRD_API int oakrender_set_backend(const char *backend_id);
|
||||||
|
OAKRD_API int oakrender_current_backend(char *buf, int n);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `oakrender/cache.h`(PlaybackCache/FrameHashCache/waveform 缓存)
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakRenderCache OakRenderCache;
|
||||||
|
OAKRD_API void oakrender_cache_invalidate(OakRenderCache *c,
|
||||||
|
int64_t in_ts, int64_t out_ts);
|
||||||
|
OAKRD_API void oakrender_cache_validate(OakRenderCache *c,
|
||||||
|
int64_t in_ts, int64_t out_ts);
|
||||||
|
OAKRD_API int oakrender_cache_has_validated_ranges(
|
||||||
|
const OakRenderCache *c);
|
||||||
|
OAKRD_API int oakrender_cache_indicator_height(void); /* 常量查询 */
|
||||||
|
/* 帧哈希缓存 */
|
||||||
|
OAKRD_API int oakrender_frame_cache_load(OakRenderCache *c,
|
||||||
|
const char *path, const char *uuid, int64_t ts,
|
||||||
|
OakCodecFrame **out_frame);
|
||||||
|
OAKRD_API void oakrender_frame_cache_save(OakRenderCache *c,
|
||||||
|
const char *path, const char *uuid, const OakCodecFrame *f);
|
||||||
|
/* 缓存事件(playback invalidated/validated、frame invalidated) */
|
||||||
|
OAKRD_API int64_t oakrender_cache_subscribe(OakRenderCache *c,
|
||||||
|
int32_t event_id, oakrender_event_fn fn, void *userdata);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 `oakrender/color.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakColorProcessor OakColorProcessor;
|
||||||
|
OAKRD_API OakColorProcessor *oakrender_color_processor_create(
|
||||||
|
const char *src_space, const char *dst_transform, int direction);
|
||||||
|
OAKRD_API void oakrender_color_processor_free(OakColorProcessor *p);
|
||||||
|
OAKRD_API int oakrender_color_processor_convert(OakColorProcessor *p,
|
||||||
|
double ir, double ig, double ib, double ia,
|
||||||
|
double *or_, double *og, double *ob, double *oa);
|
||||||
|
/* ColorManager */
|
||||||
|
OAKRD_API int oakrender_color_manager_set_up_default_config(void);
|
||||||
|
OAKRD_API int oakrender_color_manager_get_config(char *buf, int n);
|
||||||
|
OAKRD_API int oakrender_color_manager_display_transform(
|
||||||
|
const char *display, const char *view, char *buf, int n);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 `oakrender/manager.h`(RenderManager/PreviewAutoCacher/
|
||||||
|
DiskManager/job 提交)
|
||||||
|
|
||||||
|
```c
|
||||||
|
OAKRD_API int oakrender_manager_init(void);
|
||||||
|
OAKRD_API void oakrender_manager_shutdown(void);
|
||||||
|
/* 异步帧请求:完成经回调送 OakCodecFrame(跨线程,retain 规则同 A.3) */
|
||||||
|
typedef void (*oakrender_frame_ready_fn)(OakCodecFrame *frame,
|
||||||
|
int64_t ts, void *userdata);
|
||||||
|
OAKRD_API int64_t oakrender_request_frame(OakNodeNode *viewer,
|
||||||
|
int64_t ts, oakrender_frame_ready_fn cb, void *userdata);
|
||||||
|
OAKRD_API void oakrender_cancel_request(int64_t request_id);
|
||||||
|
OAKRD_API int oakrender_set_cacher_multicam(OakNodeNode *multicam_or_NULL);
|
||||||
|
OAKRD_API int oakrender_set_display_color_processor(
|
||||||
|
OakColorProcessor *p_or_NULL);
|
||||||
|
OAKRD_API int oakrender_disk_cache_path(char *buf, int n);
|
||||||
|
OAKRD_API int64_t oakrender_disk_cache_size(void);
|
||||||
|
OAKRD_API int oakrender_disk_cache_clear(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 切割点
|
||||||
|
|
||||||
|
| 现状 | 处理 |
|
||||||
|
|---|---|
|
||||||
|
| render → node/ 38(project 7、viewer 5、value 4、footage 4、traverser 3 等) | 全部经 oaknode C ABI + 适配类(M3 已就位)——**本手册的工作量主体** |
|
||||||
|
| render → codec/ 9(frame 7、decoder 1、conformmanager 1) | 经 oakcodec C ABI(M5) |
|
||||||
|
| render → task/ 2 | 回调注册(01 §4),task 句柄不传 |
|
||||||
|
| render → undo/ 1(undostack.h) | oakundo 适配类(M2) |
|
||||||
|
| render → audio/ 3 | 经 oakaudio C ABI(M6) |
|
||||||
|
| render → src/capi/displayinternal.h 1 次(违规) | R7-A 时随 display.h 重写消除;若 R7-A 未做,M7 先把该内部头的内容并入 oakrender |
|
||||||
|
| render → plugin/ 5 | pluginjob 等:M9 时经 oakplugin C ABI(M7 暂不断链,禁止新增) |
|
||||||
|
|
||||||
|
## 4. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
|
- cache:invalidate/validate 状态机、帧缓存存取往返、事件触发。
|
||||||
|
- color:默认 config 建置、processor convert 已知值(sRGB→Linear
|
||||||
|
抽样点数值断言,容差 1e-3)。
|
||||||
|
- manager:request_frame 对 demo.mp4 + 最小 sequence 出帧非空
|
||||||
|
(offscreen 可跑的部分);cancel 路径。
|
||||||
|
- GL 相关:沿用 GTEST_SKIP 模式,不强求离屏可绘。
|
||||||
|
- `oakrender_debug_alive_count()` 泄漏断言。
|
||||||
|
|
||||||
|
## 5. 收尾复核(02 §4 裁决 A 的兑现)
|
||||||
|
|
||||||
|
M7 闭环时执行:
|
||||||
|
```
|
||||||
|
grep -rn '#include "render/' oaknode/src | wc -l # 必须 0
|
||||||
|
nm -D oaknode 构建产物 | grep -c " U _ZN5olive.*render" # 必须 0
|
||||||
|
```
|
||||||
|
node→render 的 47 次引用此时应全部经 oakrender C ABI(M3 遗留的
|
||||||
|
"暂不断链"在此结清)。
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# M8 · oaktask 拆分手册
|
||||||
|
|
||||||
|
> 内容:`engine/task/`(Task 基类、TaskManager、project/
|
||||||
|
> load/save/import/loadotio/saveotio、cache 任务)。
|
||||||
|
> 依赖:node 38、codec 4、render 4、common 4、config 2、timeline 1、
|
||||||
|
> coreengine 2。
|
||||||
|
> 拆分顺序第 8 位。
|
||||||
|
|
||||||
|
## 1. 目标形态
|
||||||
|
|
||||||
|
```
|
||||||
|
oaktask/
|
||||||
|
include/oaktask/{task.h, manager.h, project.h, types.h, export.h}
|
||||||
|
src/
|
||||||
|
tests/ # oaktask_gtest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 冻结 C API
|
||||||
|
|
||||||
|
### 2.1 `oaktask/task.h`(单任务句柄,照 oakengine/task.h 模板对齐)
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakTaskTask OakTaskTask;
|
||||||
|
OAKTK_API void oaktask_task_free(OakTaskTask *t);
|
||||||
|
OAKTK_API int oaktask_task_start(OakTaskTask *t); /* 异步 */
|
||||||
|
OAKTK_API int oaktask_task_start_sync(OakTaskTask *t);
|
||||||
|
OAKTK_API int oaktask_task_cancel(OakTaskTask *t);
|
||||||
|
OAKTK_API int oaktask_task_is_finished(const OakTaskTask *t);
|
||||||
|
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_title(OakTaskTask *t, char *buf, int n);
|
||||||
|
OAKTK_API int oaktask_task_error(OakTaskTask *t, char *buf, int n);
|
||||||
|
/* 事件:STARTED/PROGRESS/FINISHED(id 沿用 oakengine events 段) */
|
||||||
|
OAKTK_API int64_t oaktask_task_subscribe(OakTaskTask *t, int32_t event_id,
|
||||||
|
oaktask_event_fn fn, void *userdata);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `oaktask/project.h`(任务工厂 + 结果访问器)
|
||||||
|
|
||||||
|
```c
|
||||||
|
OAKTK_API OakTaskTask *oaktask_create_project_load(const char *filename);
|
||||||
|
OAKTK_API OakTaskTask *oaktask_create_project_save(OakNodeProject *p,
|
||||||
|
const char *filename_or_NULL, int use_compression,
|
||||||
|
const void *layout_or_NULL);
|
||||||
|
OAKTK_API OakTaskTask *oaktask_create_project_import(OakNodeNode *folder,
|
||||||
|
const char *const *urls, int url_count);
|
||||||
|
OAKTK_API OakTaskTask *oaktask_create_project_load_otio(
|
||||||
|
const char *filename, const int *sequence_indexes, int count);
|
||||||
|
OAKTK_API OakTaskTask *oaktask_create_project_save_otio(
|
||||||
|
OakNodeProject *p, const char *filename, const int *sequence_indexes,
|
||||||
|
int count);
|
||||||
|
/* import 结果(task 成功后读,borrowed) */
|
||||||
|
OAKTK_API void *oaktask_import_take_command(OakTaskTask *t); /* 所有权转移 */
|
||||||
|
OAKTK_API int oaktask_import_footage_count(OakTaskTask *t);
|
||||||
|
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_at(OakTaskTask *t, int i,
|
||||||
|
char *buf, int n);
|
||||||
|
/* load 结果 */
|
||||||
|
OAKTK_API OakNodeProject *oaktask_load_take_project(OakTaskTask *t);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 `oaktask/manager.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
OAKTK_API int oaktask_manager_count(void);
|
||||||
|
OAKTK_API OakTaskTask *oaktask_manager_at(int i); /* borrowed */
|
||||||
|
OAKTK_API void oaktask_manager_delete_finished(void);
|
||||||
|
/* 事件:TASK_ADDED/REMOVED/FAILED/LIST_CHANGED */
|
||||||
|
OAKTK_API int64_t oaktask_manager_subscribe(int32_t event_id,
|
||||||
|
oaktask_event_fn fn, void *userdata);
|
||||||
|
OAKTK_API void oaktask_unsubscribe(int64_t id);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 切割点
|
||||||
|
|
||||||
|
| 现状 | 处理 |
|
||||||
|
|---|---|
|
||||||
|
| task → node/ 38(footage 6、project 5、sequence 3、serializer/layout 3、colormanager 2 等) | 全部经 oaknode C ABI + 适配类(M3 已就位)——本手册工作量主体 |
|
||||||
|
| task → codec/ 4 | 经 oakcodec C ABI(M5) |
|
||||||
|
| task → render/ 4 | 经 oakrender C ABI(M7) |
|
||||||
|
| task → timeline/ 1 | 经 oaktimeline C ABI(M4) |
|
||||||
|
| task → coreengine.h 2 | coreengine 的 Task 注册点改为 oaktask_manager 自管(manager 原本就是单例,注册调用内聚进 oaktask) |
|
||||||
|
|
||||||
|
## 4. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
|
- 每个工厂 1 个用例:demo.mp4 import(footage_count>0、command 可
|
||||||
|
入栈)、save/load 往返(临时目录 .ove,load 后 project 非空、
|
||||||
|
root 非空)。
|
||||||
|
- start_sync 成功/失败路径(不存在文件 → failed + error 非空)。
|
||||||
|
- 事件:STARTED→PROGRESS→FINISHED 序列(导入任务断言至少一次
|
||||||
|
PROGRESS 且 FINISHED.succeeded==1)。
|
||||||
|
- manager:添加/删除/list_changed 事件。
|
||||||
|
- `oaktask_debug_alive_count()` 泄漏断言。
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# M9 · oakplugin 拆分手册 + facade 装配层裁决
|
||||||
|
|
||||||
|
> 内容:`engine/pluginSupport/`(OpenFX host:OliveHost、
|
||||||
|
> OlivePluginInstance、PluginNode、PluginProgressReporter)。
|
||||||
|
> 依赖:node 6、render 6、common 6、core 2、undo 2、coreengine 2。
|
||||||
|
> 拆分顺序第 9 位(最后拆的实体模块)。文末 §4 是 liboakengine
|
||||||
|
> 装配层的最终裁决。
|
||||||
|
|
||||||
|
## 1. 目标形态
|
||||||
|
|
||||||
|
```
|
||||||
|
oakplugin/
|
||||||
|
include/oakplugin/{host.h, instance.h, progress.h, types.h, export.h}
|
||||||
|
src/
|
||||||
|
tests/ # oakplugin_gtest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 冻结 C API
|
||||||
|
|
||||||
|
### 2.1 `oakplugin/host.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
OAKPL_API int oakplugin_host_init(void);
|
||||||
|
OAKPL_API void oakplugin_host_shutdown(void);
|
||||||
|
OAKPL_API int oakplugin_host_scan(const char *const *bundle_dirs,
|
||||||
|
int dir_count);
|
||||||
|
OAKPL_API int oakplugin_host_plugin_count(void);
|
||||||
|
OAKPL_API int oakplugin_host_plugin_id_at(int i, char *buf, int n);
|
||||||
|
OAKPL_API int oakplugin_host_plugin_label(const char *plugin_id,
|
||||||
|
char *buf, int n);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `oakplugin/instance.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakPluginInstance OakPluginInstance;
|
||||||
|
OAKPL_API OakPluginInstance *oakplugin_instance_create(
|
||||||
|
const char *plugin_id);
|
||||||
|
OAKPL_API void oakplugin_instance_free(OakPluginInstance *i);
|
||||||
|
OAKPL_API int oakplugin_instance_set_param(OakPluginInstance *i,
|
||||||
|
const char *param_id, const oak_node_value *v);
|
||||||
|
OAKPL_API int oakplugin_instance_get_param(OakPluginInstance *i,
|
||||||
|
const char *param_id, oak_node_value *out);
|
||||||
|
OAKPL_API int oakplugin_instance_render(OakPluginInstance *i,
|
||||||
|
OakCodecFrame *dst, const OakCodecFrame *src, int64_t ts);
|
||||||
|
/* 进度事件(R6 已把 cancelled 信号改成 C 回调,沿用该机制) */
|
||||||
|
OAKPL_API void oakplugin_instance_set_progress_cb(OakPluginInstance *i,
|
||||||
|
oakplugin_progress_fn fn, void *userdata);
|
||||||
|
OAKPL_API void oakplugin_instance_cancel(OakPluginInstance *i);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 `oakplugin/progress.h`
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct OakPluginProgress OakPluginProgress; /* 报告器句柄 */
|
||||||
|
OAKPL_API OakPluginProgress *oakplugin_progress_create_dialog(
|
||||||
|
const char *message, const char *title); /* UI 侧实现 */
|
||||||
|
OAKPL_API void oakplugin_progress_free(OakPluginProgress *p);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 切割点
|
||||||
|
|
||||||
|
| 现状 | 处理 |
|
||||||
|
|---|---|
|
||||||
|
| plugin → node/ 6(plugins/plugin.h 3 等) | 经 oaknode C ABI(PluginNode 是节点,留在 oaknode;oakplugin 只经 factory/host 接口交互)——**PluginNode 归属裁决:PluginNode 类随 oaknode 走**(它是节点体系成员),oakplugin 提供 host/instance |
|
||||||
|
| plugin → render/ 6(videoparams 3 等) | videoparams 已下沉;其余经 oakrender C ABI |
|
||||||
|
| plugin → undo/ 2 | oakundo 适配类 |
|
||||||
|
| plugin → coreengine.h 2 | 插件注册点内聚进 oakplugin_host_init |
|
||||||
|
|
||||||
|
## 4. facade 装配层最终裁决(liboakengine 的终态)
|
||||||
|
|
||||||
|
M1-M9 全部完成后,`engine/src/capi` + `coreengine` + `tool/` +
|
||||||
|
`ui/` 残余构成 liboakengine。裁决(选定):
|
||||||
|
|
||||||
|
**facade 直链各模块,不绕 C ABI 自调**。即:facade 的 `oakengine_*`
|
||||||
|
实现可以继续直接调用各模块的 C++ 内部(链接 oaknode/oakrender 等
|
||||||
|
的静态或共享库),不要求 facade 经各模块的公共 C ABI 兜圈。
|
||||||
|
理由:facade 与 app 的边界(oakengine_*)已经纯 C 且 nm=0,模块间
|
||||||
|
边界是给"模块互相调用"用的;facade 是装配层,自家人不绕远路。
|
||||||
|
**但**:`coreengine.h` 被 node/render/task/plugin 引用的 5+2+2+2
|
||||||
|
处必须内聚(各模块的初始化改由各自的 `oak<mod>_init` 完成,
|
||||||
|
coreengine 只做编排调用)。
|
||||||
|
|
||||||
|
终态验证:
|
||||||
|
|
||||||
|
```
|
||||||
|
nm -D --defined-only liboakengine.so | grep -c " T _Z" # 0(R7-B)
|
||||||
|
nm -D --defined-only liboaknode.so | grep -c " T _Z" # 0(每模块同查)
|
||||||
|
ldd liboakengine.so | grep oak # 只见 oak* 模块库
|
||||||
|
全量构建 0 error;全量 ctest 绿
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 测试(映射 03 §2/§3)
|
||||||
|
|
||||||
|
- host:init/scan/枚举(照现有 OliveHost 测试的 bundle fixture)。
|
||||||
|
- instance:Shadertoy 类插件(CI 里可用的)创建/参数/渲染一帧非空。
|
||||||
|
- progress:回调触发与 cancel。
|
||||||
|
- `oakplugin_debug_alive_count()` 泄漏断言。
|
||||||
Reference in New Issue
Block a user