// 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 . //! OfxProgressSuite v1/v2:进度 → 当前渲染的 //! [`crate::progress::ProgressReporter`]。 //! //! 定位方式:TLS(render 驱动在调用插件 action 前 //! [`set_current`],action 返回后清除)——进度只在渲染期内有意义, //! 不设全局状态(progress.rs 文档)。 //! 语义:update 返回 false(取消)→ kOfxStatReplyNo //! (HS: ofxhImageEffect.cpp gProgressSuite);无报告器(渲染外 //! 调用)→ 静默 OK。start/end 为括号,OK。 use std::ffi::{c_char, c_double, c_int, c_void}; use crate::progress::ProgressReporter; use crate::suites::status; thread_local! { static CURRENT: std::cell::RefCell> = const { std::cell::RefCell::new(None) }; } /// 设置/清除当前报告器(render 驱动调用;公开:render 驱动在 /// host 层,测试也直接注入)。 pub fn set_current(reporter: Option) { CURRENT.with(|c| *c.borrow_mut() = reporter); } /// 读取当前报告器的取消状态(image effect suite 的 abort 用)。 pub(crate) fn is_cancelled() -> bool { CURRENT.with(|c| c.borrow().as_ref().is_some_and(|r| r.is_cancelled())) } /// 进度更新:取消 → kOfxStatReplyNo;无报告器/未取消 → OK。 fn progress_update(progress: f64) -> c_int { let cancelled = CURRENT.with(|c| match c.borrow().as_ref() { Some(r) => !r.update(progress), None => false, }); if cancelled { status::REPLY_NO } else { status::OK } } /// 公共入口模板:panic 兜底。 fn caught(f: impl FnOnce() -> c_int) -> c_int { std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or(status::FAILED) } /// 函数表布局(OfxProgressSuiteV1)。 #[repr(C)] pub struct ProgressSuiteV1 { /// progressStart pub start: unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int, /// progressUpdate(返回非 kOfxStatOK 即取消) pub update: unsafe extern "C" fn(*mut c_void, c_double) -> c_int, /// progressEnd pub end: unsafe extern "C" fn(*mut c_void) -> c_int, } /// 函数表布局(OfxProgressSuiteV2:start 带 label + message)。 #[repr(C)] pub struct ProgressSuiteV2 { /// progressStart pub start: unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char) -> c_int, /// progressUpdate pub update: unsafe extern "C" fn(*mut c_void, c_double) -> c_int, /// progressEnd pub end: unsafe extern "C" fn(*mut c_void) -> c_int, } /// C 字符串解码(空指针/非法 UTF-8 → 空串)。 unsafe fn decode<'a>(p: *const c_char) -> &'a str { if p.is_null() { return ""; } unsafe { std::ffi::CStr::from_ptr(p) } .to_str() .unwrap_or("") } /// progressStart:括号起点;携 label(v2 另携 message)经 /// [`crate::progress`] 工厂现造 UI 报告器(无工厂/已有报告器时 /// no-op),状态恒 OK。 unsafe extern "C" fn progress_start_v1(_handle: *mut c_void, label: *const c_char) -> c_int { caught(|| { let label = unsafe { decode(label) }.to_string(); CURRENT.with(|c| { if let Some(r) = c.borrow().as_ref() { r.install_ui(&label, ""); } }); status::OK }) } unsafe extern "C" fn progress_update_v1(_handle: *mut c_void, progress: c_double) -> c_int { caught(|| progress_update(progress)) } unsafe extern "C" fn progress_end_v1(_handle: *mut c_void) -> c_int { caught(|| status::OK) } unsafe extern "C" fn progress_start_v2( _handle: *mut c_void, label: *const c_char, message: *const c_char, ) -> c_int { caught(|| { let label = unsafe { decode(label) }.to_string(); let message = unsafe { decode(message) }.to_string(); CURRENT.with(|c| { if let Some(r) = c.borrow().as_ref() { r.install_ui(&label, &message); } }); status::OK }) } unsafe extern "C" fn progress_update_v2(_handle: *mut c_void, progress: c_double) -> c_int { caught(|| progress_update(progress)) } unsafe extern "C" fn progress_end_v2(_handle: *mut c_void) -> c_int { caught(|| status::OK) } /// 静态函数表实例(v1)。 pub fn suite_v1() -> &'static ProgressSuiteV1 { static SUITE: std::sync::OnceLock = std::sync::OnceLock::new(); SUITE.get_or_init(|| ProgressSuiteV1 { start: progress_start_v1, update: progress_update_v1, end: progress_end_v1, }) } /// 静态函数表实例(v2)。 pub fn suite_v2() -> &'static ProgressSuiteV2 { static SUITE: std::sync::OnceLock = std::sync::OnceLock::new(); SUITE.get_or_init(|| ProgressSuiteV2 { start: progress_start_v2, update: progress_update_v2, end: progress_end_v2, }) } #[cfg(test)] mod tests { use super::*; /// 取消型出口(头文件契约:非 0 = 中止)。 unsafe extern "C" fn cancel_cb(_p: f64, _userdata: *mut c_void) -> c_int { 1 } /// 静默:update 恒 OK;带取消回调:update → REPLY_NO 且 abort /// 查询为真(粘滞)。 #[test] fn update_cancel_and_abort() { // 无报告器(渲染外):静默 OK。 set_current(None); let s = suite_v1(); unsafe { assert_eq!((s.update)(std::ptr::null_mut(), 0.5), status::OK); assert_eq!( (s.start)(std::ptr::null_mut(), std::ptr::null()), status::OK ); assert_eq!((s.end)(std::ptr::null_mut()), status::OK); } // 带取消回调:update → REPLY_NO;is_cancelled 为真。 set_current(Some(unsafe { ProgressReporter::new(cancel_cb, std::ptr::null_mut()) })); unsafe { assert_eq!((s.update)(std::ptr::null_mut(), 0.5), status::REPLY_NO); } assert!(is_cancelled()); set_current(None); assert!(!is_cancelled()); } }