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

16 KiB
Raw Blame History

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 直接 #includecodec/frame.hrender/ 下的多个头文件(缓存类型、作业类型等)。
  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 类(若已是不透明指针)。
Footagenode/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

#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

// 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,并在创建节点时注入:

class RenderCacheCallbacks : public NodeCacheCallbacks {
    void InvalidateCache(const QString& cache_id, const TimeRange& range) override {
        // 原有 FrameHashCache 的失效逻辑
    }
    // ...
};

这样 Node.h 不再需要包含 render/rendercache.h,编译期依赖被打破。

2.3 关键解耦实现:RenderJob 虚函数参数抽象

当前 Node 有虚函数:

// 改造前
virtual void ProcessShader(TexturePtr destination, const Node* node, const ShaderJob* job);

改造后:

// 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 中)调用时:

ShaderJob job = ...;
NodeJobData data{NodeJobType::kShader, &job};
node->ProcessJob(destination, data);

3. 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:适配新的 NodeCacheCallbacksNodeJobData

验收标准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.soliboliverender.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 直接使用现有的 ProjectSerializerXML 格式完全不变,向下兼容。
Footage 节点持有 Decoder Footage 内部持有 DecoderPtrC API 不暴露 Decoder 细节,只暴露 Footage 的文件路径设置/获取。