Files
oak-editor/docs/zh/plans/riir/M1-oakcommon.md
T
Mike-Solar bad52feda3 refactor(common): de-Qt oakcommon and wrap it in a pure C ABI
- de-Qt all classes under src/common (std::string/vector/mutex,
  std::filesystem, expat-based XmlStreamReader/Writer)
- add pure C ABI in include/common + src/common/c_api: opaque handles,
  init returns NULL on failure, free(NULL) is a no-op, out-params with
  negative OAKCOMMON_E_* error codes (include/common/error.h)
- remove single-consumer classes from oakcommon (html, jobtime,
  otioutils, playbackaudioclock, tohex, util, avframeptr,
  crashpadinterface/crashpadutils, autoscroll, digit, range); their
  destinations are recorded in docs/zh/plans/riir/notes.md
- add gtest suites under src/common/tests (127 cases), standalone
  build driver in src/common/standalone
2026-08-05 17:20:19 +08:00

7.7 KiB
Raw Blame History

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

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.h8 次被引)

/* 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.hcommon/filefunctions.hrender 7 次)

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.hoakcommon/oii.hoakcommon/ffmpeg.h

ocioutils/oiioutils/ffmpegutils,按 01 §3 机械 POD 化; OAK/OIIO/FFmpeg 类型全部句柄化或拍平字段。)

2.5 oakcommon/misc.h

jobtimedouble 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 次 同上,归 oaknodeM3
common → undo/ 1 次 同上,归 oakundoM2
common → codec/ 1 次 同上,归 oakcodecM5
common → pluginSupport/ 1 次 同上,归 oakpluginM9

判据:切完后 grep -rn '#include "' oakcommon/src | grep -vE '"(oakcommon|olive/core)' 为空(只剩 oakcore 与 Qt/系统头)。

4. 测试(映射 03 §2

  • configset/get 往返、int/double fallback、两段式 buf。
  • xmlreader 解析样例串、attr/text 读取、skip、writer 产出解析回读。
  • files:临时目录建/写/读/尺寸/删除。
  • 每函数 1 正常 + 1 错误路径;oakcommon_debug_alive_count() 泄漏断言。

实施现状(2026-08-05

M1 已落地并可独立构建、测试全绿(127 个用例:126 通过,1 个 GTEST_SKIP)。以下为与上文计划的实际差异。

最终目录结构

  • src/common/src/ — 去 Qt 化 C++ 实现(olive:: 命名空间),target oakcommonSHARED)。
  • src/common/c_api/ — 纯 C ABI 包装,通过 target_sources 合并进 oakcommon,不单独成库。
  • src/common/tests/ — gtesttarget oakcommon-gtest gtest_discover_tests
  • include/common/(仓库根)— 公共 C 头:commandlineparser.hcurrent.hdebug.hdropworkflowbehavior.herror.hffmpegutils.hfilefunctions.hmiscutils.hocioutils.hoiioutils.hpower.hqtutils.hxmlutils.h
  • src/common/standalone/CMakeLists.txt — 独立构建 driver(见下)。

独立构建与测试

cmake -S src/common/standalone -B build-oakcommon
cmake --build build-oakcommon -j
ctest --test-dir build-oakcommon --output-on-failure

driver 通过 find_package(... CONFIG) 使用 Homebrew 的 OCIO/OIIO/GTest (顶层 cmake/FindOpenColorIO.cmake/FindOpenImageIO.cmake 面向 .so,macOS 下不适用),并把 config target 映射到 oakcommon CMakeLists 消费的 ${OCIO_LIBRARIES} 等变量。driver 中额外处理了两点: 给 olivecorethird_party/openfx/include 头路径(顶层靠全局 include);禁用 OpenTimelineIO/opt/otio 的 dylib 用 @loader_path 安装名,构建树内无法加载,且 oakcommon 不需要 OTIO)。

实际依赖

  • 第三方:EXPATXML 解析)、OpenColorIO、OpenImageIO、FFmpeg(经 ffmpeg_bridge 间接)、GTest(仅测试)。
  • Oak 内部库:仍链接 olivecoreffmpeg_bridge,二者均为真实 符号依赖而非纯头文件:
    • olive::core::Rationalcore/include/olive/core/util/rational.h 是对 oakcore_rational_* C ABI 的包装,构造/析构/运算都需要 olivecore 的符号;
    • FFmpegUtils::get_compatible_bridge_pixel_format 调用 fb_find_best_pix_fmt_of_listffmpeg_bridge 导出符号)。
    • pixelformat.h/sampleformat.h 本身是 header-only,只需头路径 core/includeffmpeg_bridge/include,因这两个 target 的 include 目录是 PRIVATE,在 oakcommon 里显式补为 PUBLIC)。
  • 未按计划只链 oakcore 头;OLIVECORE_BUILD_TESTS 在独立构建中关闭。

与计划的主要差异

  • 接口未按 §2 冻结清单逐条实现,而是按实际使用面包装:C ABI 头放在 仓库根 include/common/,命名 oakcommon_<模块>_<动词>,两段式 buffer(先查询尺寸再拷入)约定统一。
  • xmlutils 用 expat 实现(事件预先解析成队列);C API 的 oakcommon_xml_reader_read_element_text 因底层是消费型读取,在 handle 内缓存最近一次文本以兼容两段式调用。
  • 被移除/上移的类(播放钟、autoscroll 等反向 include 涉及项)见 notes.md
  • 计划中提到的 switch(PixelFormat) 编译问题实际不存在: olive::core::PixelFormat 提供 operator Format(),可直接 switch。