R8: finish app/ pure C ABI migration (P3-P9) and make OTIO required

- app/ no longer includes engine C++ headers nor holds engine C++ types:
  engine access goes through the oakengine C ABI plus C++ wrappers
  (oakutil/oaknode.h, oakutil/oakvideo.h) and app-local mirror types
  (tooltypes, trackreferencehandle, timelinecommonapp, keyframetypes,
  subtitleapp, serializedlayoutinfoapp, nodevaluehandle, sliderdisplaytypeapp)
- engine: new C ABI functions for block/track/clip/transition navigation
  and predicates, links, caches, waveform/playback, disk folder,
  sequence_track_list, node_free, footage_is_valid, block_get_track,
  get_brush; loadotio/saveotio ported to the current engine API
- OTIO is now a required dependency: CI and CD build it on every
  platform, FindOpenTimelineIO fixed for OTIO 0.16/0.19 (the old deps
  include requirement silently disabled OTIO everywhere), runtime
  libraries are bundled into packages and copied next to macOS binaries
  (oak_copy_otio_runtime)
- fix ProjectViewModel drag&drop mime read/write size mismatch (segfault)
- unify color label naming (k_olive -> "Oak") in the app-side mirror
- docs: OTIO required, FFmpeg minimum corrected to 6.0 (en/zh)
- gtest suite: 1925 passed, 0 failed
This commit is contained in:
2026-07-31 22:46:52 +08:00
parent 18aed979a2
commit 66d761b4b7
285 changed files with 13261 additions and 5895 deletions
+62 -27
View File
@@ -7,7 +7,8 @@ This document describes how to build Oak Video Editor from source on Windows, Li
- CMake 3.20+
- Ninja (recommended)
- Qt 6 (with private headers)
- FFmpeg 8.0+ development libraries (distro packages on Ubuntu/Debian are often too old; see the Linux section below)
- FFmpeg 6.0+ development libraries
- OpenTimelineIO (0.16+, built from source below — no distro package on most platforms)
- OpenImageIO
- OpenColorIO (2.x)
- OpenEXR
@@ -50,7 +51,23 @@ pacman -S --needed \
> **Note:** Qt 6 private headers may require additional packages depending on the MSYS2 repository state. If CMake reports missing private headers, install `mingw-w64-ucrt-x86_64-qt6-base-private` if available.
### 3. Clone and Build
### 3. Build OpenTimelineIO (required)
There is no MSYS2 package for OpenTimelineIO, so build it from source:
```bash
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
cmake --build OpenTimelineIO/build
cmake --install OpenTimelineIO/build
```
### 4. Clone and Build
```bash
# Clone the repository
@@ -60,13 +77,14 @@ cd oak
# Configure
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DOTIO_LOCATION="/path/to/otio-install" \
-DBUILD_QT6=ON
# Build
cmake --build build --config Release
```
### 4. Run Tests (Optional)
### 5. Run Tests (Optional)
```bash
ctest --test-dir build --output-on-failure -C Release
@@ -78,36 +96,30 @@ ctest --test-dir build --output-on-failure -C Release
### Debian / Ubuntu
Install dependencies (FFmpeg is built from source below because distro packages are often too old):
Install dependencies (Ubuntu 24.04+ ships FFmpeg 6.1, which satisfies the 6.0 minimum; on older releases build FFmpeg from source as described in Troubleshooting):
```bash
sudo apt-get update
sudo apt-get install -y \
cmake ninja-build pkg-config nasm \
cmake ninja-build pkg-config \
qt6-base-dev qt6-base-dev-tools qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev \
libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \
portaudio19-dev libgl1-mesa-dev libvulkan-dev libxkbcommon-dev
```
Build FFmpeg 8.0+ from source:
Build OpenTimelineIO (required, no distro package):
```bash
git clone --branch n8.1.1 --depth 1 https://git.ffmpeg.org/ffmpeg.git ffmpeg-src
cd ffmpeg-src
./configure \
--prefix="$PWD/../ffmpeg-install" \
--enable-static \
--disable-shared \
--disable-doc \
--disable-programs \
--disable-avdevice \
--disable-network \
--enable-pic \
--enable-gpl \
--enable-version3
make -j$(nproc)
make install
cd ..
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
cmake --build OpenTimelineIO/build
cmake --install OpenTimelineIO/build
```
Configure and build:
@@ -115,7 +127,7 @@ Configure and build:
```bash
cmake -S . -B build -G Ninja \
-DBUILD_TESTS=ON -DBUILD_QT6=ON \
-DFFMPEG_ROOT="$PWD/ffmpeg-install"
-DOTIO_LOCATION="$PWD/otio-install"
cmake --build build --config Release
```
@@ -147,10 +159,26 @@ sudo dnf install -y \
bzip2-devel
```
Build OpenTimelineIO (required, no distro package):
```bash
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
cmake --build OpenTimelineIO/build
cmake --install OpenTimelineIO/build
```
Configure and build:
```bash
cmake -S . -B build -G Ninja -DBUILD_TESTS=ON -DBUILD_QT6=ON
cmake -S . -B build -G Ninja -DBUILD_TESTS=ON -DBUILD_QT6=ON \
-DOTIO_LOCATION="$PWD/otio-install"
cmake --build build --config Release
```
@@ -175,6 +203,7 @@ sudo pacman -S --needed \
openexr \
expat \
portaudio \
opentimelineio \
mesa \
vulkan-headers \
vulkan-icd-loader \
@@ -211,13 +240,14 @@ brew update
brew install cmake ninja pkg-config qt@6 ffmpeg openimageio opencolorio openexr portaudio expat molten-vk vulkan-headers vulkan-loader
```
Build OpenTimelineIO (optional, required for OTIO support):
Build OpenTimelineIO (required):
```bash
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
cmake --build OpenTimelineIO/build
@@ -257,7 +287,8 @@ ctest --test-dir build --output-on-failure -C Release
| `BUILD_DOXYGEN` | `OFF` | Build Doxygen documentation |
| `USE_WERROR` | `OFF` | Treat warnings as errors |
| `BUILD_QT6` | `ON` | Build with Qt 6 instead of Qt 5 |
| `OTIO_LOCATION` | - | Path to OpenTimelineIO installation (optional) |
| `OTIO_LOCATION` | - | Path to OpenTimelineIO installation (required) |
| `OAK_BUNDLE_OTIO` | `ON` | Install OTIO runtime libraries alongside Oak (set `OFF` for distro-native packaging where `opentimelineio` is a package dependency, e.g. Arch) |
| `OCIO_LOCATION` | - | Path to OpenColorIO installation |
| `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` | `ON` | Build dynamic render backend libraries (`liboakgl.so` / `liboakvulkan.so`) |
@@ -292,7 +323,7 @@ pkg-config --exists libavcodec && echo "Found" || echo "Not found"
### FFmpeg Version Too Old
If you encounter errors like `AV_PIX_FMT_GRAYF16 was not declared in this scope`, your FFmpeg is too old (Oak requires 8.0+). Build from source:
Oak requires FFmpeg 6.0 or newer; CMake configure fails with `Could NOT find FFMPEG (missing: FFMPEG_VERSION) (Required is at least version "6.0")` on older versions. Ubuntu 24.04+ / Fedora / Arch / Homebrew / MSYS2 all ship new enough FFmpeg. If your distro is older, build from source:
```bash
git clone --branch n8.1.1 --depth 1 https://git.ffmpeg.org/ffmpeg.git ffmpeg-src
@@ -314,3 +345,7 @@ cd ..
```
Then pass `-DFFMPEG_ROOT="$PWD/ffmpeg-install"` to CMake.
### OpenTimelineIO Not Found
OpenTimelineIO is a required dependency. Build it from source as shown in your platform's section above and pass `-DOTIO_LOCATION=/path/to/otio-install` to CMake. On Arch Linux the `opentimelineio` package provides it directly.
+4 -13
View File
@@ -90,9 +90,9 @@ brew install googletest
---
## Build OpenTimelineIO (Optional)
## Build OpenTimelineIO (Required)
OpenTimelineIO enables importing/exporting timeline data in OTIO format. If you don't need OTIO support, you can skip this step.
OpenTimelineIO enables importing/exporting timeline data in OTIO format. It is a required dependency.
```bash
# Clone the repository
@@ -103,6 +103,7 @@ cd OpenTimelineIO
cmake -S . -B build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/install"
@@ -127,16 +128,6 @@ cd oak
### 2. Configure with CMake
Basic configuration (without OTIO):
```bash
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DOCIO_LOCATION=$(brew --prefix opencolorio)
```
Configuration with OTIO support:
```bash
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
@@ -188,7 +179,7 @@ ctest --test-dir build --output-on-failure -C Release
| `BUILD_TESTS` | `OFF` | Build unit tests |
| `BUILD_DOXYGEN` | `OFF` | Build Doxygen documentation |
| `USE_WERROR` | `OFF` | Treat warnings as errors |
| `OTIO_LOCATION` | - | Path to OpenTimelineIO installation (optional) |
| `OTIO_LOCATION` | - | Path to OpenTimelineIO installation (required) |
| `OCIO_LOCATION` | - | Path to OpenColorIO installation |
---
+61 -27
View File
@@ -7,7 +7,8 @@
- CMake 3.20+
- Ninja(推荐)
- Qt 6(含私有头文件)
- FFmpeg 8.0+ 开发库Ubuntu/Debian 系统源里的版本通常太旧,见下文 Linux 章节)
- FFmpeg 6.0+ 开发库
- OpenTimelineIO0.16+,按下文从源码构建——大多数平台没有发行版软件包)
- OpenImageIO
- OpenColorIO2.x
- OpenEXR
@@ -50,7 +51,23 @@ pacman -S --needed \
> **注意:** Qt 6 私有头文件可能需要额外安装。如果 CMake 报告找不到私有头文件,请尝试安装 `mingw-w64-ucrt-x86_64-qt6-base-private`(如果仓库中有)。
### 3. 克隆并构建
### 3. 构建 OpenTimelineIO(必需)
MSYS2 仓库没有 OpenTimelineIO 软件包,需从源码构建:
```bash
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
cmake --build OpenTimelineIO/build
cmake --install OpenTimelineIO/build
```
### 4. 克隆并构建
```bash
# 克隆仓库
@@ -60,13 +77,14 @@ cd oak
# 配置
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DOTIO_LOCATION="/path/to/otio-install" \
-DBUILD_QT6=ON
# 构建
cmake --build build --config Release
```
### 4. 运行测试(可选)
### 5. 运行测试(可选)
```bash
ctest --test-dir build --output-on-failure -C Release
@@ -78,36 +96,30 @@ ctest --test-dir build --output-on-failure -C Release
### Debian / Ubuntu
安装依赖:
安装依赖Ubuntu 24.04+ 自带的 FFmpeg 6.1 已满足 6.0 最低要求;更旧的发行版请按"故障排除"一节从源码编译 FFmpeg
```bash
sudo apt-get update
sudo apt-get install -y \
cmake ninja-build pkg-config nasm \
cmake ninja-build pkg-config \
qt6-base-dev qt6-base-dev-tools qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev \
libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \
portaudio19-dev libgl1-mesa-dev libvulkan-dev libxkbcommon-dev
```
从源码编译 FFmpeg 8.0+
从源码构建 OpenTimelineIO(必需,无发行版软件包)
```bash
git clone --branch n8.1.1 --depth 1 https://git.ffmpeg.org/ffmpeg.git ffmpeg-src
cd ffmpeg-src
./configure \
--prefix="$PWD/../ffmpeg-install" \
--enable-static \
--disable-shared \
--disable-doc \
--disable-programs \
--disable-avdevice \
--disable-network \
--enable-pic \
--enable-gpl \
--enable-version3
make -j$(nproc)
make install
cd ..
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
cmake --build OpenTimelineIO/build
cmake --install OpenTimelineIO/build
```
配置并构建:
@@ -115,7 +127,7 @@ cd ..
```bash
cmake -S . -B build -G Ninja \
-DBUILD_TESTS=ON -DBUILD_QT6=ON \
-DFFMPEG_ROOT="$PWD/ffmpeg-install"
-DOTIO_LOCATION="$PWD/otio-install"
cmake --build build --config Release
```
@@ -147,10 +159,25 @@ sudo dnf install -y \
bzip2-devel
```
从源码构建 OpenTimelineIO(必需,无发行版软件包):
```bash
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
cmake --build OpenTimelineIO/build
cmake --install OpenTimelineIO/build
```
配置并构建:
```bash
cmake -S . -B build -G Ninja -DBUILD_TESTS=ON -DBUILD_QT6=ON
cmake -S . -B build -G Ninja -DBUILD_TESTS=ON -DBUILD_QT6=ON \
-DOTIO_LOCATION="$PWD/otio-install"
cmake --build build --config Release
```
@@ -175,6 +202,7 @@ sudo pacman -S --needed \
openexr \
expat \
portaudio \
opentimelineio \
mesa \
vulkan-headers \
vulkan-icd-loader \
@@ -211,13 +239,14 @@ brew update
brew install cmake ninja pkg-config qt@6 ffmpeg openimageio opencolorio openexr portaudio expat molten-vk vulkan-headers vulkan-loader
```
构建 OpenTimelineIO可选,如需 OTIO 支持):
构建 OpenTimelineIO必需):
```bash
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
cmake --build OpenTimelineIO/build
@@ -257,7 +286,8 @@ ctest --test-dir build --output-on-failure -C Release
| `BUILD_DOXYGEN` | `OFF` | 构建 Doxygen 文档 |
| `USE_WERROR` | `OFF` | 将警告视为错误 |
| `BUILD_QT6` | `ON` | 使用 Qt 6 而非 Qt 5 |
| `OTIO_LOCATION` | - | OpenTimelineIO 安装路径(可选 |
| `OTIO_LOCATION` | - | OpenTimelineIO 安装路径(必需 |
| `OAK_BUNDLE_OTIO` | `ON` | 随 Oak 一并安装 OTIO 运行库(发行版原生打包且 `opentimelineio` 是包依赖时设 `OFF`,如 Arch |
| `OCIO_LOCATION` | - | OpenColorIO 安装路径 |
| `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` | `ON` | 构建动态渲染后端库(`liboakgl.so` / `liboakvulkan.so` |
@@ -284,7 +314,7 @@ export PATH="/ucrt64/bin:$PATH"
### FFmpeg 版本太旧
如果遇到 `AV_PIX_FMT_GRAYF16 was not declared in this scope` 之类的错误,说明你的 FFmpeg 版本太旧(Oak 需要 8.0+)。请从源码编译:
Oak 要求 FFmpeg 6.0 或更新版本;版本不足时 CMake 配置阶段会报 `Could NOT find FFMPEG ... (Required is at least version "6.0")`。Ubuntu 24.04+ / Fedora / Arch / Homebrew / MSYS2 自带的版本都足够新。如果发行版过旧,请从源码编译:
```bash
git clone --branch n8.1.1 --depth 1 https://git.ffmpeg.org/ffmpeg.git ffmpeg-src
@@ -306,3 +336,7 @@ cd ..
```
然后在 CMake 中加上 `-DFFMPEG_ROOT="$PWD/ffmpeg-install"`
### 找不到 OpenTimelineIO
OpenTimelineIO 是必需依赖。按你所在平台章节的说明从源码构建,并在 CMake 中加上 `-DOTIO_LOCATION=/path/to/otio-install`。Arch Linux 可直接安装 `opentimelineio` 包。
+4 -13
View File
@@ -90,9 +90,9 @@ brew install googletest
---
## 编译 OpenTimelineIO可选
## 编译 OpenTimelineIO必需
OpenTimelineIO 支持以 OTIO 格式导入/导出时间线数据。如果你不需要 OTIO 支持,可以跳过此步骤
OpenTimelineIO 支持以 OTIO 格式导入/导出时间线数据,是必需依赖
```bash
# 克隆仓库
@@ -103,6 +103,7 @@ cd OpenTimelineIO
cmake -S . -B build -G Ninja \
-DOTIO_SHARED_LIBS=ON \
-DOTIO_PYTHON_BINDINGS=OFF \
-DOTIO_FIND_IMATH=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${PWD}/install"
@@ -127,16 +128,6 @@ cd oak
### 2. 使用 CMake 配置
基础配置(不包含 OTIO):
```bash
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DOCIO_LOCATION=$(brew --prefix opencolorio)
```
包含 OTIO 支持的配置:
```bash
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
@@ -188,7 +179,7 @@ ctest --test-dir build --output-on-failure -C Release
| `BUILD_TESTS` | `OFF` | 构建单元测试 |
| `BUILD_DOXYGEN` | `OFF` | 构建 Doxygen 文档 |
| `USE_WERROR` | `OFF` | 将警告视为错误 |
| `OTIO_LOCATION` | - | OpenTimelineIO 安装路径(可选 |
| `OTIO_LOCATION` | - | OpenTimelineIO 安装路径(必需 |
| `OCIO_LOCATION` | - | OpenColorIO 安装路径 |
---
+53 -2
View File
@@ -67,7 +67,7 @@ engine 内部保留原文件。
| 批次 | 子系统 | 违规 include | 替换方案 |
|---|---|---|---|
| P2 | node/value.h + node/keyframe.h | ~10 处 | oakengine/node.h 的 oak_node_value_type 枚举 |
| P3 | node/node.h + node/param.h | ~20 处 | OakEngineNode* + oakengine_node_* 函数 |
| P3 | node/node.h + node/param.h | ~20 处 | OakEngineNode* + oakengine_node_* 函数(执行手册:[r8-p3-node-param-abi.md](r8-p3-node-param-abi.md) |
| P4 | node/project*.h | ~15 处 | OakEngineProject*/OakEngineSequence* |
| P5 | render/* | ~10 处 | oakengine/display.h + viewer.h |
| P6 | timeline/* | ~6 处 | oakengine/timeline.h |
@@ -95,9 +95,52 @@ cmake --build build && ctest --test-dir build --output-on-failure
## 状态
- [x] Phase 1shared/ 纯头工具层(define, lerp, decibel, digit, range, crashpadutils, qtutils, filefunctions, xmlutils, autoscroll
- [ ] Phase 2-9C++ 类替换(node/, render/, timeline/, codec/, audio/, task/, undo/, tool/, pluginSupport/
- [x] Phase 2node/value.h + node/keyframe.h 直接 include 已全部移除
- app/common/nodevaluehandle.h 提供 AppNodeValueType 本地枚举
- app/common/oakvaluehelper.h 提供 AppKeyframeType + 转换函数
- NodeValue::Type → int, NodeValue::k_* → AppNodeValueType 常量
- keyframeproperties.h 改为前置声明 NodeKeyframe
- [x] Phase 3node/node.h + node/param.h 主体清理完成,**以双适配器(oak:: wrapper)形态落地**
消费侧统一经 `shared/include/oakutil/oaknode.h` 访问 engineAppNodeInput 方案已废弃
(落地细节与 WRAPPER-GAP 登记见 [r8-p3-node-param-abi.md](r8-p3-node-param-abi.md) 的"落地状态"一节)
- [x] Phase 4-9 + 补充批(2026-07-29 完成):node/project*、render/*、timeline/*、codec/*、audio/*、
tool/*、undo/*、pluginSupport/* 及 P3 遗留(block/track/clip/gizmo/factory 等)全部清理,
同样以双适配器形态落地:
- engine 侧新增 30 个 C ABI 函数(block/track/clip/transition 导航与谓词、链接、
thumbnail/waveform/frame cache、playback cache、disk folder、sequence_track_list、
visible_block_at_time、set_length_and_media_out、node_free、footage_is_valid、
block_get_track 等)
- 新增 app 侧构件:`shared/include/oakutil/oakvideo.h`oak::VideoParams/ColorTransform)、
`app/common/`tooltypes.h、trackreferencehandle.h、keyframetypes.h、subtitleapp.h、
serializedlayoutinfoapp.h、nodedatatypes.h、projecttypes.h、sliderdisplaytypeapp.h)、
`app/timeline/timelinecommonapp.h`TimelineApp 枚举镜像)、
`app/widget/history/historywidget.{h,cpp}`HistoryModelC ABI 驱动)
- `app/core.h` 不再 include engine `coreengine.h`Tool/Timecode/Color 改经镜像与 olive/core
- [x] 类型清洗(2026-07-29Wave4/5):app/ 内部接口不再持有 engine C++ 类型
olive::Node*/Block*/ClipBlock*/Track*/Sequence*/ViewerOutput*/TimelineMarker* 等全部
换成 OakEngine* 句柄或 oak:: wrapper),~600 处双向 reinterpret_cast 消除;
engine 侧配套新增 `oakengine_node_get_brush`Qt QBrush 越界,照 QPainter* 先例)。
全树 grep 终验:代码级 engine 类型使用仅剩 1 处论证例外(见下)。
全量链接构建 0 error。
- [ ] Phase 10CMake 收口
### Phase 4-9 已知遗留
- `app/widget/timebased/timebasedwidget.h` 仍 include engine `node/output/viewer/viewer.h`
+ `timebasedwidget.cpp:104``ViewerOutput*` 桥接:
`QPointer<ViewerOutput>` 需要完整 QObject 类型,C ABI 无节点销毁事件与 QObject* 访问器,
无法等价替代(文件内已论证并标 WRAPPER-GAP;是 app/ 唯一保留的 engine 类型使用)。
- WRAPPER-GAP 登记(app 代码内注释,后续 facade 扩充时清理):
undo 命令装配、group passthrough、traverse、clipboard、NodeValueRow create/free
viewerdisplay 的 gizmo_db_)、oak::Footage 的 proxy 家族方法、TrackList 枚举
oakengine_sequence_track_list 暂无查询函数)、VideoParams::stream_index、
Node::get_context_positions、waveform validated ranges 专用 ABI、
playback_cache_draw 缺 y 偏移参数、泛型 QVariant input property 读取
widgetbridge 以类型化 getter 重建)、engine keyframe has_sibling/closest 系
"整秒"契约与 engine 语义不符(app 侧已用精确 rational 路径规避)、
plugin CAdapter 的 is_cancelled 回调未接(取消不生效,engine 侧缺口)。
### Phase 1 已知遗留
- `app/widget/timebased/timebasedwidget.cpp` 仍引用 `common/current.h`(依赖 pluginSupport + render,需 Phase 2+ 处理)
@@ -106,3 +149,11 @@ cmake --build build && ctest --test-dir build --output-on-failure
`common/dropworkflowbehavior.h`app/widget/timelinewidget/tool/import.h
- `engine/node/project.h` 原先经 `common/xmlutils.h` 间接获得 `NodeGroup` 前置声明,
精简后已改为在 project.h 内显式前置声明
### Phase 2 已知遗留
- `node/node.h` 仍被 11 个 app/ 头文件直接引用(widget 层深度使用 NodeInput、
NodeKeyframeTrackReference、Node::Position、Node* 方法调用)—— 待 Phase 3 处理
- `node/param.h` 仍被 4 个 app/ 头文件直接引用(NodeInput 类型)
- 其他 engine 内部头引用(render/, timeline/, codec/, audio/, node/project*,
node/output/, pluginSupport/, tool/, undo/)—— 待 Phase 4-9
+549
View File
@@ -0,0 +1,549 @@
# R8-P3 执行手册:清除 app/ 对 node/node.h + node/param.h 的直接引用
> 本手册是**纯执行指令**。所有设计决策已经做出,不要重新设计、不要引入
> 本手册之外的改动。遇到与本手册矛盾的事实时停下来报告,不要自行变通。
>
> 目标:`grep -rn '#include "node/node.h"\|#include "node/param.h"' app/` 结果为 0。
> 原则:**engine/ 侧零改动**。所需的全部 C ABI 函数已存在于
> `engine/include/oakengine/node.h`(映射表见第 3 节),本阶段只在 app/ 内工作。
---
## 落地状态(2026-07-29 修订,取代 §2.1 的 AppNodeInput 方案)
本阶段最终按**双适配器**形态落地,与下文 §2.1 的原始决策不同:
- 消费侧不直接调 C ABI,统一经过 C++ wrapper 层
`shared/include/oakutil/oaknode.h`namespace `oak`):
`Node`/`Project`/`Footage`/`Input`/`Keyframe`/`KeyframeTrackRef`/`InputPair`
+ `NodeCategory` 枚举 + `NodeConnection`/`ContextNodeItem` 结构。
wrapper 只做转发;owned/borrowed 语义见文件头注释。
- `app/common/nodeinputhandle.h``AppNodeInput`/`AppNodeInputPair`/
`AppNodeKeyframeTrackReference`/`AppNodeCategory` 方案)**已废弃并删除**
全部消费点迁到 `oak::` 类型。
- app/ 中 node/param 相关裸 `oakengine_*` 调用已清零;保留的裸调用均属
其它子系统,以 `// WRAPPER-GAP:` 注释登记(见下表),归后续批次建立
对应 wrapper 时清理。
WRAPPER-GAP 登记(按归属批次分组):
| 归属 | 函数族 | 位置(示例) |
|---|---|---|
| undo 批次 | `oakengine_undo_*``oakengine_node_*_command` | nodeview.cpp、nodeparamviewcontext.cpp、mainwindowundo.cpp |
| group 批次 | `oakengine_group_*`passthrough/resolve/create | nodeview.cpp、nodeparamview.cpp、nodeparamviewitem.cpp |
| traverse 批次 | `oakengine_traverse_*``oakengine_node_set_value_hint` | nodevaluetree.cpp、nodetableview.cpp |
| clipboard 批次 | `oakengine_clipboard_*``oakengine_nodes_delete_many` | nodeview.cpp |
| keyframe 查询 | 最早/最晚/最近 keyframe、`get_split_*` 等 | nodeparamviewkeyframecontrol.cpp |
| 杂项 | `Node::getPluginInstance``Node::get_input_property``oakengine_node_array_insert_at/remove_at``Node::brush()``Node::has_gizmos`、效果标志常量 | nodeparamviewitem.cpp、nodeparamview.cpp、nodeviewitem.cpp |
除原手册列出的文件外,本次一并完成了前半程未迁完的文件:
`nodeparamviewitem.h/.cpp``nodeparamview.h/.cpp``nodeparamviewcontext.cpp`
`nodeparamviewwidgetbridge.cpp``nodevaluetree.h/.cpp``curvewidget.h/.cpp`
`curveview.cpp``timebasedwidget.cpp``nodeviewscene.cpp``nodewidget.h`
`panel/node/node.h``panel/param/param.*``toolbar.cpp``mainwindow.cpp`
`mainwindowundo.h/.cpp``timelinewidget/tool/import.cpp`(仅一处)、
`hashstreamapp.cpp``tests/gtest/widget_panels_model_test.cpp`
### 落地后已知遗留(2026-07-29 Wave3 收尾后)
- ~~`app/widget/viewer/viewerdisplay.h``app/widget/colorwheel/*.cpp` 仍直接
include `node/node.h`~~Wave2 已清)。
- ~~`app/widget/nodeparamview/nodeparamviewwidgetbridge.h` 公开构造签名仍用
engine `NodeInput`~~Wave3 已收敛到 `oak::Input`)。
- app/ 全树唯一保留的违规 include:`app/widget/timebased/timebasedwidget.h`
的 engine `node/output/viewer/viewer.h``QPointer<ViewerOutput>` 需要完整
QObject 类型,C ABI 无节点销毁事件,文件内有论证,待 facade 增加销毁通知后清理)。
- `tests/gtest/` 5 个文件白盒引用 engine C++ 头(engine 级测试,不在 P3 范围)。
- `nodeparamviewkeyframecontrol.cpp` 等仍经 `node/value.h` 传递使用 engine
C++ keyframe 查询方法(见 WRAPPER-GAP 表 keyframe 查询行)。
- `app/core.cpp``app/dialog/otioproperties/otiopropertiesdialog.cpp`
OTIO 适配已在 OTIO 必需化后本机验证编译通过(/opt/otio 0.19 与
otio-install 0.16 双版本)。
- 全量对象编译 + 全量链接构建均 0 errorcmake-build-debug,含 OTIO)。
---
## 0. 前置条件(先确认再动手)
1. 工作树中 P2 的改动(node/value.h + node/keyframe.h 清理)应先已提交。
P3 必须基于干净工作树开始,结束时单独成一个 commit。
2. 本机(macOS)验证环境说明,**不要被全量构建失败迷惑**:
- `liboakengine.dylib` 链接依赖 `18aed979a``-U,__ZN5olive13k_app_versionE`
修复,确认该 commit 已在历史中。
- 本机未安装 OpenTimelineIO`app/core.cpp`
`app/dialog/otioproperties/otiopropertiesdialog.cpp` **永远无法在本机编译**
与本次改动无关,验证时跳过这两个文件。
- 验证编译用对象级构建(绕过 dylib 依赖与 OTIO 文件):
```bash
cd cmake-build-debug
ninja -t targets all | grep -oE 'app/CMakeFiles/libolive-editor\.dir/[^:]+\.o' \
| sort -u | grep -vE 'dir/core\.cpp\.o|otiopropertiesdialog\.cpp\.o' > /tmp/app_objs.txt
xargs ninja -j"$(sysctl -n hw.ncpu)" < /tmp/app_objs.txt
# engine 侧:
ninja engine/CMakeFiles/oakengine-obj.dir/all 2>/dev/null || ninja $(ninja -t targets all | grep -oE 'engine/CMakeFiles/oakengine-obj\.dir/[^:]+\.o' | sort -u)
```
- 每完成下面一个步骤就跑一次上述 app 对象编译,保持可编译状态。
---
## 1. 现状清单(20 个文件,调查于 P2 完成后)
事实:`engine/node/node.h:40` 自己 include `node/param.h`,所以 include
node/node.h 等于同时引入两个头。
| 分组 | 文件 | 直接 include | 关键用法 |
|---|---|---|---|
| A 琐碎 | `app/widget/nodecombobox/nodecombobox.h` | node/node.h | **完全未使用任何符号**,直接删 |
| A 琐碎 | `app/dialog/preferences/tabs/preferencesappearancetab.cpp` | node/node.h | 仅用 `Node::k_category_count`:72 |
| A 琐碎 | `app/widget/menu/factorymenu.h` | node/node.h | `Node::CategoryID`/`k_category_unknown`:44)、`Node*` 返回值(:52 |
| B 指针替换 | `app/widget/nodeparamview/nodeparamviewitembase.h` | node/node.h | `get_title_bar_text_from_node(Node*)`:52)一处 |
| B 指针替换 | `app/widget/nodetableview/nodetableview.h` | node/node.h | `QVector<Node*>` 参数、`QMap<Node*, QTreeWidgetItem*>`:44 |
| B 指针替换 | `app/widget/nodevaluetree/nodevaluetree.h` | node/node.h | `set_node(const NodeInput&, Rational)`:35)一处 |
| C nodeview | `app/widget/nodeview/nodeviewedge.h` | node/node.h | `Node* output_`、`NodeInput input_` 值成员(:125/127 |
| C nodeview | `app/widget/nodeview/nodeviewitem.h` | node/node.h | `Node::Position`:60/63)、`get_input()` 按值返回 NodeInput:75 |
| C nodeview | `app/widget/nodeview/nodeviewcontext.h` | node/node.h | `Node*` 成员/参数、`NodeInput` const 引用参数 |
| D TrackRef | `app/widget/nodetreeview/nodetreeview.h` | node/node.h | `NodeKeyframeTrackReference` 值存储、QHash key、signal 签名(:69-73 |
| D TrackRef | `app/widget/keyframeview/keyframeviewinputconnection.h` | node/node.h + node/param.h | `NodeKeyframe*`、`Node::get_keyframe_tracks()`:55-61 |
| E paramview | `app/widget/nodeparamview/nodeparamview.h` | node/node.h | `QVector<Node*>`、`QHash<Node*,Node*>`、slot 签名带 NodeInput |
| E paramview | `app/widget/nodeparamview/nodeparamviewarraywidget.h/.cpp` | param.h / node.h | `Node* node_`、`node_->input_array_size()`cpp:53 |
| E paramview | `app/widget/nodeparamview/nodeparamviewconnectedlabel.h/.cpp` | param.h / node.h | `NodeInput input_` 值成员、NodeInput 成员调用 |
| E paramview | `app/widget/nodeparamview/nodeparamviewitem.h` | node/node.h | `QHash<NodeInput,…>`、`QHash<NodeInputPair,…>`、signal 签名 |
| E paramview | `app/widget/nodeparamview/nodeparamviewkeyframecontrol.h` | node/param.h | `NodeInput input_` 值成员、slot 签名 |
| F 特殊 | `app/common/hashstreamapp.cpp` | node/param.h | NodeInput/NodeInputPair/NodeKeyframeTrackReference 的 qHash |
| F 特殊 | `app/widget/viewer/viewerdisplay.h` | node/node.h | `Node*` 参数/成员、`NodeValueRow`P5 遗留见 4.8 |
`Node::Position`、`Node::get_keyframe_tracks` 的真实用法比预想少:
`Node::Position` 仅 nodeviewitem.h 两处且**纯 UI 聚合,不触 engine**
`NodeKeyframeTrackReference` 仅 nodetreeview.h + keyframeviewinputconnection.h。
---
## 2. 核心决策(已定,照做即可)
### 2.1 新建 `app/common/nodeinputhandle.h`(本阶段唯一新增文件)
这是 P2 的 `nodevaluehandle.h` 的姊妹文件:app 侧值类型镜像 + 薄封装。
**完整内容如下,直接照抄创建**(许可证头与 P2 文件保持一致):
```cpp
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_NODEINPUTHANDLE_H
#define OAK_NODEINPUTHANDLE_H
#include <QHash>
#include <QString>
#include "oakengine/node.h"
namespace olive
{
/**
* @brief App-local mirror of engine's olive::NodeInput (node/param.h).
*
* Value type identifying one input (or array element) on a node. The C ABI
* identifies inputs by (node, input_id, element), so this struct interoperates
* with oakengine_node_input_*() directly. Semantics must stay identical to the
* engine type: default-constructed is {nullptr, QString(), -1}.
*/
struct AppNodeInput {
AppNodeInput() : node(nullptr), element(-1) {}
AppNodeInput(OakEngineNode *n, const QString &i, int e = -1)
: node(n), input(i), element(e)
{
}
bool operator==(const AppNodeInput &rhs) const
{
return node == rhs.node && input == rhs.input &&
element == rhs.element;
}
bool operator!=(const AppNodeInput &rhs) const { return !(*this == rhs); }
bool operator<(const AppNodeInput &rhs) const
{
if (node != rhs.node) return node < rhs.node;
if (input != rhs.input) return input < rhs.input;
return element < rhs.element;
}
bool is_valid() const { return node != nullptr; }
OakEngineNode *node;
QString input;
int element;
};
inline uint qHash(const AppNodeInput &i, uint seed = 0)
{
return ::qHash(i.node, seed) ^ ::qHash(i.input, seed) ^
::qHash(i.element, seed);
}
/**
* @brief App-local mirror of engine's olive::NodeInputPair.
*
* NOTE: engine's NodeInputPair is {Node *node; QString input;} — NO element.
* Keep the same shape; it is used as the array-widget key.
*/
struct AppNodeInputPair {
bool operator==(const AppNodeInputPair &rhs) const
{
return node == rhs.node && input == rhs.input;
}
OakEngineNode *node = nullptr;
QString input;
};
inline uint qHash(const AppNodeInputPair &p, uint seed = 0)
{
return ::qHash(p.node, seed) ^ ::qHash(p.input, seed);
}
/**
* @brief App-local mirror of engine's olive::NodeKeyframeTrackReference.
*/
struct AppNodeKeyframeTrackReference {
AppNodeKeyframeTrackReference() : track(-1) {}
AppNodeKeyframeTrackReference(const AppNodeInput &i, int t = 0)
: input(i), track(t)
{
}
bool operator==(const AppNodeKeyframeTrackReference &rhs) const
{
return input == rhs.input && track == rhs.track;
}
bool operator!=(const AppNodeKeyframeTrackReference &rhs) const
{
return !(*this == rhs);
}
AppNodeInput input;
int track;
};
inline uint qHash(const AppNodeKeyframeTrackReference &r, uint seed = 0)
{
return qHash(r.input, seed) ^ ::qHash(r.track, seed);
}
/**
* @brief App-local mirror of engine's Node::CategoryID ordinals
* (engine/node/node.h). Values must stay in sync with the engine enum;
* oakengine_node_category_name() takes these ordinals directly.
*/
enum AppNodeCategory {
k_app_category_unknown = -1,
k_app_category_output,
k_app_category_generator,
k_app_category_math,
k_app_category_keying,
k_app_category_filter,
k_app_category_color,
k_app_category_time,
k_app_category_timeline,
k_app_category_transition,
k_app_category_distort,
k_app_category_project,
k_app_category_open_fx,
k_app_category_count
};
/* Thin wrappers over the C ABI replacing the NodeInput member functions that
* app code actually called. */
inline bool app_input_is_connected(const AppNodeInput &i)
{
return oakengine_node_input_is_connected(
i.node, i.input.toUtf8().constData()) != 0;
}
inline OakEngineNode *app_input_get_connected_node(const AppNodeInput &i)
{
return oakengine_node_input_get_connected_node(
i.node, i.input.toUtf8().constData(), i.element);
}
} // namespace olive
Q_DECLARE_METATYPE(olive::AppNodeInput)
Q_DECLARE_METATYPE(olive::AppNodeKeyframeTrackReference)
#endif // OAK_NODEINPUTHANDLE_H
```
要点:
- `AppNodeInput` 字段公开(node/input/element),替换代码里 `input_.node()`
→ `input_.node`、`input_.input()` → `input_.input`、`input_.element()`
→ `input_.element`。
- `Q_DECLARE_METATYPE` 使这两个类型可以进 Qt signal/slot 签名和
`QVariant::fromValue`item data)。这与 `OakEngineNode*` 进 MOC 的既有
先例一致;**不要**再把它们改成非 slot 规避。
### 2.2 `Node::Position` → nodeviewitem 本地纯 UI 结构
`NodeViewItem::get_node_position_data()` 只是把 item 自身的
`pos()+is_expanded()` 打包(nodeviewitem.cpp:120-123),`set_node_position`
只是解包(:137-141),完全不触 engine。决策:在 `nodeviewitem.h` 内定义
局部结构,不进公共头:
```cpp
struct NodeViewItemPosition {
QPointF position;
bool expanded = false;
};
```
签名改为 `NodeViewItemPosition get_node_position_data() const` /
`void set_node_position(const NodeViewItemPosition &pos)`。调用点
nodeview 相关 .cpp)同名替换即可。
### 2.3 `Node*` → `OakEngineNode*`
所有成员变量、参数、返回值、容器 key 机械替换。两者是同一指针的不同
opaque 类型,边界处用 `reinterpret_cast`(既有先例:
nodeparamview.h:152、connectedlabel.cpp 等)。EngineEventBridge 的信号
全部已经是 `OakEngineNode*` + `(QString input, int element)`lambda 里
把旧的 `NodeInput(reinterpret_cast<Node*>(source), input, element)` 改为
`AppNodeInput(source, input, element)`。
### 2.4 `Node::CategoryID` → `AppNodeCategory`
`preferencesappearancetab.cpp:72` 的 `Node::k_category_count` →
`k_app_category_count``factorymenu.h:44` 的参数类型与默认值
`Node::k_category_unknown` → `AppNodeCategory` / `k_app_category_unknown`。
`oakengine_node_category_name(i, …)` 的调用已经用 int 序数,不用动。
### 2.5 `NodeKeyframe*` / `Node::get_keyframe_tracks` → C ABI 循环
keyframeviewinputconnection.h:55-61 目前内联调用
`input_.input().node()->get_keyframe_tracks(input_.input()).at(input_.track())`
返回 `const QVector<NodeKeyframe*>&`。决策:
- 成员类型改为 `AppNodeKeyframeTrackReference input_;`(含 track)。
- `get_keyframes()` 改为**按值返回** `QVector<OakEngineKeyframe*>`,实现
移到 .cpp,用 C ABI 拼装:
```cpp
QVector<OakEngineKeyframe *> KeyframeViewInputConnection::get_keyframes() const
{
QVector<OakEngineKeyframe *> keys;
const int n = oakengine_node_keyframe_count_on_track(
input_.input.node, input_.input.input.toUtf8().constData(),
input_.input.element, input_.track);
keys.reserve(n);
for (int i = 0; i < n; i++) {
keys.append(oakengine_node_keyframe_handle_on_track(
input_.input.node, input_.input.input.toUtf8().constData(),
input_.input.element, input_.track, i));
}
return keys;
}
```
- 完成后 grep `get_keyframes()` 的全部调用方(keyframeview.cpp 等),
把 `NodeKeyframe*` 改为 `OakEngineKeyframe*`,属性访问改走 P2 已建立的
keyframe C ABI`oakengine_node_keyframe_*` / `OakEngineKeyframe` 访问器)。
若调用方用到某个没有 C ABI 对应的 keyframe 成员函数,**停下来报告**,
不要自己在 engine 加函数。
### 2.6 hashstreamapp.cpp 瘦身
删除 :31-44 的三个 qHashNodeInput/NodeInputPair/
NodeKeyframeTrackReference)——App 类型的 qHash 已由新头内联提供。
保留 Track::Reference 的 qHash 与 QDataStream 运算符(来自
`node/output/track/track.h`,属 P6 范围),删除
`#include "node/param.h"`,保留 `#include "node/output/track/track.h"`。
### 2.7 nodetreeview 顺带修一个既有 bug
nodetreeview.h:96-97 `k_item_input_reference` 与 `k_item_node_pointer`
都定义为 `Qt::UserRole + 1`。改为 `Qt::UserRole + 1` / `Qt::UserRole + 2`。
item data 中存的引用类型随之改为
`QVariant::fromValue(AppNodeKeyframeTrackReference)`。
### 2.8 viewerdisplay.h 只做最小改动
该头还有 `node/gizmo/text.h`、`node/output/track/tracklist.h`、
`node/color/colormanager/colormanager.h` 三处直接 include,属 P5/P6 范围,
**本阶段不动**。本阶段只做:
- 删除 `#include "node/node.h"`
- `set_gizmos(Node*)`、`generate_gizmo_transform(Node*, Node*, …)`、
成员 `Node *gizmos_` 改为 `OakEngineNode*`
- `NodeValueRow`、`NodeGizmo*`、`TextGizmo*` 维持现状(它们经 gizmo/text.h
链传递引入,P5 处理),.cpp 中把 `OakEngineNode*` 传给 gizmo C++ API 的
边界处加 `reinterpret_cast<Node*>`。
---
## 3. C ABI 映射表(证明 engine 零改动)
| 旧调用 | 替换 |
|---|---|
| `input.is_connected()` | `app_input_is_connected()`(新头,包装 `oakengine_node_input_is_connected` |
| `input.get_connected_output()` | `app_input_get_connected_node()`(包装 `oakengine_node_input_get_connected_node` |
| `input.node()/.input()/.element()` | `AppNodeInput` 公开字段 |
| `node->input_array_size(id)` | `oakengine_node_input_array_size` |
| `node->name()` | `oakengine_node_get_name` |
| `Node::disconnect` 等 | 已迁过(`oakengine_node_disconnect_ex` 等),不在本阶段 |
| `Node::get_keyframe_tracks` | `oakengine_node_keyframe_count_on_track` + `oakengine_node_keyframe_handle_on_track`(见 2.5 |
| `Node::k_category_count` / `CategoryID` | `AppNodeCategory`2.4 |
| `Node::Position` | 本地 `NodeViewItemPosition`2.2 |
| `NodeInputPair` | `AppNodeInputPair`(注意:无 element 字段) |
| `NodeKeyframeTrackReference` | `AppNodeKeyframeTrackReference`(字段 input/track 公开) |
---
## 4. 执行步骤(按序,每步后可编译)
### 4.1 步骤 1:新建头 + hashstreamapp 瘦身
- 按 2.1 创建 `app/common/nodeinputhandle.h`。
- 按 2.6 改 `app/common/hashstreamapp.cpp`。
- 编译验证(此时还没有使用方,只验证新头自身可编译:随便一个已改文件
include 它即可,或等到步骤 2 一起验证)。
### 4.2 步骤 2A 组(琐碎)
- `nodecombobox.h`:删除 `#include "node/node.h"`。
- `preferencesappearancetab.cpp`include 换 `common/nodeinputhandle.h`
:72 `Node::k_category_count` → `k_app_category_count`。
- `factorymenu.h`include 换 `common/nodeinputhandle.h`:44 参数类型
`Node::CategoryID` → `AppNodeCategory`,默认值 → `k_app_category_unknown`
:52 返回值 `Node*` → `OakEngineNode*`。检查 factorymenu.cpp 及调用方
`create_node_from_menu_action` 的使用处)同步改类型。
### 4.3 步骤 3:B 组(纯指针/引用替换)
- `nodeparamviewitembase.h``Node*` → `OakEngineNode*`;检查对应
.cpp 实现内部(如用 `n->name()`/`n->GetLabel()` 改 C ABI
`oakengine_node_get_name`/`oakengine_node_get_label`)。
- `nodetableview.h``QVector<Node*>` → `QVector<OakEngineNode*>`
`QMap<Node*, QTreeWidgetItem*>` → `QMap<OakEngineNode*, …>`。
该头用到的 `Rational` 来自 core`olive/core/...`),确保 include
core 头而不是靠 node.h 传递。
- `nodevaluetree.h``set_node(const NodeInput&, const Rational&)` →
`set_node(const AppNodeInput&, const Rational&)`.cpp 内
`input.node()/input()/element()` 改字段访问 + C ABI。
### 4.4 步骤 4C 组(nodeview 簇)
顺序:nodeviewedge.h → nodeviewitem.h → nodeviewcontext.h → 各自 .cpp。
- `nodeviewedge.h``Node* output_` → `OakEngineNode*`
`NodeInput input_` → `AppNodeInput input_``output()`/`input()` 返回类型
同步。构造函数参数同步。
- `nodeviewitem.h`:按 2.2 加 `NodeViewItemPosition``Node*` →
`OakEngineNode*``NodeInput get_input()` → `AppNodeInput get_input()`
(返回 `AppNodeInput(node_, input_, element_)`);
`get_item_for_input(NodeInput)` → `AppNodeInput`。
- `nodeviewcontext.h``Node*` → `OakEngineNode*`(含 `QMap`、`QHash` key、
`context_subs_`);`const NodeInput &` 参数 → `const AppNodeInput &`。
- 各 .cpp:桥接 lambda 按 2.3 组装 `AppNodeInput`;其它 Node 成员调用改
C ABI(绝大多数之前已迁,只剩类型改名)。
### 4.5 步骤 5D 组(TrackRef
- `nodetreeview.h`include 换 `common/nodeinputhandle.h`(保留已有的
`oakengine/node.h`);`NodeKeyframeTrackReference` →
`AppNodeKeyframeTrackReference`signals、成员、QHash key 全部);
按 2.7 修 UserRole bug`Node*` → `OakEngineNode*`。
- `nodetreeview.cpp``ref.input()` → `ref.input``ref.track()` →
`ref.track`item data 读写改 `QVariant::fromValue(...)`/`value<...>()`。
- `keyframeviewinputconnection.h/.cpp`:按 2.5。注意 `get_reference()`
返回类型改为 `AppNodeKeyframeTrackReference`。
### 4.6 步骤 6E 组(nodeparamview 簇,最大)
顺序:connectedlabel → keyframecontrol → arraywidget → item → view。
- `nodeparamviewconnectedlabel.h/.cpp`
- 成员 `NodeInput input_` → `AppNodeInput input_`
`Node *connected_node_` → `OakEngineNode *connected_node_`
- slots `input_connected/input_disconnected(OakEngineNode*, const NodeInput&)`
第二个参数改 `const AppNodeInput &`
- cpp:85-89 `input_.is_connected()`/`get_connected_output()` →
`app_input_is_connected(input_)`/`app_input_get_connected_node(input_)`
- cpp:95-108 lambda 里 `NodeInput(reinterpret_cast<Node*>(source), …)` →
`AppNodeInput(source, …)`
- cpp:180-181 的 `oakengine_node_disconnect_ex` 调用已合规,只需把
`input_.node()`/`input_.input()`/`input_.element()` 改字段访问;
- cpp:199 `connected_node_->name()` → `oakengine_node_get_name`
- `value_tree_->set_node(input_, …)` 随 4.3 的 nodevaluetree 改后自然兼容。
- `nodeparamviewkeyframecontrol.h``NodeInput input_` → `AppNodeInput`
slot `keyframe_enable_changed(const NodeInput&, bool)` → `AppNodeInput`
`get_connected_input()` 返回类型同步。
- `nodeparamviewarraywidget.h/.cpp``Node* node_` → `OakEngineNode*`
cpp:53 `node_->input_array_size(input_)` →
`oakengine_node_input_array_size(node_, input_.toUtf8().constData())`
cpp:45 的 `reinterpret_cast<void*>(node_)` 订阅保持不变。
- `nodeparamviewitem.h``QHash<NodeInput, InputUI>` →
`QHash<AppNodeInput, InputUI>``NodeInputPair` → `AppNodeInputPair`
signal `input_checked_changed(const NodeInput&, bool)` 与
slot `edge_changed(OakEngineNode*, const NodeInput&)` 改 `AppNodeInput`
`get_element_y(NodeInput)` → `AppNodeInput``Node*` → `OakEngineNode*`。
对应 .cppnodeparamviewitem.cpp)内 NodeInput 构造/比较改 AppNodeInput。
- `nodeparamview.h``QVector<Node*>` ×4 → `QVector<OakEngineNode*>`
`QHash<Node*, Node*>`paste 映射、generate_existing_paste_map)→
`QHash<OakEngineNode*, OakEngineNode*>`
`QHash<Node*, QPair<int64_t,int64_t>> context_subs_` → key 换类型;
slots `input_check_box_changed / group_input_passthrough_added/removed`
的 `const NodeInput &` → `const AppNodeInput &`
`get_snap_ignore_keyframes()` 返回 `std::vector<NodeKeyframe*>*` →
`std::vector<OakEngineKeyframe*>*`NodeKeyframe 经 node.h 传递而来,
随 include 删除必须一并处理;调用点 grep `get_snap_ignore_keyframes`
逐一适配,keyframe 属性访问走 P2 的 C ABI)。
### 4.7 步骤 7F 组(viewerdisplay.h
按 2.8 做最小改动。
### 4.8 步骤 8:验证 + 提交
```bash
# 1. 直接 include 清零
grep -rn '#include "node/node.h"\|#include "node/param.h"' app/
# 期望:无输出
# 2. engine 符号泄漏检查(app 对象中不应出现 olive::Node/NodeInput 等未定义符号)
# 对象编译全部通过后即视为通过
# 3. app 对象编译(命令见第 0 节)
# 4. engine 未动:git diff --stat -- engine/ 应为空
# 5. 提交(单独一个 commit
git add app/ docs/
git commit -m "R8 phase 3: replace node/node.h + node/param.h in app/ with C ABI and app-local value types"
```
---
## 5. 明令禁止
- 不要改 engine/ 下任何文件(包括 oakengine/node.h)。
- 不要把 `NodeInput` 等 engine 类型换成 `using` 别名指回 engine 头。
- 不要在本阶段处理 P4-P9 的范围(viewerdisplay.h 的 gizmo/tracklist/
colormanager include、nodeviewcontext.cpp 的 block.h/track.h/project.h、
hashstreamapp.cpp 的 track.h)。
- 不要"顺手"重构无关代码;唯一允许的顺手修复是 2.7 的 UserRole bug。
- 发现映射表覆盖不到的 engine 成员调用时,停下来报告,不要自行在
engine 加 C ABI 函数。
## 6. 已知遗留(写进提交说明/计划文档状态)
- `app/widget/viewer/viewerdisplay.h/.cpp` 仍有 gizmo/text.h、tracklist.h、
colormanager.h 直接 includeP5/P6)。
- `app/widget/nodeview/nodeviewcontext.cpp` 有 block.h、track.h、project.h、
sequence.h 直接 includeP3/P4 后续批)。
- `app/common/hashstreamapp.cpp` 保留 track.hP6)。