Files
oak-editor/docs/zh/plans/r8-p3-node-param-abi.md
T
Mike-Solar 66d761b4b7 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
2026-07-31 22:46:52 +08:00

26 KiB
Raw Blame History

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.hnamespace oak): Node/Project/Footage/Input/Keyframe/KeyframeTrackRef/InputPair
    • NodeCategory 枚举 + NodeConnection/ContextNodeItem 结构。 wrapper 只做转发;owned/borrowed 语义见文件头注释。
  • app/common/nodeinputhandle.hAppNodeInput/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::getPluginInstanceNode::get_input_propertyoakengine_node_array_insert_at/remove_atNode::brush()Node::has_gizmos、效果标志常量 nodeparamviewitem.cpp、nodeparamview.cpp、nodeviewitem.cpp

除原手册列出的文件外,本次一并完成了前半程未迁完的文件: nodeparamviewitem.h/.cppnodeparamview.h/.cppnodeparamviewcontext.cppnodeparamviewwidgetbridge.cppnodevaluetree.h/.cppcurvewidget.h/.cppcurveview.cpptimebasedwidget.cppnodeviewscene.cppnodewidget.hpanel/node/node.hpanel/param/param.*toolbar.cppmainwindow.cppmainwindowundo.h/.cpptimelinewidget/tool/import.cpp(仅一处)、 hashstreamapp.cpptests/gtest/widget_panels_model_test.cpp

落地后已知遗留(2026-07-29 Wave3 收尾后)

  • app/widget/viewer/viewerdisplay.happ/widget/colorwheel/*.cpp 仍直接 include node/node.hWave2 已清)。
  • app/widget/nodeparamview/nodeparamviewwidgetbridge.h 公开构造签名仍用 engine NodeInputWave3 已收敛到 oak::Input)。
  • app/ 全树唯一保留的违规 include:app/widget/timebased/timebasedwidget.h 的 engine node/output/viewer/viewer.hQPointer<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.cppapp/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 已在历史中。

    • 本机未安装 OpenTimelineIOapp/core.cppapp/dialog/otioproperties/otiopropertiesdialog.cpp 永远无法在本机编译, 与本次改动无关,验证时跳过这两个文件。

    • 验证编译用对象级构建(绕过 dylib 依赖与 OTIO 文件):

      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* 参数/成员、NodeValueRowP5 遗留见 4.8

Node::PositionNode::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 文件保持一致):

/***

  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_.nodeinput_.input()input_.inputinput_.element()input_.element
  • Q_DECLARE_METATYPE 使这两个类型可以进 Qt signal/slot 签名和 QVariant::fromValueitem 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 内定义 局部结构,不进公共头:

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::CategoryIDAppNodeCategory

preferencesappearancetab.cpp:72Node::k_category_countk_app_category_countfactorymenu.h:44 的参数类型与默认值 Node::k_category_unknownAppNodeCategory / k_app_category_unknownoakengine_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 拼装:
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 ABIoakengine_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_referencek_item_node_pointer 都定义为 Qt::UserRole + 1。改为 Qt::UserRole + 1 / Qt::UserRole + 2。 item data 中存的引用类型随之改为 QVariant::fromValue(AppNodeKeyframeTrackReference)

2.8 viewerdisplay.h 只做最小改动

该头还有 node/gizmo/text.hnode/output/track/tracklist.hnode/color/colormanager/colormanager.h 三处直接 include,属 P5/P6 范围, 本阶段不动。本阶段只做:

  • 删除 #include "node/node.h"
  • set_gizmos(Node*)generate_gizmo_transform(Node*, Node*, …)、 成员 Node *gizmos_ 改为 OakEngineNode*
  • NodeValueRowNodeGizmo*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 AppNodeCategory2.4
Node::Position 本地 NodeViewItemPosition2.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.cppinclude 换 common/nodeinputhandle.h :72 Node::k_category_countk_app_category_count
  • factorymenu.hinclude 换 common/nodeinputhandle.h:44 参数类型 Node::CategoryIDAppNodeCategory,默认值 → k_app_category_unknown :52 返回值 Node*OakEngineNode*。检查 factorymenu.cpp 及调用方 create_node_from_menu_action 的使用处)同步改类型。

4.3 步骤 3:B 组(纯指针/引用替换)

  • nodeparamviewitembase.hNode*OakEngineNode*;检查对应 .cpp 实现内部(如用 n->name()/n->GetLabel() 改 C ABI oakengine_node_get_name/oakengine_node_get_label)。
  • nodetableview.hQVector<Node*>QVector<OakEngineNode*> QMap<Node*, QTreeWidgetItem*>QMap<OakEngineNode*, …>。 该头用到的 Rational 来自 coreolive/core/...),确保 include core 头而不是靠 node.h 传递。
  • nodevaluetree.hset_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.hNode* output_OakEngineNode* NodeInput input_AppNodeInput input_output()/input() 返回类型 同步。构造函数参数同步。
  • nodeviewitem.h:按 2.2 加 NodeViewItemPositionNode*OakEngineNode*NodeInput get_input()AppNodeInput get_input() (返回 AppNodeInput(node_, input_, element_)); get_item_for_input(NodeInput)AppNodeInput
  • nodeviewcontext.hNode*OakEngineNode*(含 QMapQHash key、 context_subs_);const NodeInput & 参数 → const AppNodeInput &
  • 各 .cpp:桥接 lambda 按 2.3 组装 AppNodeInput;其它 Node 成员调用改 C ABI(绝大多数之前已迁,只剩类型改名)。

4.5 步骤 5D 组(TrackRef

  • nodetreeview.hinclude 换 common/nodeinputhandle.h(保留已有的 oakengine/node.h);NodeKeyframeTrackReferenceAppNodeKeyframeTrackReferencesignals、成员、QHash key 全部); 按 2.7 修 UserRole bugNode*OakEngineNode*
  • nodetreeview.cppref.input()ref.inputref.track()ref.trackitem 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.hNodeInput input_AppNodeInput slot keyframe_enable_changed(const NodeInput&, bool)AppNodeInput get_connected_input() 返回类型同步。
  • nodeparamviewarraywidget.h/.cppNode* 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.hQHash<NodeInput, InputUI>QHash<AppNodeInput, InputUI>NodeInputPairAppNodeInputPair signal input_checked_changed(const NodeInput&, bool) 与 slot edge_changed(OakEngineNode*, const NodeInput&)AppNodeInput get_element_y(NodeInput)AppNodeInputNode*OakEngineNode*。 对应 .cppnodeparamviewitem.cpp)内 NodeInput 构造/比较改 AppNodeInput。
  • nodeparamview.hQVector<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/removedconst 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:验证 + 提交

# 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)。