Files
oak-editor/docs/zh/modularization-plan/05-olivenode.md
T

414 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# libolivenode.so — 节点图系统
> **依赖**`libolivecore.so`, `libolivecodec.so`
> **外部依赖**Qt::CoreQObject, QString, XML
> **包含源码**`app/node/`, `app/timeline/`, `app/undo/`, `app/config/`
> **当前状态**:整个项目的**核心枢纽**,被 `render/`, `widget/`, `panel/`, `task/` 等几乎所有上层模块依赖
> **改造难度**:⭐⭐⭐⭐⭐(最困难,耦合最深)
---
## 1. 当前状态分析
`app/node/` 是整个编辑器的**数据与计算模型核心**,采用节点图范式组织所有处理。其特点是:
1. **被几乎所有模块依赖**`render/` 遍历节点图,`widget/` 绘制节点连接,`panel/` 包装节点编辑器,`task/` 在导出时读取节点图。
2. **头文件耦合严重**`Node.h` 直接 `#include``codec/frame.h``render/` 下的多个头文件(缓存类型、作业类型等)。
3. **Qt 深度集成**`Node` 继承 `QObject`,使用信号槽、元对象系统、`QVariant`
4. **序列化内建**`ProjectSerializer` 支持将节点图保存/加载为 XML。
### 1.1 关键耦合点与解耦策略
| 耦合点 | 当前状态 | 解耦策略 |
|---|---|---|
| `Node.h` 包含 `render/rendercache.h` | `Node` 直接操作 `FrameHashCache` | 将缓存失效抽象为虚函数 `InvalidateCache()`,或注入 `NodeCacheCallbacks` 接口指针。移除 `rendercache.h` 的包含。 |
| `Node.h` 包含 `render/job/*.h` | `Node::ProcessShader()` 等虚函数使用具体 Job 类型 | 将 `ProcessShader` 等改为接受 `const void* job_data` + `JobType` 枚举,内部再 `static_cast`。或前向声明 Job 类(若已是不透明指针)。 |
| `Footage``node/project/footage/`)依赖 `Decoder` | `Footage` 需要解码器信息预览 | 保留此依赖,`libolivenode.so` 链接 `libolivecodec.so` 是合理的。 |
| `ViewerOutput` 被 UI 直接引用 | `ViewerOutput` 是节点图与 UI 的桥梁 | `ViewerOutput` 保留在 `node/` 中,C API 暴露 `OliveViewerOutput*` 句柄。 |
| `timeline/` 依赖 `node/` | `TimelineMarker` 等引用 `Node` | `timeline/` 并入 `libolivenode.so`,不单独拆分。 |
| `undo/` 依赖 `node/` | `UndoCommand` 操作 `Node` 对象 | `undo/` 并入 `libolivenode.so`。 |
---
## 2. C API 设计
### 2.1 头文件:`c_api/include/olive/node_api.h`
```c
#ifndef OLIVE_NODE_API_H
#define OLIVE_NODE_API_H
#ifdef __cplusplus
extern "C" {
#endif
#include "core_api.h"
#define OLIVE_NODE_API_VERSION 1
#ifdef OLIVE_BUILDING_NODE
# define OLIVE_NODE_API __attribute__((visibility("default")))
#else
# define OLIVE_NODE_API
#endif
/* ========== 不透明类型 ========== */
typedef struct OliveNodeGraph OliveNodeGraph;
typedef struct OliveNode OliveNode;
typedef struct OliveNodeInput OliveNodeInput;
typedef struct OliveNodeOutput OliveNodeOutput;
typedef struct OliveParam OliveParam;
typedef struct OliveKeyframe OliveKeyframe;
typedef struct OliveProject OliveProject;
typedef struct OliveSequence OliveSequence;
typedef struct OliveTrack OliveTrack;
typedef struct OliveClip OliveClip;
typedef struct OliveViewerOutput OliveViewerOutput;
/* ========== 枚举 ========== */
typedef enum {
OLIVE_NODE_TYPE_UNKNOWN = 0,
OLIVE_NODE_TYPE_INPUT,
OLIVE_NODE_TYPE_OUTPUT,
OLIVE_NODE_TYPE_FILTER,
OLIVE_NODE_TYPE_DISTORT,
OLIVE_NODE_TYPE_GENERATOR,
OLIVE_NODE_TYPE_COLOR,
OLIVE_NODE_TYPE_AUDIO,
OLIVE_NODE_TYPE_TRANSITION,
OLIVE_NODE_TYPE_PLUGIN,
OLIVE_NODE_TYPE_GROUP,
} OliveNodeType;
typedef enum {
OLIVE_PARAM_TYPE_INT = 0,
OLIVE_PARAM_TYPE_DOUBLE,
OLIVE_PARAM_TYPE_STRING,
OLIVE_PARAM_TYPE_RATIONAL,
OLIVE_PARAM_TYPE_COLOR,
OLIVE_PARAM_TYPE_BOOL,
OLIVE_PARAM_TYPE_VECTOR2,
OLIVE_PARAM_TYPE_VECTOR3,
OLIVE_PARAM_TYPE_VECTOR4,
} OliveParamType;
/* ========== API 版本 ========== */
OLIVE_NODE_API int olive_node_api_version(void);
/* ========== NodeGraph ========== */
OLIVE_NODE_API OliveNodeGraph* olive_node_graph_create(void);
OLIVE_NODE_API void olive_node_graph_destroy(OliveNodeGraph* g);
// 序列化
OLIVE_NODE_API int olive_node_graph_load_xml(OliveNodeGraph* g,
const char* xml_data,
size_t xml_len);
OLIVE_NODE_API char* olive_node_graph_save_xml(OliveNodeGraph* g,
size_t* out_len);
// 节点增删查
OLIVE_NODE_API OliveNode* olive_node_graph_add_node(OliveNodeGraph* g,
const char* node_type_id,
const char* node_id);
OLIVE_NODE_API int olive_node_graph_remove_node(OliveNodeGraph* g, OliveNode* node);
OLIVE_NODE_API OliveNode* olive_node_graph_find_node(OliveNodeGraph* g,
const char* node_id);
OLIVE_NODE_API int olive_node_graph_node_count(OliveNodeGraph* g);
OLIVE_NODE_API OliveNode* olive_node_graph_get_node(OliveNodeGraph* g, int index);
// 连接管理
OLIVE_NODE_API int olive_node_connect(OliveNode* from_node,
int output_index,
OliveNode* to_node,
int input_index);
OLIVE_NODE_API int olive_node_disconnect(OliveNode* node, int input_index);
OLIVE_NODE_API OliveNode* olive_node_get_connected_node(OliveNode* node,
int input_index);
/* ========== Node 属性 ========== */
OLIVE_NODE_API const char* olive_node_get_id(OliveNode* node);
OLIVE_NODE_API const char* olive_node_get_label(OliveNode* node);
OLIVE_NODE_API OliveNodeType olive_node_get_type(OliveNode* node);
OLIVE_NODE_API const char* olive_node_get_type_id(OliveNode* node);
OLIVE_NODE_API int olive_node_input_count(OliveNode* node);
OLIVE_NODE_API int olive_node_output_count(OliveNode* node);
/* ========== Param 操作 ========== */
OLIVE_NODE_API int olive_node_param_count(OliveNode* node);
OLIVE_NODE_API OliveParam* olive_node_get_param(OliveNode* node, int index);
OLIVE_NODE_API OliveParam* olive_node_find_param(OliveNode* node,
const char* param_name);
OLIVE_NODE_API const char* olive_param_get_name(OliveParam* param);
OLIVE_NODE_API OliveParamType olive_param_get_type(OliveParam* param);
OLIVE_NODE_API int olive_param_set_int(OliveParam* param, int64_t value);
OLIVE_NODE_API int olive_param_set_double(OliveParam* param, double value);
OLIVE_NODE_API int olive_param_set_rational(OliveParam* param, OliveRational value);
OLIVE_NODE_API int olive_param_set_color(OliveParam* param, OliveColor value);
OLIVE_NODE_API int olive_param_set_string(OliveParam* param, const char* value);
OLIVE_NODE_API int64_t olive_param_get_int(OliveParam* param);
OLIVE_NODE_API double olive_param_get_double(OliveParam* param);
OLIVE_NODE_API OliveRational olive_param_get_rational(OliveParam* param);
OLIVE_NODE_API OliveColor olive_param_get_color(OliveParam* param);
/* ========== Keyframe ========== */
OLIVE_NODE_API int olive_param_add_keyframe(OliveParam* param,
OliveRational time,
double value);
OLIVE_NODE_API int olive_param_remove_keyframe(OliveParam* param,
OliveRational time);
OLIVE_NODE_API int olive_param_keyframe_count(OliveParam* param);
/* ========== Project ========== */
OLIVE_NODE_API OliveProject* olive_project_create(const char* name);
OLIVE_NODE_API void olive_project_destroy(OliveProject* proj);
OLIVE_NODE_API int olive_project_load_file(OliveProject* proj, const char* filename);
OLIVE_NODE_API int olive_project_save_file(OliveProject* proj, const char* filename);
OLIVE_NODE_API OliveNodeGraph* olive_project_get_graph(OliveProject* proj);
/* ========== Sequence / Timeline ========== */
OLIVE_NODE_API OliveSequence* olive_sequence_create(const char* name,
OliveVideoParams vparams,
OliveAudioParams aparams);
OLIVE_NODE_API OliveViewerOutput* olive_sequence_get_viewer_output(OliveSequence* seq);
/* ========== ViewerOutput(渲染目标) ========== */
OLIVE_NODE_API const char* olive_viewer_output_get_node_id(OliveViewerOutput* viewer);
OLIVE_NODE_API OliveVideoParams olive_viewer_output_get_video_params(OliveViewerOutput* viewer);
OLIVE_NODE_API OliveAudioParams olive_viewer_output_get_audio_params(OliveViewerOutput* viewer);
/* ========== Undo ========== */
typedef struct OliveUndoStack OliveUndoStack;
OLIVE_NODE_API OliveUndoStack* olive_undo_stack_create(void);
OLIVE_NODE_API void olive_undo_stack_destroy(OliveUndoStack* stack);
OLIVE_NODE_API void olive_undo_stack_push(OliveUndoStack* stack,
const char* action_name,
void* undo_data,
void (*undo_fn)(void*),
void (*redo_fn)(void*),
void (*free_fn)(void*));
OLIVE_NODE_API int olive_undo_stack_can_undo(OliveUndoStack* stack);
OLIVE_NODE_API int olive_undo_stack_can_redo(OliveUndoStack* stack);
OLIVE_NODE_API void olive_undo_stack_undo(OliveUndoStack* stack);
OLIVE_NODE_API void olive_undo_stack_redo(OliveUndoStack* stack);
OLIVE_NODE_API void olive_undo_stack_clear(OliveUndoStack* stack);
#ifdef __cplusplus
}
#endif
#endif // OLIVE_NODE_API_H
```
### 2.2 关键解耦实现:NodeCacheCallbacks
```cpp
// app/node/node.h(改造后,移除 render/ 头文件包含)
// 前向声明
class NodeCacheCallbacks;
class Node : public QObject {
// ...
void SetCacheCallbacks(NodeCacheCallbacks* callbacks);
protected:
virtual void InvalidateCacheInternal(const TimeRange& range);
private:
NodeCacheCallbacks* cache_callbacks_ = nullptr;
};
// app/node/nodecachecallbacks.h(新增)
class NodeCacheCallbacks {
public:
virtual ~NodeCacheCallbacks() = default;
virtual void InvalidateCache(const QString& cache_id, const TimeRange& range) = 0;
virtual void InvalidateAllCaches() = 0;
};
```
`RenderManager`(在 `liboliverender.so` 中)实现 `NodeCacheCallbacks`,并在创建节点时注入:
```cpp
class RenderCacheCallbacks : public NodeCacheCallbacks {
void InvalidateCache(const QString& cache_id, const TimeRange& range) override {
// 原有 FrameHashCache 的失效逻辑
}
// ...
};
```
这样 `Node.h` 不再需要包含 `render/rendercache.h`,编译期依赖被打破。
### 2.3 关键解耦实现:RenderJob 虚函数参数抽象
当前 `Node` 有虚函数:
```cpp
// 改造前
virtual void ProcessShader(TexturePtr destination, const Node* node, const ShaderJob* job);
```
改造后:
```cpp
// app/node/jobtypes.h(新增,只含枚举和基类,无 render/ 依赖)
enum class NodeJobType {
kShader,
kGenerate,
kFootage,
kColorTransform,
kSample,
kCache,
};
struct NodeJobData {
NodeJobType type;
void* data; // 实际数据由 render/ 中的具体类解释
};
// app/node/node.h
virtual void ProcessJob(TexturePtr destination, const NodeJobData& job);
```
`RenderProcessor`(在 `liboliverender.so` 中)调用时:
```cpp
ShaderJob job = ...;
NodeJobData data{NodeJobType::kShader, &job};
node->ProcessJob(destination, data);
```
---
## 3. CMake 改造
```cmake
# app/node/CMakeLists.txt
set(NODE_INTERNAL_SOURCES
node.cpp node.h
traverser.cpp traverser.h
traverserproxy.cpp traverserproxy.h
nodevalue.cpp nodevalue.h
# ... 所有 node/ 子目录源文件
)
set(TIMELINE_SOURCES
../timeline/timelinecoordinate.cpp ../timeline/timelinecoordinate.h
../timeline/timelinemarker.cpp ../timeline/timelinemarker.h
../timeline/timelineworkarea.cpp ../timeline/timelineworkarea.h
../timeline/undo/*.cpp ../timeline/undo/*.h
)
set(UNDO_SOURCES
../undo/undocommand.cpp ../undo/undocommand.h
../undo/undostack.cpp ../undo/undostack.h
)
set(CONFIG_SOURCES
../config/config.cpp ../config/config.h
)
set(NODE_API_SOURCES
${CMAKE_SOURCE_DIR}/c_api/src/node_api.cpp
)
add_library(olivenode SHARED
${NODE_INTERNAL_SOURCES}
${TIMELINE_SOURCES}
${UNDO_SOURCES}
${CONFIG_SOURCES}
${NODE_API_SOURCES}
)
target_compile_definitions(olivenode PRIVATE OLIVE_BUILDING_NODE)
target_include_directories(olivenode
PRIVATE
${CMAKE_SOURCE_DIR}/app
${CMAKE_SOURCE_DIR}/c_api/include
PUBLIC
$<INSTALL_INTERFACE:include>
)
target_link_libraries(olivenode
PUBLIC
olivecore
olivecodec
Qt${QT_VERSION_MAJOR}::Core
)
set_target_properties(olivenode PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
)
install(TARGETS olivenode DESTINATION lib)
install(FILES ${CMAKE_SOURCE_DIR}/c_api/include/olive/node_api.h DESTINATION include/olive)
```
---
## 4. 小步快跑实施步骤
### Step 0: 头文件解耦(3–4 天,最关键)
- [ ] 创建 `app/node/nodecachecallbacks.h`,定义 `NodeCacheCallbacks` 接口。
- [ ] 修改 `Node.h`:移除 `render/rendercache.h` 包含,添加 `NodeCacheCallbacks*` 成员和 `SetCacheCallbacks()` 方法。
- [ ] 创建 `app/node/jobtypes.h`,定义 `NodeJobType` 枚举和 `NodeJobData` 结构体。
- [ ] 修改 `Node.h`:将所有 `ProcessXxx` 虚函数合并为 `ProcessJob(TexturePtr, const NodeJobData&)`,或保留原签名但将参数类型改为前向声明。
- [ ] 修改 `RenderProcessor`:适配新的 `NodeCacheCallbacks``NodeJobData`
**验收标准**`app/node/` 目录可以独立编译,不直接或间接包含 `app/render/` 下的任何头文件。
### Step 1: 独立编译 libolivenode.so1 天)
- [ ]`node/`, `timeline/`, `undo/`, `config/` 的源文件聚合,构建为 `olivenode SHARED`
- [ ] 处理 `node/` 下的 `add_subdirectory` 嵌套,确保所有源文件被正确收集。
**验收标准**`libolivenode.so` 编译成功,`nm -D libolivenode.so | grep olive_node` 能看到导出的 C 符号。
### Step 2: 最小 C API2 天)
- [ ] 先实现项目级接口:
- `olive_project_create/destroy/load_file/save_file`
- `olive_node_graph_create/destroy/load_xml/save_xml`
- [ ] 这些接口是渲染子进程最需要的:子进程需要加载 XML 节点图并渲染。
**验收标准**:可以用 C API 创建一个项目、保存为 XML、再加载回来,内容一致。
### Step 3: 节点操作 API2 天)
- [ ] 实现节点增删查改:`add_node`, `remove_node`, `find_node`, `connect`, `disconnect`
- [ ] 实现参数读写:`set_param_double`, `get_param_double`, `set_param_rational` 等。
**验收标准**:可以用 C API 构建一个简单的节点图(如 Generator -> ViewerOutput),并序列化为 XML。
### Step 4: Undo API1 天)
- [ ] 实现 `olive_undo_stack_*` 系列函数。
- [ ] C API 的 undo 采用函数指针回调模式,避免暴露 C++ 的 `UndoCommand` 类。
### Step 5: ViewerOutput 和 Sequence1 天)
- [ ] 实现 `OliveSequence*``OliveViewerOutput*` 的 C API。
- [ ] 这是渲染的入口:渲染子进程需要知道哪个 `ViewerOutput` 是输出目标。
---
## 5. 风险与回退
| 风险 | 对策 |
|---|---|
| Node.h 解耦工作量过大,影响面太广 | **分阶段**:第一阶段只做"编译期解耦"(移除 include),不改虚函数签名。若仍然困难,允许 `libolivenode.so``liboliverender.so` 暂时合并为 `libolive-engine.so`,后续再拆分。 |
| `QObject` 信号槽跨动态库 | Qt 信号槽跨动态库在正确链接 Qt 的情况下工作正常。确保所有含 `Q_OBJECT` 的类在动态库内被 `moc` 处理。 |
| `NodeValueTable` 等模板类难以导出 C 接口 | 不在 C API 中暴露模板类。`NodeTraverser` 的遍历结果(`NodeValueTable`)在 C++ 内部处理,C API 只提供高阶函数如 `olive_node_graph_evaluate_at_time`。 |
| 序列化 XML 格式变更 | C API 中的 `load_xml`/`save_xml` 直接使用现有的 `ProjectSerializer`,XML 格式完全不变,向下兼容。 |
| `Footage` 节点持有 `Decoder` | `Footage` 内部持有 `DecoderPtr`C API 不暴露 Decoder 细节,只暴露 `Footage` 的文件路径设置/获取。 |