// 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 .
//! OfxParameterSuite v1:参数定义与读写。
//!
//! 语义对照 HS: ofxhParam.cpp:
//! - paramDefine:describe 期把参数定义挂到效果描述符(未定义类型 →
//! kOfxStatErrUnsupported,HS:1665-1710;parametric 类型放行,其
//! 曲线读写走 OfxParametricParameterSuite,见 suites/parametric.rs);
//! - paramGetHandle:按名查(未找到 → kOfxStatErrUnknown,HS:1758);
//! - paramGetValue/paramSetValue 等变长入口在 C shim
//! (cbits/ofx_param_shim.c):按 [`crate::param::ParamKind`] 解析
//! va_args 后转发到本模块的 `ofx_param_*_impl`;
//! - paramSetValue 成功后触发 instanceChanged(kOfxChangePluginEdited,
//! HS:1982-1994 的 `paramChangedByPlugin`),经
//! [`crate::param::notify_instance_changed`] 回写绑定节点;
//! - 第 1 期无动画:AtTime == 当前值;keys 恒 0;derivative/integral
//! → kOfxStatErrMissingHostFeature;editBegin/End 为编辑事务括号
//! (undo 分组:事务内回写并入一条 multi 命令,见
//! [`crate::instance::Instance::edit_begin`])。
use std::collections::HashMap;
use std::ffi::{c_char, c_double, c_int, c_void, CStr, CString};
use std::sync::Mutex;
use crate::descriptor::EffectDescriptor;
use crate::instance::Instance;
use crate::param::{ChangeReason, ParamDef, ParamInstance, ParamKind, ParamValue};
use crate::suites::status;
use crate::suites::tag;
/// 函数表布局(与 SDK `OfxParameterSuiteV1` 一致;字段序以
/// SDK 为准)。
#[repr(C)]
pub struct ParameterSuiteV1 {
/// paramDefine(describe 期间):定义参数。
pub param_define:
unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char, *mut *mut c_void) -> c_int,
/// paramGetHandle
pub param_get_handle: unsafe extern "C" fn(
*mut c_void,
*const c_char,
*mut *mut c_void,
*mut *mut c_void,
) -> c_int,
/// paramSetGetPropertySet
pub param_set_get_property_set: unsafe extern "C" fn(*mut c_void, *mut *mut c_void) -> c_int,
/// paramGetPropertySet
pub param_get_property_set: unsafe extern "C" fn(*mut c_void, *mut *mut c_void) -> c_int,
/// paramGetValue(变长出口参数按类型解释;入口在 C shim)
pub param_get_value: unsafe extern "C" fn(*mut c_void, ...) -> c_int,
/// paramGetValueAtTime
pub param_get_value_at_time: unsafe extern "C" fn(*mut c_void, c_double, ...) -> c_int,
/// paramGetDerivative
pub param_get_derivative: unsafe extern "C" fn(*mut c_void, c_double, ...) -> c_int,
/// paramGetIntegral
pub param_get_integral: unsafe extern "C" fn(*mut c_void, c_double, c_double, ...) -> c_int,
/// paramSetValue(触发 instanceChanged)
pub param_set_value: unsafe extern "C" fn(*mut c_void, ...) -> c_int,
/// paramSetValueAtTime
pub param_set_value_at_time: unsafe extern "C" fn(*mut c_void, c_double, ...) -> c_int,
/// paramGetNumKeys
pub param_get_num_keys: unsafe extern "C" fn(*mut c_void, *mut c_int) -> c_int,
/// paramGetKeyTime
pub param_get_key_time: unsafe extern "C" fn(*mut c_void, c_int, *mut c_double) -> c_int,
/// paramGetKeyIndex
pub param_get_key_index:
unsafe extern "C" fn(*mut c_void, c_double, c_int, *mut c_int) -> c_int,
/// paramDeleteKey
pub param_delete_key: unsafe extern "C" fn(*mut c_void, c_double) -> c_int,
/// paramDeleteAllKeys
pub param_delete_all_keys: unsafe extern "C" fn(*mut c_void) -> c_int,
/// paramCopy
pub param_copy:
unsafe extern "C" fn(*mut c_void, *mut c_void, c_double, c_double, *const c_void) -> c_int,
/// paramEditBegin / paramEditEnd(编辑事务括号)
pub param_edit_begin: unsafe extern "C" fn(*mut c_void) -> c_int,
/// paramEditEnd
pub param_edit_end: unsafe extern "C" fn(*mut c_void) -> c_int,
}
// ---- 句柄解析 -----------------------------------------------------------
/// param-set handle:describe 期是效果描述符,实例期是实例
/// (getParamSet 返回与 effect 相同的 handle,见 image_effect suite)。
enum ParamSetRef<'a> {
Descriptor(&'a mut EffectDescriptor),
Instance(&'a Instance),
}
/// param handle:describe 期是定义(值=默认值),实例期是实例。
enum ParamRef<'a> {
Def(&'a ParamDef),
Instance(&'a ParamInstance),
}
/// 解析 param-set(effect)句柄。空指针/标签不符 → BadHandle。
fn resolve_param_set(handle: *mut c_void) -> Result, c_int> {
if handle.is_null() {
return Err(status::ERR_BAD_HANDLE);
}
// 两种对象 props 均在偏移 0(句柄约定)。
unsafe {
match tag::kind(handle) {
tag::DESCRIPTOR => Ok(ParamSetRef::Descriptor(
&mut *(tag::strip(handle) as *mut EffectDescriptor),
)),
tag::INSTANCE => Ok(ParamSetRef::Instance(
&*(tag::strip(handle) as *const Instance),
)),
_ => Err(status::ERR_BAD_HANDLE),
}
}
}
/// 解析 param 句柄。
fn resolve_param(handle: *mut c_void) -> Result, c_int> {
if handle.is_null() {
return Err(status::ERR_BAD_HANDLE);
}
unsafe {
match tag::kind(handle) {
tag::PARAM_DEF => Ok(ParamRef::Def(&*(tag::strip(handle) as *const ParamDef))),
tag::PARAM_INSTANCE => Ok(ParamRef::Instance(
&*(tag::strip(handle) as *const ParamInstance),
)),
_ => Err(status::ERR_BAD_HANDLE),
}
}
}
/// 属性名(空指针/非 UTF-8 → ErrValue;防御性)。
unsafe fn c_name<'a>(name: *const c_char) -> Result<&'a str, c_int> {
if name.is_null() {
return Err(status::ERR_VALUE);
}
unsafe { CStr::from_ptr(name) }
.to_str()
.map_err(|_| status::ERR_VALUE)
}
/// 公共入口模板:panic 兜底。
#[track_caller]
fn caught(f: impl FnOnce() -> Result<(), c_int>) -> c_int {
let caller = std::panic::Location::caller();
let code = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).map_or_else(
|_| status::FAILED,
|r| r.map_or_else(|c| c, |()| status::OK),
);
if std::env::var_os("OAK_OFX_TRACE").is_some() {
if code != status::OK {
eprintln!("[ofx] param suite error {code} at {caller}");
}
}
code
}
// ---- 变长参数实现(C shim 转发)----------------------------------------
impl ParamKind {
fn from_i32(v: c_int) -> Option {
Some(match v {
1 => ParamKind::Int,
2 => ParamKind::Int2,
3 => ParamKind::Int3,
4 => ParamKind::Double,
5 => ParamKind::Double2,
6 => ParamKind::Double3,
7 => ParamKind::Bool,
8 => ParamKind::Choice,
9 => ParamKind::Rgb,
10 => ParamKind::Rgba,
11 => ParamKind::Str,
12 => ParamKind::StrChoice,
_ => return None,
})
}
}
/// 按 kind 把 ParamValue 写进类型化 out(数值类)。
fn write_value(v: &ParamValue, kind: ParamKind, out: *mut c_void) -> Result<(), c_int> {
let err = || Err(status::FAILED);
unsafe {
match kind {
ParamKind::Int => match v {
ParamValue::Int(a, _) => {
*(out as *mut c_int) = a[0];
Ok(())
}
_ => err(),
},
ParamKind::Int2 | ParamKind::Int3 => match v {
ParamValue::Int(a, dim) => {
let n = if kind == ParamKind::Int2 { 2 } else { 3 };
let dst = std::slice::from_raw_parts_mut(out as *mut c_int, n);
for (d, s) in dst.iter_mut().zip(a.iter()).take(n) {
*d = *s;
}
let _ = dim;
Ok(())
}
_ => err(),
},
ParamKind::Double => match v {
ParamValue::Double(d, _) => {
*(out as *mut f64) = d[0];
Ok(())
}
_ => err(),
},
ParamKind::Double2 | ParamKind::Double3 => match v {
ParamValue::Double(d, _) => {
let n = if kind == ParamKind::Double2 { 2 } else { 3 };
let dst = std::slice::from_raw_parts_mut(out as *mut f64, n);
for (dd, s) in dst.iter_mut().zip(d.iter()).take(n) {
*dd = *s;
}
Ok(())
}
_ => err(),
},
ParamKind::Bool => match v {
ParamValue::Bool(b) => {
*(out as *mut c_int) = *b as c_int;
Ok(())
}
_ => err(),
},
ParamKind::Choice => match v {
ParamValue::Choice(c) => {
*(out as *mut c_int) = *c;
Ok(())
}
_ => err(),
},
ParamKind::Rgb | ParamKind::Rgba => match v {
ParamValue::Color(c, _) => {
let n = if kind == ParamKind::Rgb { 3 } else { 4 };
let dst = std::slice::from_raw_parts_mut(out as *mut f64, n);
for (dd, s) in dst.iter_mut().zip(c.iter()).take(n) {
*dd = *s;
}
Ok(())
}
_ => err(),
},
ParamKind::Str | ParamKind::StrChoice => {
// 字符串走 ofx_param_get_string_impl(内驻指针)。
err()
}
}
}
}
/// 按 kind 从类型化 input 构造 ParamValue(数值类)。
fn read_value(kind: ParamKind, input: *const c_void) -> Option {
unsafe {
match kind {
ParamKind::Int => Some(ParamValue::Int([*(input as *const c_int), 0, 0], 1)),
ParamKind::Int2 => {
let a = *(input as *const [c_int; 2]);
Some(ParamValue::Int([a[0], a[1], 0], 2))
}
ParamKind::Int3 => {
let a = *(input as *const [c_int; 3]);
Some(ParamValue::Int([a[0], a[1], a[2]], 3))
}
ParamKind::Double => Some(ParamValue::Double([*(input as *const f64), 0.0, 0.0], 1)),
ParamKind::Double2 => {
let a = *(input as *const [f64; 2]);
Some(ParamValue::Double([a[0], a[1], 0.0], 2))
}
ParamKind::Double3 => {
let a = *(input as *const [f64; 3]);
Some(ParamValue::Double([a[0], a[1], a[2]], 3))
}
ParamKind::Bool => Some(ParamValue::Bool(*(input as *const c_int) != 0)),
ParamKind::Choice => Some(ParamValue::Choice(*(input as *const c_int))),
ParamKind::Rgb => {
let a = *(input as *const [f64; 3]);
Some(ParamValue::Color([a[0], a[1], a[2], 0.0], 3))
}
ParamKind::Rgba => {
let a = *(input as *const [f64; 4]);
Some(ParamValue::Color([a[0], a[1], a[2], a[3]], 4))
}
ParamKind::Str | ParamKind::StrChoice => None,
}
}
}
/// 取"当前值"(实例期=当前值,describe 期=默认值)。
fn value_of(r: &ParamRef) -> ParamValue {
match r {
ParamRef::Instance(p) => p.get(),
ParamRef::Def(d) => d.default.clone(),
}
}
/// param → 所属 instance 的回写定位表(instanceChanged 用)。
/// `// TODO(instance)`:createInstance 后由宿主登记,destroy 时摘除。
static PARAM_OWNER: std::sync::LazyLock>> =
std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
/// 登记参数 → 实例(实例化路径调用)。
pub(crate) fn register_param_owner(param_props: usize, instance_props: usize) {
PARAM_OWNER
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(param_props, instance_props);
}
/// 摘除某实例的全部参数登记(destroy 路径调用)。
pub(crate) fn unregister_params_of(instance_props: usize) {
PARAM_OWNER
.lock()
.unwrap_or_else(|e| e.into_inner())
.retain(|_, owner| *owner != instance_props);
}
/// paramSetValue 成功后的 instanceChanged 触发(HS:
/// ofxhParam.cpp:1991-1994 `paramChangedByPlugin`)。未登记实例时
/// no-op(未绑定节点的场景本就 no-op)。parametric suite 的
/// Set/Add/Delete 复用此路径(pub(crate):suites/parametric.rs)。
pub(crate) fn notify_changed(param_props: usize, name: &str) {
let owner = PARAM_OWNER
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(¶m_props)
.copied();
if let Some(inst_addr) = owner {
// instance 的 props 在偏移 0(句柄约定)。
let inst = unsafe { &*(inst_addr as *const Instance) };
crate::param::notify_instance_changed(inst, name, ChangeReason::PluginEdited);
}
}
/// 参数类型查询(C shim 调用):未知/非法句柄 → 0(dispatch 失败)。
#[no_mangle]
pub unsafe extern "C" fn ofx_param_kind_of(param: *mut c_void) -> c_int {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
match resolve_param(param) {
Ok(r) => match &r {
ParamRef::Instance(p) => p.def.kind().map_or(0, |k| k as c_int),
ParamRef::Def(d) => d.kind().map_or(0, |k| k as c_int),
},
Err(_) => 0,
}
}))
.unwrap_or(0)
}
/// 数值类 get(C shim 转发):`out` 为按 kind 的类型化数组。
#[no_mangle]
pub unsafe extern "C" fn ofx_param_get_impl(
param: *mut c_void,
kind: c_int,
out: *mut c_void,
) -> c_int {
caught(|| {
if out.is_null() {
return Err(status::ERR_VALUE);
}
let k = ParamKind::from_i32(kind).ok_or(status::ERR_UNKNOWN)?;
let r = resolve_param(param)?;
write_value(&value_of(&r), k, out)?;
Ok(())
})
}
/// 字符串 get(内驻指针:指向实例存储 / 定义默认值;下次 set 前
/// 有效——与属性 suite 的字符串指针契约一致)。
#[no_mangle]
pub unsafe extern "C" fn ofx_param_get_string_impl(
param: *mut c_void,
out: *mut *mut c_char,
) -> c_int {
caught(|| {
if out.is_null() {
return Err(status::ERR_VALUE);
}
let ptr: *const c_char = match resolve_param(param)? {
ParamRef::Instance(p) => p.string_ptr().ok_or(status::FAILED)?,
ParamRef::Def(d) => match &d.default {
ParamValue::String(s) | ParamValue::StrChoice(s) => s.as_ptr(),
_ => return Err(status::FAILED),
},
};
unsafe { *out = ptr as *mut c_char };
Ok(())
})
}
/// 数值类 set(C shim 转发):`input` 为按 kind 的类型化数组。
/// describe 期(ParamDef)→ BadHandle(HS: paramSetValue 的
/// verifyMagic 对 descriptor 失败)。
#[no_mangle]
pub unsafe extern "C" fn ofx_param_set_impl(
param: *mut c_void,
kind: c_int,
input: *const c_void,
) -> c_int {
caught(|| {
if input.is_null() {
return Err(status::ERR_VALUE);
}
let k = ParamKind::from_i32(kind).ok_or(status::ERR_UNKNOWN)?;
let value = read_value(k, input).ok_or(status::FAILED)?;
let (name, props_addr) = match resolve_param(param)? {
ParamRef::Instance(p) => {
p.set_ofx(value);
(p.def.name.clone(), &p.props as *const _ as usize)
}
ParamRef::Def(_) => return Err(status::ERR_BAD_HANDLE),
};
notify_changed(props_addr, &name);
Ok(())
})
}
/// 字符串 set。
#[no_mangle]
pub unsafe extern "C" fn ofx_param_set_string_impl(
param: *mut c_void,
input: *const c_char,
) -> c_int {
caught(|| {
if input.is_null() {
return Err(status::ERR_VALUE);
}
let s = CString::new(unsafe { CStr::from_ptr(input) }.to_bytes()).unwrap();
let (name, props_addr) = match resolve_param(param)? {
ParamRef::Instance(p) => {
p.set_ofx(ParamValue::String(s));
(p.def.name.clone(), &p.props as *const _ as usize)
}
ParamRef::Def(_) => return Err(status::ERR_BAD_HANDLE),
};
notify_changed(props_addr, &name);
Ok(())
})
}
/// derivative/integral:第 1 期无动画 → MissingHostFeature
/// (C shim 不消费 va_args 直接转发)。
#[no_mangle]
pub unsafe extern "C" fn ofx_param_missing_feature_impl(param: *mut c_void) -> c_int {
caught(|| {
// handle 合法性仍检查(空/坏句柄 → BadHandle)。
let _ = resolve_param(param)?;
Err(status::ERR_MISSING_HOST_FEATURE)
})
}
// ---- 非变长入口 ----------------------------------------------------------
/// paramDefine:describe 期定义参数(未定义类型 → Unsupported,
/// HS:1704;重复名 → HS 允许,原样再建一条——宿主以首个为准,与
/// HS 的 map 覆盖语义一致)。
unsafe extern "C" fn param_define(
param_set: *mut c_void,
param_type: *const c_char,
name: *const c_char,
property_set: *mut *mut c_void,
) -> c_int {
caught(|| {
if property_set.is_null() {
return Err(status::ERR_BAD_HANDLE);
}
unsafe { *property_set = std::ptr::null_mut() };
let t = unsafe { c_name(param_type)? };
let n = unsafe { c_name(name)? };
// 仅 describe 期(HS: SetDescriptor 专属,实例期 dynamic_cast
// 失败 → BadHandle)。
let desc = match resolve_param_set(param_set)? {
ParamSetRef::Descriptor(d) => d,
ParamSetRef::Instance(_) => return Err(status::ERR_BAD_HANDLE),
};
// 类型合法性(kind_of_type 之外还有无值类与 parametric;
// parametric 的曲线值走 OfxParametricParameterSuite)。
let valid = crate::param::kind_of_type(t).is_some()
|| matches!(
t,
crate::param::TYPE_BYTES
| crate::param::TYPE_CUSTOM
| crate::param::TYPE_PUSHBUTTON
| crate::param::TYPE_GROUP
| crate::param::TYPE_PAGE
| crate::param::TYPE_PARAMETRIC
);
if !valid {
return Err(status::ERR_UNSUPPORTED);
}
let def = ParamDef::new(n, t);
desc.params.push(Box::new(def));
// 地址必须取自已入盒的对象(栈上临时变量在 push 后失效,
// 句柄会指向死内存——Box 负载地址在 Vec 重分配时稳定)。
let def = desc.params.last().expect("just pushed");
let addr = &def.props as *const _ as usize;
unsafe {
*property_set = tag::make(addr as *const crate::property::PropertySet, tag::PARAM_DEF)
};
Ok(())
})
}
/// paramGetHandle:按名查(未找到 → ErrUnknown,HS:1758)。
unsafe extern "C" fn param_get_handle(
param_set: *mut c_void,
name: *const c_char,
param: *mut *mut c_void,
property_set: *mut *mut c_void,
) -> c_int {
caught(|| {
if param.is_null() {
return Err(status::ERR_BAD_HANDLE);
}
unsafe { *param = std::ptr::null_mut() };
let n = unsafe { c_name(name)? };
let handle = match resolve_param_set(param_set)? {
ParamSetRef::Descriptor(d) => {
let def = d.param(n).ok_or(status::ERR_UNKNOWN)?;
let addr = &def.props as *const _ as usize;
tag::make(addr as *const crate::property::PropertySet, tag::PARAM_DEF)
}
ParamSetRef::Instance(i) => {
let p = i.params.find(n).ok_or(status::ERR_UNKNOWN)?;
let addr = &p.props as *const _ as usize;
tag::make(
addr as *const crate::property::PropertySet,
tag::PARAM_INSTANCE,
)
}
};
unsafe { *param = handle };
if !property_set.is_null() {
// props 在偏移 0:param handle 与 props handle 同值。
unsafe { *property_set = handle };
}
Ok(())
})
}
/// paramSetGetPropertySet:param-set 的属性集(= effect handle 本体)。
unsafe extern "C" fn param_set_get_property_set(
param_set: *mut c_void,
property_set: *mut *mut c_void,
) -> c_int {
caught(|| {
if property_set.is_null() {
return Err(status::ERR_BAD_HANDLE);
}
let _ = resolve_param_set(param_set)?;
unsafe { *property_set = param_set };
Ok(())
})
}
/// paramGetPropertySet:param 的属性集(= param handle 本体)。
unsafe extern "C" fn param_get_property_set(
param: *mut c_void,
property_set: *mut *mut c_void,
) -> c_int {
caught(|| {
if property_set.is_null() {
return Err(status::ERR_BAD_HANDLE);
}
let _ = resolve_param(param)?;
unsafe { *property_set = param };
Ok(())
})
}
/// paramGetNumKeys:第 1 期无动画 → 恒 0。
unsafe extern "C" fn param_get_num_keys(param: *mut c_void, count: *mut c_int) -> c_int {
caught(|| {
let _ = resolve_param(param)?;
if count.is_null() {
return Err(status::ERR_VALUE);
}
unsafe { *count = 0 };
Ok(())
})
}
/// paramGetKeyTime:无关键帧 → BadIndex。
unsafe extern "C" fn param_get_key_time(
param: *mut c_void,
_index: c_int,
_time: *mut c_double,
) -> c_int {
caught(|| {
let _ = resolve_param(param)?;
Err(status::ERR_BAD_INDEX)
})
}
/// paramGetKeyIndex:无关键帧 → BadIndex。
unsafe extern "C" fn param_get_key_index(
param: *mut c_void,
_time: c_double,
_direction: c_int,
_index: *mut c_int,
) -> c_int {
caught(|| {
let _ = resolve_param(param)?;
Err(status::ERR_BAD_INDEX)
})
}
/// paramDeleteKey / paramDeleteAllKeys:无关键帧 → OK no-op。
unsafe extern "C" fn param_delete_key(param: *mut c_void, _time: c_double) -> c_int {
caught(|| {
let _ = resolve_param(param)?;
Ok(())
})
}
unsafe extern "C" fn param_delete_all_keys(param: *mut c_void) -> c_int {
caught(|| {
let _ = resolve_param(param)?;
Ok(())
})
}
/// paramCopy:第 1 期无关键帧可拷 → OK no-op。
unsafe extern "C" fn param_copy(
dst: *mut c_void,
src: *mut c_void,
_dst_time: c_double,
_src_time: c_double,
_key_range: *const c_void,
) -> c_int {
caught(|| {
let _ = resolve_param(dst)?;
let _ = resolve_param(src)?;
Ok(())
})
}
/// paramEditBegin:进入编辑事务(undo 分组)。解析 param 句柄后经
/// PARAM_OWNER 定位所属实例并递增其事务深度;未登记实例时 no-op
/// (手工构造的实例没有宿主注册,edit 括号退化为无分组)。
unsafe extern "C" fn param_edit_begin(param: *mut c_void) -> c_int {
caught(|| {
let addr = match resolve_param(param)? {
ParamRef::Instance(p) => &p.props as *const _ as usize,
ParamRef::Def(_) => return Err(status::ERR_BAD_HANDLE),
};
if let Some(owner) = PARAM_OWNER
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&addr)
.copied()
{
let inst = unsafe { &*(owner as *const Instance) };
inst.edit_begin();
}
Ok(())
})
}
/// paramEditEnd:退出编辑事务;最外层结束时把累积的 multi 命令
/// redo 生效并释放(见 [`crate::instance::Instance::edit_end`])。
unsafe extern "C" fn param_edit_end(param: *mut c_void) -> c_int {
caught(|| {
let addr = match resolve_param(param)? {
ParamRef::Instance(p) => &p.props as *const _ as usize,
ParamRef::Def(_) => return Err(status::ERR_BAD_HANDLE),
};
if let Some(owner) = PARAM_OWNER
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&addr)
.copied()
{
let inst = unsafe { &*(owner as *const Instance) };
inst.edit_end();
}
Ok(())
})
}
extern "C" {
fn ofx_param_get_value_shim(param: *mut c_void, ...) -> c_int;
fn ofx_param_get_value_at_time_shim(param: *mut c_void, time: c_double, ...) -> c_int;
fn ofx_param_set_value_shim(param: *mut c_void, ...) -> c_int;
fn ofx_param_set_value_at_time_shim(param: *mut c_void, time: c_double, ...) -> c_int;
fn ofx_param_get_derivative_shim(param: *mut c_void, time: c_double, ...) -> c_int;
fn ofx_param_get_integral_shim(
param: *mut c_void,
time1: c_double,
time2: c_double,
...
) -> c_int;
}
/// 静态函数表实例。
pub fn suite_v1() -> &'static ParameterSuiteV1 {
static SUITE: std::sync::OnceLock = std::sync::OnceLock::new();
SUITE.get_or_init(|| ParameterSuiteV1 {
param_define: param_define,
param_get_handle: param_get_handle,
param_set_get_property_set: param_set_get_property_set,
param_get_property_set: param_get_property_set,
param_get_value: ofx_param_get_value_shim,
param_get_value_at_time: ofx_param_get_value_at_time_shim,
param_get_derivative: ofx_param_get_derivative_shim,
param_get_integral: ofx_param_get_integral_shim,
param_set_value: ofx_param_set_value_shim,
param_set_value_at_time: ofx_param_set_value_at_time_shim,
param_get_num_keys: param_get_num_keys,
param_get_key_time: param_get_key_time,
param_get_key_index: param_get_key_index,
param_delete_key: param_delete_key,
param_delete_all_keys: param_delete_all_keys,
param_copy: param_copy,
param_edit_begin: param_edit_begin,
param_edit_end: param_edit_end,
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
use std::sync::Arc;
use crate::host::Plugin;
use crate::param::{ParamInstance, ParamSetInstance};
use crate::property::PropertySet;
fn cs(s: &str) -> CString {
CString::new(s).unwrap()
}
fn descriptor_handle(d: &EffectDescriptor) -> *mut c_void {
tag::make(
&d.props as *const crate::property::PropertySet,
tag::DESCRIPTOR,
)
}
/// 假插件(host::Plugin 的构造只为拿 Arc 喂给 Instance;describe
/// 之外的字段不被本测试触碰)。
fn dummy_plugin(descriptor: EffectDescriptor) -> Arc {
unsafe extern "C" fn dummy_entry(
_: *const c_char,
_: *const c_void,
_: *mut c_void,
_: *mut c_void,
) -> c_int {
status::OK
}
Arc::new(Plugin {
identifier: "test.plugin".into(),
version: (1, 0),
bundle_path: std::path::PathBuf::new(),
contexts: vec![],
descriptor,
lib: std::ptr::null_mut(),
entry: dummy_entry,
ofx_plugin: std::ptr::null_mut(),
unloaded: std::sync::atomic::AtomicBool::new(false),
})
}
fn instance_handle(i: &Instance) -> *mut c_void {
tag::make(
&i.props as *const crate::property::PropertySet,
tag::INSTANCE,
)
}
/// 用 paramDefine 建一个实例(describe 产物 → createInstance)。
fn make_instance() -> (Arc, *mut c_void) {
let desc = EffectDescriptor::new();
let s = suite_v1();
let dhandle = descriptor_handle(&desc);
unsafe {
for (t, n) in [
("OfxParamTypeInteger", "gain"),
("OfxParamTypeDouble", "opacity"),
("OfxParamTypeDouble2D", "pos"),
("OfxParamTypeRGB", "color"),
("OfxParamTypeBoolean", "enabled"),
("OfxParamTypeString", "label"),
] {
let t = cs(t);
let n = cs(n);
let mut ph: *mut c_void = std::ptr::null_mut();
assert_eq!(
(s.param_define)(dhandle, t.as_ptr(), n.as_ptr(), &mut ph),
0
);
}
}
let params = ParamSetInstance {
params: desc
.params
.iter()
.map(|d| Box::new(ParamInstance::from_def((**d).clone())))
.collect(),
};
let plugin = dummy_plugin(desc);
let inst = Arc::new(Instance {
props: PropertySet::new(),
plugin,
context: "OfxImageEffectContextFilter".into(),
params,
clips: vec![],
node_identity: std::sync::atomic::AtomicUsize::new(0),
destroyed: std::sync::atomic::AtomicBool::new(false),
sequence_range: std::sync::Mutex::new(None),
progress_cb: std::sync::Mutex::new(None),
cancel: std::sync::atomic::AtomicBool::new(false),
edit: std::sync::Mutex::new(crate::instance::EditTransaction::new()),
render_lock: std::sync::Mutex::new(()),
interact: std::sync::Mutex::new(None),
});
(inst.clone(), instance_handle(&inst))
}
#[test]
fn describe_define_and_get() {
let desc = EffectDescriptor::new();
let s = suite_v1();
let handle = descriptor_handle(&desc);
let t = cs("OfxParamTypeDouble");
let n = cs("opacity");
let mut ph: *mut c_void = std::ptr::null_mut();
unsafe {
assert_eq!((s.param_define)(handle, t.as_ptr(), n.as_ptr(), &mut ph), 0);
}
assert!(!ph.is_null());
assert_eq!(tag::kind(ph), tag::PARAM_DEF);
// describe 期 paramGetValue:默认值 0.0。
let mut v = 99.0;
unsafe {
assert_eq!((s.param_get_value)(ph, &mut v), 0);
}
assert_eq!(v, 0.0);
// describe 期 set → BadHandle(HS: verifyMagic 对 descriptor 失败)。
unsafe {
assert_eq!((s.param_set_value)(ph, 42.0), status::ERR_BAD_HANDLE);
}
// paramGetHandle 按名取(props handle 同值)。
let mut ph2: *mut c_void = std::ptr::null_mut();
unsafe {
assert_eq!(
(s.param_get_handle)(handle, n.as_ptr(), &mut ph2, std::ptr::null_mut()),
0
);
assert_eq!(ph, ph2);
}
// 未找到 → ErrUnknown(HS:1758)。
let nope = cs("nope");
unsafe {
assert_eq!(
(s.param_get_handle)(handle, nope.as_ptr(), &mut ph2, std::ptr::null_mut()),
status::ERR_UNKNOWN
);
}
// 未定义类型 → ErrUnsupported(HS:1704);parametric → 放行
// (曲线读写走 OfxParametricParameterSuite)。
let bad = cs("OfxParamTypeBogus");
let par = cs("OfxParamTypeParametric");
unsafe {
assert_eq!(
(s.param_define)(handle, bad.as_ptr(), n.as_ptr(), &mut ph2),
status::ERR_UNSUPPORTED
);
assert_eq!((s.param_define)(handle, par.as_ptr(), n.as_ptr(), &mut ph2), 0);
}
assert_eq!(tag::kind(ph2), tag::PARAM_DEF);
// 实例期 paramDefine → BadHandle。
let (_inst, ih) = make_instance();
unsafe {
assert_eq!(
(s.param_define)(ih, t.as_ptr(), n.as_ptr(), &mut ph2),
status::ERR_BAD_HANDLE
);
}
}
#[test]
fn instance_get_set_variadic_roundtrip() {
let (_inst, ih) = make_instance();
let s = suite_v1();
// 取各参数 handle。
let mut gain: *mut c_void = std::ptr::null_mut();
let mut opacity: *mut c_void = std::ptr::null_mut();
let mut pos: *mut c_void = std::ptr::null_mut();
let mut color: *mut c_void = std::ptr::null_mut();
let mut enabled: *mut c_void = std::ptr::null_mut();
let mut label: *mut c_void = std::ptr::null_mut();
unsafe {
for (n, out) in [
("gain", &mut gain),
("opacity", &mut opacity),
("pos", &mut pos),
("color", &mut color),
("enabled", &mut enabled),
("label", &mut label),
] {
let n = cs(n);
let r = (s.param_get_handle)(ih, n.as_ptr(), out, std::ptr::null_mut());
assert_eq!(r, 0, "gethandle {} failed: {}", n.to_str().unwrap(), r);
assert_eq!(tag::kind(*out), tag::PARAM_INSTANCE);
}
}
// Integer set/get。
unsafe {
assert_eq!((s.param_set_value)(gain, 7), 0);
}
let mut v = 0;
unsafe {
assert_eq!((s.param_get_value)(gain, &mut v), 0);
}
assert_eq!(v, 7);
// Double2D。
unsafe {
assert_eq!((s.param_set_value)(pos, 1.5, -2.5), 0);
}
let (mut x, mut y) = (0.0, 0.0);
unsafe {
assert_eq!((s.param_get_value)(pos, &mut x, &mut y), 0);
}
assert_eq!((x, y), (1.5, -2.5));
// RGB。
unsafe {
assert_eq!((s.param_set_value)(color, 0.1, 0.2, 0.3), 0);
}
let (mut r, mut g, mut b) = (0.0, 0.0, 0.0);
unsafe {
assert_eq!((s.param_get_value)(color, &mut r, &mut g, &mut b), 0);
}
assert_eq!((r, g, b), (0.1, 0.2, 0.3));
// Boolean(int 0/1)。
unsafe {
assert_eq!((s.param_set_value)(enabled, 1), 0);
}
let mut e = 0;
unsafe {
assert_eq!((s.param_get_value)(enabled, &mut e), 0);
}
assert_eq!(e, 1);
// String:set 后 get 返回内驻指针。
let hello = cs("hello");
unsafe {
assert_eq!((s.param_set_value)(label, hello.as_ptr()), 0);
}
let mut p: *mut c_char = std::ptr::null_mut();
unsafe {
assert_eq!((s.param_get_value)(label, &mut p), 0);
assert_eq!(CStr::from_ptr(p).to_bytes(), b"hello");
}
// AtTime == 当前值(无动画)。
let mut v2 = 0.0;
unsafe {
assert_eq!((s.param_get_value_at_time)(opacity, 12.0, &mut v2), 0);
assert_eq!(v2, 0.0);
assert_eq!((s.param_set_value_at_time)(opacity, 12.0, 0.75), 0);
}
let mut v3 = 0.0;
unsafe {
assert_eq!((s.param_get_value)(opacity, &mut v3), 0);
}
assert_eq!(v3, 0.75);
// keys:恒 0 / BadIndex。
let mut nkeys = -1;
unsafe {
assert_eq!((s.param_get_num_keys)(opacity, &mut nkeys), 0);
assert_eq!(nkeys, 0);
let mut kt = 0.0;
assert_eq!(
(s.param_get_key_time)(opacity, 0, &mut kt),
status::ERR_BAD_INDEX
);
}
// derivative/integral → MissingHostFeature。
let mut dv = 0.0;
unsafe {
assert_eq!(
(s.param_get_derivative)(opacity, 1.0, &mut dv),
status::ERR_MISSING_HOST_FEATURE
);
}
// editBegin/End、deleteKey(s)、copy:no-op OK。
unsafe {
assert_eq!((s.param_edit_begin)(opacity), 0);
assert_eq!((s.param_edit_end)(opacity), 0);
assert_eq!((s.param_delete_key)(opacity, 1.0), 0);
assert_eq!((s.param_delete_all_keys)(opacity), 0);
assert_eq!(
(s.param_copy)(opacity, opacity, 1.0, 1.0, std::ptr::null()),
0
);
}
}
/// paramSetValue → instanceChanged 回写(C++ paramChangedByPlugin):
/// 实例绑定节点后,插件侧改值经 PARAM_OWNER 定位实例、
/// notify_instance_changed 生成 undo 命令并立即 redo——节点输入
/// 即新值。
#[test]
fn set_value_writes_back_to_bound_node() {
let (inst, ih) = make_instance();
let s = suite_v1();
let mut gain: *mut c_void = std::ptr::null_mut();
unsafe {
assert_eq!(
(s.param_get_handle)(ih, cs("gain").as_ptr(), &mut gain, std::ptr::null_mut()),
0
);
}
// 宿主侧登记:param → 实例(生产由 createInstance 路径做),
// 实例 → oaknode 节点(facade 装配期做)。
let project = oak_node::project::Project::new();
let node_id = {
let mut guard = project.lock().unwrap_or_else(|e| e.into_inner());
let id = guard.graph.add_node(
oak_node::node::NodeCore::empty(),
Box::new(oak_node::nodes::EmptyBehavior),
);
guard.graph.get_mut(id).unwrap().core.add_input(
oak_node::input::Input::new(
"gain",
oak_node::value::ValueType::Int,
oak_node::value::NodeValue::Int(0),
),
);
id
};
let identity = crate::node::register_node(project.clone(), node_id);
inst.bind_node(identity as usize);
let param_addr = tag::strip(gain) as usize;
register_param_owner(param_addr, &inst.props as *const PropertySet as usize);
unsafe {
assert_eq!((s.param_set_value)(gain, 5), 0);
}
let stored = project
.lock()
.unwrap_or_else(|e| e.into_inner())
.graph
.get(node_id)
.unwrap()
.core
.standard_value("gain", -1);
assert_eq!(
stored,
oak_node::value::NodeValue::Int(5),
"插件改值必须经 instanceChanged 写回节点输入"
);
unregister_params_of(&inst.props as *const PropertySet as usize);
crate::node::unregister_node(identity);
}
}