Files
oak-editor/crates/oak-plugin/src/property.rs
T
Mike-Solar 244d5e860f
CI / Build & test (Windows) (push) Failing after 7s
workspace: kebab-case crates, app under crates/oak-app, shared versions
All crates take the oak-* kebab-case naming (oak-audio, oak-codec,
oak-common, oak-core, oak-ffmpeg-link, oak-node, oak-otio, oak-plugin,
oak-render, oak-storage, oak-task, oak-timeline, oak-undo), with the
lib identifiers rewritten (oakrender:: -> oak_render::, oakcore_rs:: ->
oak_core::, ...) across all 226 referencing files.

The GUI application moves from the workspace root into
crates/oak-app/: src/, build.rs (paths fixed for the new location) and
tests/ travel with it, the root Cargo.toml becomes workspace-only
([workspace] + workspace.package + profiles), and the app package
inherits the workspace version. The screenshots example becomes a
standalone crate examples/simple_player/ with its own Cargo.toml.

Every crate now inherits the single workspace version
(version.workspace = true), and the workflows' crate paths and the
build docs follow the renames.

Validated with a clean cargo check --workspace.
2026-08-22 16:58:37 +08:00

173 lines
5.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 <http://www.gnu.org/licenses/>.
//! OFX 属性集(Property Suite 的宿主侧存储)。
//!
//! 每个 OFX 对象(host/plugin/descriptor/instance/clip/image/param
//! 都挂一个 [`PropertySet`]。属性是多维数组,按类型存取。
//! 参照:HS: ofxhProperty.cppGetSuite 的读写语义:越界返回
//! kOfxStatErrBadIndex,类型不符返回 kOfxStatErrBadHandle)。
use std::sync::Mutex;
use crate::error::{Error, Result};
/// 属性值(单元素)。字符串以 OFX 的 `char*` 语义持有(host 拥有,
/// 插件借用)。
#[derive(Clone, Debug)]
pub enum Value {
/// 32 位整型。
Int(i32),
/// 双精度。
Double(f64),
/// UTF-8 字符串(OFX 侧为 NUL 结尾 char*)。
String(std::ffi::CString),
/// 不透明指针(如 OfxImageEffectHandle 互指)。
Pointer(*mut std::ffi::c_void),
}
// 裸指针默认禁 Send/Sync;但属性集语义把指针当作**不透明令牌**:
// 跨线程只搬运/比较值,解引用永远是 suite 层的 unsafe 责任(OFX
// multithread suite 本就要求插件线程可读写宿主图像缓冲——指针的
// 跨线程传递是规范语义,而非逃逸)。Mutex 包裹后属性集整体可共享。
// 与 Rust 安全模型不冲突:安全代码只能拿到 `&Value`,无法解引用指针。
unsafe impl Send for Value {}
unsafe impl Sync for Value {}
/// 一个属性:名字 + 多维值数组。
#[derive(Clone, Debug)]
pub struct Property {
/// OFX 属性名(kOfxProp*;拥有型——协商期的动态名
/// "OfxImageClipPropComponents_<clip>" 需要,`&'static str`
/// 无法表达)。
pub name: String,
/// 元素数组;维度 = len。
pub values: Vec<Value>,
}
/// 属性集。线程安全(内部 Mutex);OFX 对象的 `*Handle` 即指向它的
/// 包装。
pub struct PropertySet {
props: Mutex<Vec<Property>>,
}
/// 深拷贝(持锁克隆内部数组;createInstance 时描述符属性 → 实例
/// 属性需要)。
impl Clone for PropertySet {
fn clone(&self) -> Self {
let props = lock(&self.props);
Self {
props: Mutex::new(props.clone()),
}
}
}
impl PropertySet {
/// 空集。
pub fn new() -> Self {
Self {
props: Mutex::new(Vec::new()),
}
}
/// 定义(或整体替换)一个属性。已存在同名属性时替换其值数组。
///
/// 对应 C++ `Set::addProperty` 的替换语义(HS: ofxhPropertySuite.cpp:462
/// 与 `PropertyTemplate::setValueN` 的整体写数组语义;属性不存在时
/// 新建(维度和值都取自 `values`)。
pub fn define(&self, name: &str, values: Vec<Value>) {
let mut props = lock(&self.props);
if let Some(p) = props.iter_mut().find(|p| p.name == name) {
p.values = values;
} else {
props.push(Property {
name: name.to_string(),
values,
});
}
}
/// 单元素便捷定义。
pub fn set_one(&self, name: &str, value: Value) {
self.define(name, vec![value]);
}
/// 读取第 `index` 个元素;属性不存在或越界返回 `None`。
///
/// 越界对应 C++ 的 kOfxStatErrBadIndex
/// HS: ofxhPropertySuite.cpp:257 `getValueRaw`),此处以 Option 表达。
pub fn get(&self, name: &str, index: usize) -> Option<Value> {
let props = lock(&self.props);
props
.iter()
.find(|p| p.name == name)
.and_then(|p| p.values.get(index).cloned())
}
/// 覆盖第 `index` 个元素的值(不改变维度);失败返回
/// [`crate::error::Error::NotFound`]。
///
/// 维度固定是刻意为之:与 C++ `setValue` 的自动扩容不同,这里
/// 扩容只能经 [`PropertySet::define`] 显式进行(维度语义由定义方
/// 掌控,避免插件意外撑大数组)。
pub fn set_at(&self, name: &str, index: usize, value: Value) -> Result<()> {
let mut props = lock(&self.props);
let p = props
.iter_mut()
.find(|p| p.name == name)
.ok_or(Error::NotFound)?;
let slot = p.values.get_mut(index).ok_or(Error::NotFound)?;
*slot = value;
Ok(())
}
/// 维度(属性不存在为 0)。
pub fn dimension(&self, name: &str) -> usize {
lock(&self.props)
.iter()
.find(|p| p.name == name)
.map_or(0, |p| p.values.len())
}
/// 删除属性;不存在为 no-op。
pub fn remove(&self, name: &str) {
lock(&self.props).retain(|p| p.name != name);
}
/// 遍历快照(dump/快照测试用)。
pub fn snapshot(&self) -> Vec<Property> {
lock(&self.props).clone()
}
/// suite 层专用:持锁访问原始存储。
///
/// 属性 suite 需要"先类型检查再按索引读写、返回内驻字符串指针"
/// 等跨多次读写的一致性语义(逐条对照 HS: ofxhProperty.cpp),
/// 公开 API 无法在不重复加锁的前提下表达;此入口把 `Vec<Property>`
/// 在单一临界区内交给 suite 实现。仅本 crate 可见。
pub(crate) fn with_locked<R>(&self, f: impl FnOnce(&mut Vec<Property>) -> R) -> R {
let mut props = lock(&self.props);
f(&mut props)
}
}
/// 取锁。毒锁(本 crate 代码在持锁时 panic)时接管其内部状态继续
/// 使用——属性集的数据本身总是完好的,宁可继续也不让一次 panic
/// 级联成后续所有 FFI 调用失败。
fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
m.lock().unwrap_or_else(|e| e.into_inner())
}