// Oak Video Editor - 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 . //! 参数体系:12 种参数实例 + param ↔ oaknode 桥。 //! //! 对应 C++ 的 `ParamInstance`/`OliveParamInstance`。桥的语义 //! (M9 已定):节点输入值变化 → 写回 OFX 参数;OFX 参数被插件 //! 改动 → 经 oaknode 回写节点(undoable 经 //! `oakundo::undocommand::UndoCommand`)。 //! //! 句柄约定([`crate::suites::tag`]):`ParamDef`/`ParamInstance` //! `#[repr(C)]` 且 `props` 在偏移 0;元素装箱(`Vec>`)保证 //! Vec 重分配不移动对象、句柄不悬垂。 //! //! 节点值布局:随 [`crate::node`] 冻结(`include/node/node.h` 的 //! `oaknode_value` POD);单库化后 oaknode 的身份注册表随其 ffi //! 删除,回写路径是保留失败路径的本地桩(见 [`crate::node`])。 use std::ffi::CString; use crate::property::{PropertySet, Value}; // ---- OFX 参数类型字符串(ofxParam.h)---- /// kOfxParamTypeInteger。 pub const TYPE_INTEGER: &str = "OfxParamTypeInteger"; /// kOfxParamTypeInteger2D。 pub const TYPE_INTEGER2D: &str = "OfxParamTypeInteger2D"; /// kOfxParamTypeInteger3D。 pub const TYPE_INTEGER3D: &str = "OfxParamTypeInteger3D"; /// kOfxParamTypeDouble。 pub const TYPE_DOUBLE: &str = "OfxParamTypeDouble"; /// kOfxParamTypeDouble2D。 pub const TYPE_DOUBLE2D: &str = "OfxParamTypeDouble2D"; /// kOfxParamTypeDouble3D。 pub const TYPE_DOUBLE3D: &str = "OfxParamTypeDouble3D"; /// kOfxParamTypeBoolean。 pub const TYPE_BOOLEAN: &str = "OfxParamTypeBoolean"; /// kOfxParamTypeChoice。 pub const TYPE_CHOICE: &str = "OfxParamTypeChoice"; /// kOfxParamTypeString。 pub const TYPE_STRING: &str = "OfxParamTypeString"; /// kOfxParamTypeStrChoice。 pub const TYPE_STRCHOICE: &str = "OfxParamTypeStrChoice"; /// kOfxParamTypeRGB。 pub const TYPE_RGB: &str = "OfxParamTypeRGB"; /// kOfxParamTypeRGBA。 pub const TYPE_RGBA: &str = "OfxParamTypeRGBA"; /// kOfxParamTypeBytes。 pub const TYPE_BYTES: &str = "OfxParamTypeBytes"; /// kOfxParamTypeCustom。 pub const TYPE_CUSTOM: &str = "OfxParamTypeCustom"; /// kOfxParamTypePushButton。 pub const TYPE_PUSHBUTTON: &str = "OfxParamTypePushButton"; /// kOfxParamTypeGroup。 pub const TYPE_GROUP: &str = "OfxParamTypeGroup"; /// kOfxParamTypePage。 pub const TYPE_PAGE: &str = "OfxParamTypePage"; /// kOfxParamTypeParametric(第 1 期不支持,paramDefine 拒绝)。 pub const TYPE_PARAMETRIC: &str = "OfxParamTypeParametric"; // ---- 属性名(ofxParam.h / ofxCore.h)---- /// kOfxParamPropSecret。 pub(crate) const P_SECRET: &str = "OfxParamPropSecret"; /// kOfxParamPropHint。 pub(crate) const P_HINT: &str = "OfxParamPropHint"; /// kOfxParamPropScriptName。 pub(crate) const P_SCRIPT_NAME: &str = "OfxParamPropScriptName"; /// kOfxParamPropParent。 pub(crate) const P_PARENT: &str = "OfxParamPropParent"; /// kOfxParamPropEnabled。 pub(crate) const P_ENABLED: &str = "OfxParamPropEnabled"; /// kOfxParamPropDataPtr。 pub(crate) const P_DATA_PTR: &str = "OfxParamPropDataPtr"; /// kOfxParamPropType。 pub(crate) const P_TYPE: &str = "OfxParamPropType"; /// kOfxParamPropIsAnimating。 pub(crate) const P_IS_ANIMATING: &str = "OfxParamPropIsAnimating"; /// kOfxParamPropIsAutoKeying。 pub(crate) const P_IS_AUTO_KEYING: &str = "OfxParamPropIsAutoKeying"; /// kOfxParamPropPersistant。 pub(crate) const P_PERSISTANT: &str = "OfxParamPropPersistant"; /// kOfxParamPropEvaluateOnChange。 pub(crate) const P_EVALUATE_ON_CHANGE: &str = "OfxParamPropEvaluateOnChange"; /// kOfxParamPropCanUndo。 pub(crate) const P_CAN_UNDO: &str = "OfxParamPropCanUndo"; /// kOfxParamPropCacheInvalidation。 pub(crate) const P_CACHE_INVALIDATION: &str = "OfxParamPropCacheInvalidation"; /// kOfxParamInvalidateValueChange(P_CACHE_INVALIDATION 的默认值)。 pub(crate) const V_INVALIDATE_VALUE_CHANGE: &str = "OfxParamInvalidateValueChange"; /// kOfxParamPropAnimates。 pub(crate) const P_ANIMATES: &str = "OfxParamPropAnimates"; /// kOfxParamPropDefault。 pub(crate) const P_DEFAULT: &str = "OfxParamPropDefault"; /// kOfxParamPropDisplayMin。 pub(crate) const P_DISPLAY_MIN: &str = "OfxParamPropDisplayMin"; /// kOfxParamPropDisplayMax。 pub(crate) const P_DISPLAY_MAX: &str = "OfxParamPropDisplayMax"; /// kOfxParamPropMin。 pub(crate) const P_MIN: &str = "OfxParamPropMin"; /// kOfxParamPropMax。 pub(crate) const P_MAX: &str = "OfxParamPropMax"; /// kOfxParamPropIncrement。 pub(crate) const P_INCREMENT: &str = "OfxParamPropIncrement"; /// kOfxParamPropDigits。 pub(crate) const P_DIGITS: &str = "OfxParamPropDigits"; /// kOfxParamPropDoubleType。 pub(crate) const P_DOUBLE_TYPE: &str = "OfxParamPropDoubleType"; /// kOfxParamDoubleTypePlain。 pub(crate) const V_DOUBLE_TYPE_PLAIN: &str = "OfxParamDoubleTypePlain"; /// kOfxParamPropDefaultCoordinateSystem。 pub(crate) const P_DEFAULT_COORD_SYS: &str = "OfxParamPropDefaultCoordinateSystem"; /// kOfxParamCoordinatesCanonical。 pub(crate) const V_COORD_CANONICAL: &str = "OfxParamCoordinatesCanonical"; /// kOfxParamPropShowTimeMarker。 pub(crate) const P_SHOW_TIME_MARKER: &str = "OfxParamPropShowTimeMarker"; /// kOfxParamPropDimensionLabel。 pub(crate) const P_DIMENSION_LABEL: &str = "OfxParamPropDimensionLabel"; /// kOfxParamPropStringMode。 pub(crate) const P_STRING_MODE: &str = "OfxParamPropStringMode"; /// kOfxParamStringIsSingleLine。 pub(crate) const V_STRING_SINGLE_LINE: &str = "OfxParamStringIsSingleLine"; /// kOfxParamPropStringFilePathExists。 pub(crate) const P_STRING_FILE_EXISTS: &str = "OfxParamPropStringFilePathExists"; /// kOfxParamPropChoiceOption。 pub(crate) const P_CHOICE_OPTION: &str = "OfxParamPropChoiceOption"; /// kOfxParamPropCustomInterpCallbackV1。 pub(crate) const P_CUSTOM_INTERP: &str = "OfxParamPropCustomCallbackV1"; /// kOfxParamPropPageChild。 pub(crate) const P_PAGE_CHILD: &str = "OfxParamPropPageChild"; /// kOfxParamPropGroupOpen。 pub(crate) const P_GROUP_OPEN: &str = "OfxParamPropGroupOpen"; /// kOfxParamPropInteractV1。 pub(crate) const P_INTERACT_V1: &str = "OfxParamPropInteractV1"; /// kOfxParamPropInteractSize。 pub(crate) const P_INTERACT_SIZE: &str = "OfxParamPropInteractSize"; /// kOfxParamPropInteractSizeAspect。 pub(crate) const P_INTERACT_ASPECT: &str = "OfxParamPropInteractSizeAspect"; /// kOfxParamPropInteractMinimumSize。 pub(crate) const P_INTERACT_MIN_SIZE: &str = "OfxParamPropInteractMinimumSize"; /// kOfxParamPropInteractPreferedSize。 pub(crate) const P_INTERACT_PREF_SIZE: &str = "OfxParamPropInteractPreferedSize"; /// kOfxPropType。 pub(crate) const PROP_TYPE: &str = "OfxPropType"; /// kOfxPropName。 pub(crate) const PROP_NAME: &str = "OfxPropName"; /// kOfxPropLabel。 pub(crate) const PROP_LABEL: &str = "OfxPropLabel"; /// kOfxPropShortLabel。 pub(crate) const PROP_SHORT_LABEL: &str = "OfxPropShortLabel"; /// kOfxPropLongLabel。 pub(crate) const PROP_LONG_LABEL: &str = "OfxPropLongLabel"; /// kOfxPropIcon。 pub(crate) const PROP_ICON: &str = "OfxPropIcon"; /// kOfxTypeParameter。 pub(crate) const TYPE_PARAMETER: &str = "OfxTypeParameter"; fn cs(s: &str) -> CString { // 静态 ASCII 常量,无内嵌 NUL。 CString::new(s).unwrap() } /// 参数值类别(param shim 的变长参数分发;与 /// cbits/ofx_param_shim.c 的 KIND_* 枚举逐字对应)。 #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ParamKind { /// Integer。 Int = 1, /// Integer2D。 Int2 = 2, /// Integer3D。 Int3 = 3, /// Double。 Double = 4, /// Double2D。 Double2 = 5, /// Double3D。 Double3 = 6, /// Boolean。 Bool = 7, /// Choice。 Choice = 8, /// RGB。 Rgb = 9, /// RGBA。 Rgba = 10, /// String。 Str = 11, /// StrChoice。 StrChoice = 12, } /// OFX 类型字符串 → 值类别 + 维度(HS: ofxhParam.cpp `findType`)。 /// 无值类(PushButton/Group/Page/Unknown)返回 None。 pub(crate) fn kind_of_type(ofx_type: &str) -> Option<(ParamKind, usize)> { match ofx_type { TYPE_INTEGER => Some((ParamKind::Int, 1)), TYPE_INTEGER2D => Some((ParamKind::Int2, 2)), TYPE_INTEGER3D => Some((ParamKind::Int3, 3)), TYPE_DOUBLE => Some((ParamKind::Double, 1)), TYPE_DOUBLE2D => Some((ParamKind::Double2, 2)), TYPE_DOUBLE3D => Some((ParamKind::Double3, 3)), TYPE_BOOLEAN => Some((ParamKind::Bool, 1)), TYPE_CHOICE => Some((ParamKind::Choice, 1)), TYPE_STRING => Some((ParamKind::Str, 1)), TYPE_STRCHOICE => Some((ParamKind::StrChoice, 1)), TYPE_RGB => Some((ParamKind::Rgb, 3)), TYPE_RGBA => Some((ParamKind::Rgba, 4)), _ => None, } } /// 数值类参数(Min/Max/DisplayMin/Max 表格适用;HS /// `addNumericParamProps` 的 isDoubleParam || isIntParam || /// isColourParam)。 fn is_numeric_type(ofx_type: &str) -> bool { matches!( ofx_type, TYPE_INTEGER | TYPE_INTEGER2D | TYPE_INTEGER3D | TYPE_DOUBLE | TYPE_DOUBLE2D | TYPE_DOUBLE3D | TYPE_RGB | TYPE_RGBA ) } /// 该类型的默认 ParamValue(describe 期基线;HS 的属性默认在 props /// 的 kOfxParamPropDefault,值语义等价)。 fn type_default(ofx_type: &str) -> ParamValue { match kind_of_type(ofx_type) { Some((ParamKind::Int, d)) | Some((ParamKind::Int2, d)) | Some((ParamKind::Int3, d)) => { ParamValue::Int([0; 3], d) } Some((ParamKind::Double, d)) | Some((ParamKind::Double2, d)) | Some((ParamKind::Double3, d)) => ParamValue::Double([0.0; 3], d), Some((ParamKind::Rgb, d)) | Some((ParamKind::Rgba, d)) => ParamValue::Color([0.0; 4], d), Some((ParamKind::Bool, _)) => ParamValue::Bool(false), Some((ParamKind::Choice, _)) => ParamValue::Choice(0), Some((ParamKind::Str, _)) => ParamValue::String(cs("")), Some((ParamKind::StrChoice, _)) => ParamValue::StrChoice(cs("")), None => match ofx_type { TYPE_BYTES | TYPE_CUSTOM => ParamValue::Bytes(Vec::new()), TYPE_PUSHBUTTON => ParamValue::PushButton, TYPE_GROUP | TYPE_PAGE => ParamValue::Container, _ => ParamValue::Container, // 未知类型占位(paramDefine 已拒绝) }, } } /// 数值属性表的值(Min/Max/Display 族;HS ofxhParam.cpp:420-449)。 /// `is_max` 选 ±f64::MAX / ±i32::MAX;colour 参数恒 0/1。 fn numeric_value(ofx_type: &str, kind: ParamKind, dim: usize, is_max: bool) -> Vec { if ofx_type == TYPE_RGB || ofx_type == TYPE_RGBA { // colour:display 范围 0..1,min/max 同。 let v = Value::Double(if is_max { 1.0 } else { 0.0 }); return vec![v; dim]; } match kind { ParamKind::Double | ParamKind::Double2 | ParamKind::Double3 => { let v = Value::Double(if is_max { f64::MAX } else { f64::MIN }); vec![v; dim] } _ => { let v = Value::Int(if is_max { i32::MAX } else { i32::MIN }); vec![v; dim] } } } /// 参数值(与 OFX 参数类型一一对应)。 #[derive(Clone, Debug, PartialEq)] pub enum ParamValue { /// kOfxParamTypeInteger / Integer2D / Integer3D。 Int([i32; 3], usize), /// kOfxParamTypeDouble / Double2D / Double3D。 Double([f64; 3], usize), /// kOfxParamTypeBoolean。 Bool(bool), /// kOfxParamTypeChoice(选项索引)。 Choice(i32), /// kOfxParamTypeString。 String(std::ffi::CString), /// kOfxParamTypeRGB / RGBA。 Color([f64; 4], usize), /// kOfxParamTypeStrChoice。 StrChoice(std::ffi::CString), /// kOfxParamTypeBytes / Custom(不透明字节串)。 Bytes(Vec), /// kOfxParamTypePushButton(无值,仅触发)。 PushButton, /// kOfxParamTypeGroup / Page(容器,无值)。 Container, } /// 参数定义(describe 产物,见 [`crate::descriptor::EffectDescriptor`])。 /// `#[repr(C)]` + props 在偏移 0(句柄约定,见 [`crate::suites::tag`])。 #[derive(Clone)] #[repr(C)] pub struct ParamDef { /// 定义属性(label/hint/parent/coordinate-system/secret/ /// display min-max/choice 选项与排序等;describe 期预置 /// HostSupport 同款表格)。 pub props: PropertySet, /// 参数名(OFX 标识)。 pub name: String, /// OFX 类型字符串(kOfxParamType*)。 pub ofx_type: String, /// 默认值(describe 后读取)。 pub default: ParamValue, } impl ParamDef { /// 按 OFX 类型构建定义(镜像 HS ofxhParam.cpp:224-352: /// universalProps + addStandardParamProps 的分类型属性表)。 pub(crate) fn new(name: &str, ofx_type: &str) -> Self { let mut props = PropertySet::new(); let uname = name.to_string(); let utype = ofx_type.to_string(); // universalProps(HS ofxhParam.cpp:229-246)。 props.set_one(PROP_TYPE, Value::String(cs(TYPE_PARAMETER))); props.set_one(P_SECRET, Value::Int(0)); props.set_one(P_HINT, Value::String(cs(""))); props.set_one(P_SCRIPT_NAME, Value::String(cs(&uname))); props.set_one(P_PARENT, Value::String(cs(""))); props.set_one(P_ENABLED, Value::Int(1)); props.set_one(P_DATA_PTR, Value::Pointer(std::ptr::null_mut())); props.set_one(P_TYPE, Value::String(cs(&utype))); props.set_one(PROP_NAME, Value::String(cs(&uname))); props.set_one(PROP_LABEL, Value::String(cs(&uname))); props.set_one(PROP_SHORT_LABEL, Value::String(cs(&uname))); props.set_one(PROP_LONG_LABEL, Value::String(cs(&uname))); props.define( PROP_ICON, vec![Value::String(cs("")), Value::String(cs(""))], ); // 值类参数(HS addValueParamProps,ofxhParam.cpp:352-380)。 if let Some((kind, dim)) = kind_of_type(ofx_type) { props.set_one(P_IS_ANIMATING, Value::Int(0)); props.set_one(P_IS_AUTO_KEYING, Value::Int(0)); props.set_one(P_PERSISTANT, Value::Int(1)); props.set_one(P_EVALUATE_ON_CHANGE, Value::Int(1)); props.set_one(P_CAN_UNDO, Value::Int(1)); props.set_one( P_CACHE_INVALIDATION, Value::String(cs(V_INVALIDATE_VALUE_CHANGE)), ); // 可动画性(HS ofxhParam.cpp:367-372:custom/string/ // boolean/choice 默认不可动画)。 let animates = ofx_type != TYPE_CUSTOM && ofx_type != TYPE_STRING && ofx_type != TYPE_BOOLEAN && ofx_type != TYPE_CHOICE; props.set_one(P_ANIMATES, Value::Int(animates as i32)); props.define(P_DEFAULT, default_values(kind, dim)); // 数值类(HS addNumericParamProps,ofxhParam.cpp:391-455)。 if is_numeric_type(ofx_type) { props.define(P_DISPLAY_MIN, numeric_value(ofx_type, kind, dim, false)); props.define(P_DISPLAY_MAX, numeric_value(ofx_type, kind, dim, true)); props.define(P_MIN, numeric_value(ofx_type, kind, dim, false)); props.define(P_MAX, numeric_value(ofx_type, kind, dim, true)); if matches!( kind, ParamKind::Double | ParamKind::Double2 | ParamKind::Double3 ) { props.set_one(P_INCREMENT, Value::Double(1.0)); props.set_one(P_DIGITS, Value::Int(2)); } if ofx_type == TYPE_DOUBLE || ofx_type == TYPE_DOUBLE2D || ofx_type == TYPE_DOUBLE3D { props.set_one(P_DOUBLE_TYPE, Value::String(cs(V_DOUBLE_TYPE_PLAIN))); props.set_one(P_DEFAULT_COORD_SYS, Value::String(cs(V_COORD_CANONICAL))); if dim == 1 { props.set_one(P_SHOW_TIME_MARKER, Value::Int(0)); } } if dim == 2 || dim == 3 { let labels: Vec = ["x", "y", "z"][..dim] .iter() .map(|l| Value::String(cs(l))) .collect(); props.define(P_DIMENSION_LABEL, labels); } } } // 分类型附加(HS addStandardParamProps,ofxhParam.cpp:248-301)。 match ofx_type { TYPE_STRING => { props.set_one(P_STRING_MODE, Value::String(cs(V_STRING_SINGLE_LINE))); props.set_one(P_STRING_FILE_EXISTS, Value::Int(1)); } TYPE_CHOICE => { // 维度 0:选项数由插件 SetN 决定。 props.define(P_CHOICE_OPTION, vec![]); } TYPE_CUSTOM => { props.set_one(P_CUSTOM_INTERP, Value::Pointer(std::ptr::null_mut())); } TYPE_PAGE => { props.define(P_PAGE_CHILD, vec![]); } TYPE_GROUP => { props.set_one(P_GROUP_OPEN, Value::Int(1)); } _ => {} } // 交互属性(HS addInteractParamProps,ofxhParam.cpp:305-317; // group/page 除外)。 if ofx_type != TYPE_GROUP && ofx_type != TYPE_PAGE { props.set_one(P_INTERACT_V1, Value::Pointer(std::ptr::null_mut())); props.define( P_INTERACT_SIZE, vec![Value::Double(0.0), Value::Double(0.0)], ); props.set_one(P_INTERACT_ASPECT, Value::Double(1.0)); props.define( P_INTERACT_MIN_SIZE, vec![Value::Double(10.0), Value::Double(10.0)], ); props.define(P_INTERACT_PREF_SIZE, vec![Value::Int(10), Value::Int(10)]); } Self { props, name: uname, ofx_type: utype, default: type_default(ofx_type), } } /// 值类别(变长参数分发用;无值类返回 None)。 pub(crate) fn kind(&self) -> Option { kind_of_type(&self.ofx_type).map(|(k, _)| k) } } /// kOfxParamPropDefault 的属性值(HS ofxhParam.cpp:376-377)。 fn default_values(kind: ParamKind, dim: usize) -> Vec { match kind { ParamKind::Int | ParamKind::Int2 | ParamKind::Int3 | ParamKind::Bool | ParamKind::Choice => { vec![Value::Int(0); dim.max(1)] } ParamKind::Double | ParamKind::Double2 | ParamKind::Double3 | ParamKind::Rgb | ParamKind::Rgba => vec![Value::Double(0.0); dim.max(1)], ParamKind::Str | ParamKind::StrChoice => vec![Value::String(cs(""))], } } /// 参数实例(createInstance 后由 [`ParamDef`] 实例化)。 /// `#[repr(C)]` + props 在偏移 0(句柄约定)。 #[repr(C)] pub struct ParamInstance { /// 实例级属性(当前值镜像等)。 pub props: PropertySet, /// 对应定义。 pub def: ParamDef, /// 当前值(节点桥与 OFX 侧的同步点)。 value: std::sync::Mutex, } impl ParamInstance { /// 从定义实例化(createInstance 路径调用)。实例 props 是定义 /// props 的深拷贝(HS: SetInstance::makeParam 的 descriptor 属性 /// 复制语义——插件在实例期读 label/min/max 等)。 pub fn from_def(def: ParamDef) -> Self { let value = def.default.clone(); let props = def.props.clone(); Self { props, def, value: std::sync::Mutex::new(value), } } /// 读当前值。 pub fn get(&self) -> ParamValue { self.value.lock().unwrap_or_else(|e| e.into_inner()).clone() } /// 写当前值(OFX 语义:paramSetValue action 语义,不打节点桥)。 pub fn set_ofx(&self, value: ParamValue) { *self.value.lock().unwrap_or_else(|e| e.into_inner()) = value; } /// 字符串值的内驻指针(param suite 的 get_string 用;指向实例 /// 存储,下次 set 前有效——克隆的 CString 指针会悬垂,不能给)。 pub(crate) fn string_ptr(&self) -> Option<*const std::ffi::c_char> { let v = self.value.lock().unwrap_or_else(|e| e.into_inner()); match &*v { ParamValue::String(s) | ParamValue::StrChoice(s) => Some(s.as_ptr()), _ => None, } } /// 从 oaknode 输入值写入(节点→插件方向)。类型映射表逐行对照 /// C++ 的 `valueconvert`/`paraminstance.h`(`node_get` 的镜像): /// INT→Integer、FLOAT→Double、BOOL→Boolean、COMBO→Choice、 /// COLOR→RGB/RGBA、VEC2/VEC3→Double(2D/3D)/Integer(2D/3D)。 /// 维度不齐按 OFX 语义截断/补零(缺失元素按 0)。字符串族 /// (OAKNODE_VALUE_STRING)的 POD 不携带数据——此路径不改值 /// (走 facade 的字符串 API,见 `include/plugin/instance.h`)。 /// 类型不匹配 → 忽略(保持现值;C++ `node_get` 失败时参数不回写)。 pub fn set_from_node(&self, node_value: &crate::node::Value) { if let Some(pv) = param_value_from_node(node_value, &self.def.ofx_type) { self.set_ofx(pv); } } } /// 节点值 → ParamValue(按参数类型解释;字符串族走专用路径)。 /// 镜像 `set_from_node` 的映射表;render 驱动的参数覆盖 /// (apply_param_overrides)复用同一转换。 pub(crate) fn param_value_from_node( v: &crate::node::Value, ofx_type: &str, ) -> Option { use crate::node::node_value_type as T; match ofx_type { TYPE_DOUBLE => (v.r#type == T::FLOAT).then(|| ParamValue::Double([v.f[0], 0.0, 0.0], 1)), TYPE_DOUBLE2D => (v.r#type == T::VEC2).then(|| ParamValue::Double([v.f[0], v.f[1], 0.0], 2)), TYPE_DOUBLE3D => { (v.r#type == T::VEC3).then(|| ParamValue::Double([v.f[0], v.f[1], v.f[2]], 3)) } TYPE_INTEGER => (v.r#type == T::INT).then(|| ParamValue::Int([v.num as i32, 0, 0], 1)), TYPE_INTEGER2D | TYPE_INTEGER3D => { let dim = if ofx_type == TYPE_INTEGER2D { 2 } else { 3 }; Some(ParamValue::Int( [v.f[0] as i32, v.f[1] as i32, v.f[2] as i32], dim, )) } TYPE_BOOLEAN => (v.r#type == T::BOOL).then(|| ParamValue::Bool(v.num != 0)), TYPE_CHOICE => (v.r#type == T::COMBO).then(|| ParamValue::Choice(v.num as i32)), TYPE_RGB => { (v.r#type == T::COLOR).then(|| ParamValue::Color([v.f[0], v.f[1], v.f[2], 0.0], 3)) } TYPE_RGBA => (v.r#type == T::COLOR) .then(|| ParamValue::Color([v.f[0], v.f[1], v.f[2], v.f[3]], 4)), _ => None, // 字符串/无值类:POD 无数据,不改值 } } /// ParamValue → [`crate::node::Value`](插件→节点方向; /// 字符串族经 [`crate::node::set_input_string_undoable`])。 /// 镜像 C++ `paraminstance.h` 的 `value_int`/`value_double`/ /// `value_vec`/`value_color` 构造:RGB 颜色补 alpha=1(C++ /// `RGBInstance::set` 的 `value_color(r,g,b,1.0)`)。无值类与 Bytes /// 无节点对应 → None。 pub(crate) fn to_node_value(v: &ParamValue) -> Option { use crate::node::node_value_type as T; let mut out = crate::node::Value::default(); match v { ParamValue::Double(d, 1) => { out.r#type = T::FLOAT; out.f = [d[0], 0.0, 0.0, 0.0]; } ParamValue::Double(d, 2) => { out.r#type = T::VEC2; out.f = [d[0], d[1], 0.0, 0.0]; } ParamValue::Double(d, 3) => { out.r#type = T::VEC3; out.f = [d[0], d[1], d[2], 0.0]; } ParamValue::Double(d, _) => { out.r#type = T::FLOAT; out.f = [d[0], 0.0, 0.0, 0.0]; } ParamValue::Int(a, 1) => { out.r#type = T::INT; out.num = a[0] as i64; } ParamValue::Int(a, 2) => { out.r#type = T::VEC2; out.f = [a[0] as f64, a[1] as f64, 0.0, 0.0]; } ParamValue::Int(a, 3) => { out.r#type = T::VEC3; out.f = [a[0] as f64, a[1] as f64, a[2] as f64, 0.0]; } ParamValue::Bool(b) => { out.r#type = T::BOOL; out.num = *b as i64; } ParamValue::Choice(c) => { out.r#type = T::COMBO; out.num = *c as i64; } ParamValue::Color(c, 3) => { out.r#type = T::COLOR; out.f = [c[0], c[1], c[2], 1.0]; } ParamValue::Color(c, 4) => { out.r#type = T::COLOR; out.f = *c; } _ => return None, } Some(out) } /// 参数实例集(实例级)。 pub struct ParamSetInstance { /// 全部参数(定义顺序稳定;Box 保证句柄地址稳定)。 pub params: Vec>, } impl ParamSetInstance { /// 按名查找。 pub fn find(&self, name: &str) -> Option<&ParamInstance> { self.params .iter() .find(|p| p.def.name == name) .map(|b| b.as_ref()) } /// 快照(内省 C ABI 与快照测试用)。 /// /// 声明原为 `Vec<(&str, &ParamValue)>`:值在 Mutex 内,无法返回 /// 活引用——改为拥有型 `(String, ParamValue)`(内省语义不变)。 pub fn snapshot(&self) -> Vec<(String, ParamValue)> { self.params .iter() .map(|p| (p.def.name.clone(), p.get())) .collect() } } /// 插件 → 节点方向的回写入口(instanceChanged action 触发)。 /// 经 [`crate::node`] 定位绑定节点(身份注册表),再经 /// `oakundo::undocommand::UndoCommand` 包成 undoable 修改。未绑定 /// 节点或身份查无时 no-op(单库化后身份注册表重建为 /// [`crate::node::register_node`]/[`crate::node::node_from_identity`]; /// facade 装配期登记,project 释放后弱条目自然失效)。 /// /// 语义对照 C++ `paraminstance.h` 的 `set()` 路径(`detail::node_set`/ /// `node_set_at`): /// - 只回写 [`ChangeReason::PluginEdited`](插件自改)。UserEdited/ /// TimeChanged 是宿主侧变更,值已由 [`ParamInstance::set_from_node`] /// 同步,不重复写回; /// - 字符串族经 [`crate::node::set_input_string_undoable`] /// (POD 不携带字符串数据); /// - 无值类(PushButton/Group/Page)与 Bytes 无节点对应 → no-op; /// - 编辑事务内([`crate::instance::Instance::in_edit`])并入 multi /// 命令,否则单命令立即 redo 生效(C++ `submit_undo_command`)。 pub(crate) fn notify_instance_changed( instance: &crate::instance::Instance, param_name: &str, reason: ChangeReason, ) { use crate::node; use std::sync::atomic::Ordering; if !matches!(reason, ChangeReason::PluginEdited) { return; } // 未绑定节点 → no-op(C++:!node_.ctx 时 set 只写本地值)。 let node_id = instance.node_identity.load(Ordering::Relaxed); if node_id == 0 { return; } // 身份查无(未登记 / project 已释放)→ no-op。 let Some(node_ref) = node::node_from_identity(node_id) else { return; }; let Some(param) = instance.params.find(param_name) else { return; }; let value = param.get(); let label = format!("Change {param_name}"); match to_node_value(&value) { Some(nv) => { if let Ok(cmd) = node::set_input_undoable(&node_ref, param_name, &nv) { instance.submit_undo_command(cmd, &label); } } None => match &value { ParamValue::String(s) | ParamValue::StrChoice(s) => { let Ok(s) = s.to_str() else { return; }; if let Ok(cmd) = node::set_input_string_undoable(&node_ref, param_name, s) { instance.submit_undo_command(cmd, &label); } } _ => {} // 无值类 / Bytes:无节点对应 }, } } /// instanceChanged 的原因(OFX kOfxChange*)。 #[derive(Clone, Copy, Debug)] pub enum ChangeReason { /// 用户编辑。 UserEdited, /// 插件自改。 PluginEdited, /// 时间变化。 TimeChanged, } #[cfg(test)] mod tests { use super::*; use crate::node::node_value_type as T; fn param(ofx_type: &str) -> ParamInstance { ParamInstance::from_def(ParamDef::new("p", ofx_type)) } /// ParamValue → oaknode Value 的类型映射表(镜像 C++ /// paraminstance.h 的 value_* 构造)。 #[test] fn to_node_value_mapping() { use crate::node::Value; let c = |t: i32| Value { r#type: t, num: 0, den: 0, f: [0.0; 4], }; // Double 族。 let v = to_node_value(&ParamValue::Double([1.5, 0.0, 0.0], 1)).unwrap(); assert_eq!(v, Value::float(1.5)); let v = to_node_value(&ParamValue::Double([1.0, 2.0, 0.0], 2)).unwrap(); assert_eq!(v, Value::vec(&[1.0, 2.0])); let v = to_node_value(&ParamValue::Double([1.0, 2.0, 3.0], 3)).unwrap(); assert_eq!(v, Value::vec(&[1.0, 2.0, 3.0])); // Int 族(2D/3D 升格为 VEC2/VEC3 浮点载荷)。 let v = to_node_value(&ParamValue::Int([7, 0, 0], 1)).unwrap(); assert_eq!(v, Value::int(7)); let v = to_node_value(&ParamValue::Int([1, 2, 0], 2)).unwrap(); assert_eq!(v, Value::vec(&[1.0, 2.0])); // Bool / Choice。 assert_eq!( to_node_value(&ParamValue::Bool(true)).unwrap(), Value::bool_(true) ); assert_eq!( to_node_value(&ParamValue::Choice(3)).unwrap(), Value::combo(3) ); // Color:RGB 补 alpha=1(C++ oracle),RGBA 全透传。 let v = to_node_value(&ParamValue::Color([0.1, 0.2, 0.3, 0.0], 3)).unwrap(); assert_eq!(v, Value::color(0.1, 0.2, 0.3, 1.0)); let v = to_node_value(&ParamValue::Color([0.1, 0.2, 0.3, 0.9], 4)).unwrap(); assert_eq!(v, Value::color(0.1, 0.2, 0.3, 0.9)); // 无值类 / Bytes → None(无节点对应)。 assert_eq!(to_node_value(&ParamValue::PushButton), None); assert_eq!(to_node_value(&ParamValue::Container), None); assert_eq!(to_node_value(&ParamValue::Bytes(vec![1])), None); // 字符串族走专用桥(POD 路径 None)。 assert_eq!(to_node_value(&ParamValue::String(cs(""))), None); let _ = c(T::NONE); // 哨兵:类型常量可达 } /// type_default 的无值类/字节族基线(paramDefine 拒绝前的兜底)。 #[test] fn type_default_edge_cases() { assert!(matches!(type_default(TYPE_BYTES), ParamValue::Bytes(_))); assert!(matches!(type_default(TYPE_CUSTOM), ParamValue::Bytes(_))); assert!(matches!( type_default(TYPE_PUSHBUTTON), ParamValue::PushButton )); assert!(matches!(type_default(TYPE_GROUP), ParamValue::Container)); assert!(matches!(type_default(TYPE_PAGE), ParamValue::Container)); // 未知类型占位(paramDefine 已拒绝,此处兜底)。 assert!(matches!( type_default("OfxParamTypeBogus"), ParamValue::Container )); // 数值族的默认 0。 assert!(matches!( type_default(TYPE_INTEGER), ParamValue::Int([0, 0, 0], 1) )); assert!(matches!( type_default(TYPE_DOUBLE2D), ParamValue::Double([0.0, 0.0, 0.0], 2) )); assert!(matches!( type_default(TYPE_RGBA), ParamValue::Color([0.0, 0.0, 0.0, 0.0], 4) )); } /// set_from_node 的维度截断/补零与字符串静默路径。 #[test] fn set_from_node_dimension_rules() { // Integer2D 缺第三维补零;浮点截断。 let p = param(TYPE_INTEGER2D); p.set_from_node(&crate::node::Value::vec(&[1.9, 2.9])); assert_eq!(p.get(), ParamValue::Int([1, 2, 0], 2)); // StrChoice 参数遇 STRING 类型:POD 无数据 → 不改值。 let p = param(TYPE_STRCHOICE); p.set_from_node(&crate::node::Value::string()); assert!(matches!(p.get(), ParamValue::StrChoice(_))); // Bytes/Custom 参数:任意节点值都不改(无映射)。 let p = param(TYPE_CUSTOM); p.set_from_node(&crate::node::Value::float(3.0)); assert!(matches!(p.get(), ParamValue::Bytes(_))); } }