Files
oak-editor/crates/oak-plugin/src/suites/multithread.rs
T
Mike-Solar ee7ea18d94 clippy: clear the workspace errors and apply the machine fixes
- Mark the raw-pointer interop entry points unsafe with # Safety docs
  (oak-core upload/download/frame-from-pixels, oak-audio convert) and
  satisfy the existing callers (tests).
- mut_from_ref: allow with the ABI contract documented (the handle
  get_mut helpers in oak-timeline/oak-render/oak-task take the shared
  reference the C ABI passes; exclusivity is the caller's unsafe
  contract).
- Fix the eq_op in the white-balance normalization (green / green).
- Apply cargo clippy --fix across the workspace (redundant closures and
  field names, field reassignment, items after test modules, ...).
- Revert the replace_box fix in image_effect's clip_define: a
  redefinition must allocate a new box, otherwise the old clip handle
  stays valid and the HS-map replace contract (clip != clip2) breaks.
- 283 warnings remain; they are all non-machine-applicable
  (chunks_exact -> as_chunks needs a manual iter_mut, too_many_arguments,
  complex types, missing Safety docs, ...) and are tracked as the
  follow-up.
2026-09-15 19:29:32 +08:00

344 lines
10 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/>.
//! OfxMultiThreadSuite v1:插件自起线程的登记与索引分配。
//!
//! 纪律:插件线程可在本 suite 存活期回调任意 suite——所有共享状态
//! 必须 Mutex(README §3)。线程经 `std::thread::scope` 启动并同步
//! join(OFX 语义:multiThread 返回时全部线程已完成;
//! HS: ofxhImageEffect.cpp gMultiThreadSuite),故无需常驻线程表。
//! 线程索引经 TLS 分配:宿主线程 [`index`] 为 -1、
//! [`is_spawned`] 为 0。
//!
//! 参照:HS: ofxhImageEffect.cpp gMultiThreadSuite。
use std::collections::HashMap;
use std::ffi::{c_int, c_uint, c_void};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use crate::suites::status;
thread_local! {
/// 本线程的 OFX 线程索引(宿主线程 = None)。
static THREAD_INDEX: std::cell::Cell<Option<c_uint>> = const { std::cell::Cell::new(None) };
}
/// 函数表布局(OfxMultiThreadSuiteV1)。
#[repr(C)]
pub struct MultiThreadSuiteV1 {
/// multiThread:启动 `n_threads` 个线程跑 `func`,线程索引
/// 0..n_threads-1 经 `thread_arg` 传入。
pub multi_thread: unsafe extern "C" fn(
func: unsafe extern "C" fn(c_uint, c_uint, *mut c_void),
n_threads: c_uint,
thread_arg: *mut c_void,
) -> c_int,
/// multiThreadNumCPUs
pub num_cpus: unsafe extern "C" fn(*mut c_int) -> c_int,
/// multiThreadIndex:当前线程的 OFX 线程索引(宿主线程为 -1)。
pub index: unsafe extern "C" fn(*mut c_int) -> c_int,
/// multiThreadIsSpawnedThread:当前线程是否插件线程。
pub is_spawned: unsafe extern "C" fn(*mut c_int) -> c_int,
/// mutexCreate:`count` 是初始可用计数(>1 时是信号量语义)。
pub mutex_create: unsafe extern "C" fn(*mut *mut c_void, c_int) -> c_int,
/// mutexDestroy
pub mutex_destroy: unsafe extern "C" fn(*mut c_void) -> c_int,
/// mutexLock
pub mutex_lock: unsafe extern "C" fn(*mut c_void) -> c_int,
/// mutexUnLock
pub mutex_unlock: unsafe extern "C" fn(*mut c_void) -> c_int,
/// mutexTryLock
pub mutex_try_lock: unsafe extern "C" fn(*mut c_void) -> c_int,
}
/// 公共入口模板:panic 兜底。
fn caught(f: impl FnOnce() -> c_int) -> c_int {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or(status::FAILED)
}
/// multiThread 实现:同步起 n 个线程(0..n-1),join 后返回。
/// 线程体只做两件事:登记 TLS 索引、调 `func`(C 代码,不 panic)。
unsafe extern "C" fn multi_thread(
func: unsafe extern "C" fn(c_uint, c_uint, *mut c_void),
n_threads: c_uint,
thread_arg: *mut c_void,
) -> c_int {
caught(|| {
if std::env::var_os("OAK_OFX_TRACE").is_some() {
eprintln!("[ofx] multiThread: n_threads={n_threads} func={:#x} arg={thread_arg:p}", func as usize);
}
// 裸指针不可 Send:以 usize 搬运,线程内还原(C 侧本即
// 整数传递语义)。
let arg = thread_arg as usize;
std::thread::scope(|scope| {
for i in 0..n_threads {
scope.spawn(move || {
THREAD_INDEX.with(|t| t.set(Some(i)));
// func 是插件代码:按 OFX 契约不 panic;Rust 侧
// 只做 TLS 登记,无 panic 源。
unsafe { (func)(i, n_threads, arg as *mut c_void) };
THREAD_INDEX.with(|t| t.set(None));
});
}
});
status::OK
})
}
unsafe extern "C" fn multi_thread_num_cpus(out: *mut c_int) -> c_int {
caught(|| {
if out.is_null() {
return status::ERR_VALUE;
}
// 物理核数(HS 同:系统 CPU 数)。
let n = std::thread::available_parallelism()
.map(|n| n.get() as c_int)
.unwrap_or(1);
unsafe { *out = n };
status::OK
})
}
unsafe extern "C" fn multi_thread_index(out: *mut c_int) -> c_int {
caught(|| {
if out.is_null() {
return status::ERR_VALUE;
}
// 宿主线程 → -1(HS 约定)。
unsafe { *out = THREAD_INDEX.with(|t| t.get()).map_or(-1, |i| i as c_int) };
status::OK
})
}
unsafe extern "C" fn multi_thread_is_spawned(out: *mut c_int) -> c_int {
caught(|| {
if out.is_null() {
return status::ERR_VALUE;
}
unsafe { *out = THREAD_INDEX.with(|t| t.get().is_some()) as c_int };
status::OK
})
}
// ---- 互斥锁(OfxMultiThreadSuiteV1 的 mutex* 函数组)-----------------------
//
// 句柄表:全局注册表按 usize 键发号;`count > 1` 是信号量语义(ofxMultiThread.h
// "a mutex with a count greater than 1 is a counting semaphore")。
// 插件在多线程渲染期用它们保护共享状态——ofxs 支持库的
// ofxsThreadSuiteCheck 在 load 时逐一检查这些函数非空,缺一个整批插件
// 直接拒载。
/// 计数信号量(count==1 时即普通互斥锁)。
struct OfxMutex {
permits: std::sync::Mutex<c_int>,
released: std::sync::Condvar,
}
/// 句柄注册表(句柄即下一个递增 id 转指针;0 保留给空)。
static MUTEXES: std::sync::LazyLock<Mutex<HashMap<usize, Arc<OfxMutex>>>> =
std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
/// 下一个互斥锁 id。
static NEXT_MUTEX: AtomicUsize = AtomicUsize::new(1);
fn mutex_lookup(handle: *mut c_void) -> Option<Arc<OfxMutex>> {
if handle.is_null() {
return None;
}
MUTEXES
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&(handle as usize))
.cloned()
}
unsafe extern "C" fn mutex_create(out: *mut *mut c_void, count: c_int) -> c_int {
caught(|| {
if out.is_null() {
return status::ERR_VALUE;
}
let id = NEXT_MUTEX.fetch_add(1, Ordering::Relaxed);
MUTEXES
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(id, Arc::new(OfxMutex {
permits: std::sync::Mutex::new(count.max(1)),
released: std::sync::Condvar::new(),
}));
unsafe { *out = id as *mut c_void };
status::OK
})
}
unsafe extern "C" fn mutex_destroy(handle: *mut c_void) -> c_int {
caught(|| {
if mutex_lookup(handle).is_none() {
return status::ERR_BAD_HANDLE;
}
MUTEXES
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&(handle as usize));
status::OK
})
}
unsafe extern "C" fn mutex_lock(handle: *mut c_void) -> c_int {
caught(|| {
let Some(m) = mutex_lookup(handle) else {
return status::ERR_BAD_HANDLE;
};
let mut permits = m.permits.lock().unwrap_or_else(|e| e.into_inner());
while *permits <= 0 {
permits = m.released.wait(permits).unwrap_or_else(|e| e.into_inner());
}
*permits -= 1;
status::OK
})
}
unsafe extern "C" fn mutex_unlock(handle: *mut c_void) -> c_int {
caught(|| {
let Some(m) = mutex_lookup(handle) else {
return status::ERR_BAD_HANDLE;
};
{
let mut permits = m.permits.lock().unwrap_or_else(|e| e.into_inner());
*permits += 1;
}
m.released.notify_one();
status::OK
})
}
unsafe extern "C" fn mutex_try_lock(handle: *mut c_void) -> c_int {
caught(|| {
let Some(m) = mutex_lookup(handle) else {
return status::ERR_BAD_HANDLE;
};
let mut permits = m.permits.lock().unwrap_or_else(|e| e.into_inner());
if *permits <= 0 {
// 规范:占不到锁返回 kOfxStatFailed(不是错误)。
return status::FAILED;
}
*permits -= 1;
status::OK
})
}
/// 静态函数表实例。
pub fn suite_v1() -> &'static MultiThreadSuiteV1 {
static SUITE: std::sync::OnceLock<MultiThreadSuiteV1> = std::sync::OnceLock::new();
SUITE.get_or_init(|| MultiThreadSuiteV1 {
multi_thread,
num_cpus: multi_thread_num_cpus,
index: multi_thread_index,
is_spawned: multi_thread_is_spawned,
mutex_create,
mutex_destroy,
mutex_lock,
mutex_unlock,
mutex_try_lock,
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
/// 每个插件线程把 (index, count) 累计进 userdata。
unsafe extern "C" fn worker(index: c_uint, count: c_uint, arg: *mut c_void) {
let state = unsafe { &mut *(arg as *mut (AtomicUsize, AtomicUsize)) };
state.0.fetch_add(index as usize, Ordering::Relaxed);
state.1.fetch_add(count as usize, Ordering::Relaxed);
}
#[test]
fn multi_thread_spawns_and_joins() {
let s = suite_v1();
let state = (AtomicUsize::new(0), AtomicUsize::new(0));
unsafe {
assert_eq!(
(s.multi_thread)(worker, 8, &state as *const _ as *mut c_void),
status::OK
);
}
// 索引 0..7 各一次 → 28;count 各 8 → 64。
assert_eq!(state.0.load(Ordering::Relaxed), 28);
assert_eq!(state.1.load(Ordering::Relaxed), 64);
// 0 线程:no-op OK。
unsafe {
assert_eq!(
(s.multi_thread)(worker, 0, &state as *const _ as *mut c_void),
status::OK
);
}
}
/// 插件线程内查询 index/isSpawned 并把结果写回 arg 指向的槽。
/// (闭包捕获 suite 无法转 fn 指针,用具名函数 + 静态槽。)
static SPAWNED_RESULT: std::sync::LazyLock<std::sync::Mutex<Option<(c_int, c_int)>>> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(None));
unsafe extern "C" fn query_worker(_index: c_uint, _count: c_uint, _arg: *mut c_void) {
let s = suite_v1();
let mut i = 0;
let mut sp = 1;
unsafe {
let _ = (s.index)(&mut i);
let _ = (s.is_spawned)(&mut sp);
}
*SPAWNED_RESULT.lock().unwrap_or_else(|e| e.into_inner()) = Some((i, sp));
}
#[test]
fn index_and_spawned() {
let s = suite_v1();
let mut i = 0;
let mut spawned = 1;
unsafe {
// 宿主线程:index = -1,isSpawned = 0。
assert_eq!((s.index)(&mut i), status::OK);
assert_eq!(i, -1);
assert_eq!((s.is_spawned)(&mut spawned), status::OK);
assert_eq!(spawned, 0);
// 插件线程内:index 正确、isSpawned = 1。
assert_eq!(
(s.multi_thread)(query_worker, 1, std::ptr::null_mut()),
status::OK
);
assert_eq!(
*SPAWNED_RESULT.lock().unwrap_or_else(|e| e.into_inner()),
Some((0, 1))
);
}
}
#[test]
fn num_cpus_positive() {
let s = suite_v1();
let mut n = 0;
unsafe {
assert_eq!((s.num_cpus)(&mut n), status::OK);
}
assert!(n >= 1);
}
}