Files
oak-editor/crates/oak-plugin/src/suites/message.rs
T
Mike-Solar 5ab12b937f render: real texture binding, generator layers and iteration feedback in shader passes
- process_shader_job: bind all texture params by name, recurse into nested
  shader payloads (depth cap 8), fall back to frame size without inputs
- run_effect: take iterative_input so dropshadow previous_iteration_in works
- merge: actually composite inputs; keyer mask, opacity modulation, math
  texture ops and mrg generator layers now bind their textures
- transform distort: real fragment-side inverse-matrix sampling
- time offset / time remap: wire NodeBehavior time adjustment hooks
- plugin: fix first-node identity colliding with unbound sentinel
2026-09-09 16:33:29 +08:00

377 lines
12 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/>.
//! OfxMessageSuite v1/v2:插件消息 → 宿主日志。
//!
//! stable Rust 无法定义 C-variadic 函数(c_variadic 仍不稳定),
//! v1 的 `...` 与 v2 的 va_list 入口由 C shim 承载
//! cbits/ofx_message_shim.cbuild.rs 经 `cc` 编译,符号随
//! staticlib 进入 liboakplugin):C 侧 vsnprintf 成定长缓冲后转发
//! 到本模块的 `oak_ofx_message_impl`。
//!
//! 消息出口 = facade 注册的 `oakplugin_message_fn`
//! include/plugin/host.h)。本模块按头文件契约建模。
//! (原 C ABI 出口层已随单库化删除;注册点由 facade 直接调用。)
use std::collections::HashMap;
use std::ffi::{c_char, c_int, c_void, CStr};
use crate::suites::status;
/// facade 消息回调(include/plugin/host.h `oakplugin_message_fn`):
/// `(type, message, userdata) -> OAKPLUGIN_MESSAGE_ANSWER_NO/YES`。
pub(crate) type MessageHandler =
unsafe extern "C" fn(*const c_char, *const c_char, *mut c_void) -> c_int;
/// 消息出口注册表(facade 写入,suite 读;userdata 以 usize 存,
/// 避免裸指针破坏 static 的 Send/Sync 推导)。
static HANDLER: std::sync::Mutex<(Option<MessageHandler>, usize)> =
std::sync::Mutex::new((None, 0));
// ---------------------------------------------------------------------------
// 持久消息计数(检查器效果卡的徽标数据源,阶段 6b)
// ---------------------------------------------------------------------------
//
// C++ 侧 `OlivePluginInstance::persistentErrors_` 按实例累计持久消息,
// 并 emit `node_->message_count_changed()`。这里以实例 handleprops
// 地址,见 [`crate::suites::tag`])为键累计计数;app 检查器经
// [`crate::node_factory::instance_from_id`] 拿实例后按 props 地址查询。
static PERSISTENT: std::sync::OnceLock<std::sync::Mutex<HashMap<usize, usize>>> =
std::sync::OnceLock::new();
fn persistent_slot() -> &'static std::sync::Mutex<HashMap<usize, usize>> {
PERSISTENT.get_or_init(|| std::sync::Mutex::new(HashMap::new()))
}
/// 实例 handle(props 地址)的持久消息数(未登记 → 0)。
pub fn persistent_message_count(handle: usize) -> usize {
persistent_slot()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&handle)
.copied()
.unwrap_or(0)
}
/// 清空某实例的持久消息(实例释放/宿主关闭路径;未登记的键 no-op)。
pub fn clear_persistent_messages(handle: usize) {
persistent_slot()
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&handle);
}
/// 注册/注销消息出口(facade 调用;公开:测试直接注入捕获器)。
pub fn set_handler(f: Option<MessageHandler>, userdata: *mut c_void) {
let mut h = HANDLER.lock().unwrap_or_else(|e| e.into_inner());
*h = (f, userdata as usize);
}
/// 读取当前出口。
fn handler() -> (Option<MessageHandler>, *mut c_void) {
let h = HANDLER.lock().unwrap_or_else(|e| e.into_inner());
(h.0, h.1 as *mut c_void)
}
/// kOfxMessageQuestionofxMessage.h:61):无出口时答"否"。
const K_MESSAGE_QUESTION: &[u8] = b"OfxMessageQuestion";
/// 消息类型是否为 question。
fn is_question(type_: *const c_char) -> bool {
// 空指针已在调用点拦截;此处仍防御(CStr::from_ptr 的调用方义务)。
if type_.is_null() {
return false;
}
unsafe { CStr::from_ptr(type_) }.to_bytes() == K_MESSAGE_QUESTION
}
/// Rust 侧实现(C shim 转发至此;`#[no_mangle]` 保证符号名与 C 侧
/// 引用一致)。语义逐条对照 C++:
/// - type/message 为空 → kOfxStatFailedolivehost.cpp:267vmessage
/// 对 !type || !format 的处理;format 为空的 case 由 shim 以
/// NULL message 转发);
/// - 有出口 → 转发 `(type, message, userdata)`,返回按头文件契约
/// 映射为 REPLY_YES/REPLY_NOolivehost.cpp:277-279 的 handler
/// 路径;非 question 消息插件忽略返回值);
/// - 无出口 → stderr 日志(镜像 olivehost.cpp:282 的 fprintf 默认),
/// question 答"否"olivehost.cpp:283-284)。
///
/// `# Safety``type`/`message` 必须是有效 C 字符串(插件契约,
/// shim 已保证 message 非悬垂——指向其栈缓冲,调用期间有效)。
#[no_mangle]
pub unsafe extern "C" fn oak_ofx_message_impl(
_handle: *mut c_void,
type_: *const c_char,
_id: *const c_char,
message: *const c_char,
) -> c_int {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if type_.is_null() || message.is_null() {
return status::FAILED;
}
// 持久消息:按实例 handle(剥标签后的 props 地址)累计计数
// (徽标数据源;C++ persistentErrors_ 的等价物)。
if !_handle.is_null() {
let key = crate::suites::tag::strip(_handle) as usize;
let mut map = persistent_slot().lock().unwrap_or_else(|e| e.into_inner());
*map.entry(key).or_insert(0) += 1;
drop(map);
}
let (handler, userdata) = handler();
if let Some(h) = handler {
// 头文件契约:返回值 0/1 即答复 NO/YES。
let answer = unsafe { h(type_, message, userdata) };
return if answer != 0 {
status::REPLY_YES
} else {
status::REPLY_NO
};
}
// Headless 默认:stderrC++ fprintf 的 Rust 等价物)。
eprintln!(
"OFX message: {}",
unsafe { CStr::from_ptr(message) }.to_string_lossy()
);
if is_question(type_) {
status::REPLY_NO
} else {
status::OK
}
}))
.unwrap_or(status::FAILED)
}
/// 函数表布局(OfxMessageSuiteV1)。
#[repr(C)]
pub struct MessageSuiteV1 {
/// message(变长参数版;入口在 C shim)
pub message: unsafe extern "C" fn(
*mut c_void,
*const c_char,
*const c_char,
*const c_char,
...
) -> c_int,
}
/// 函数表布局(OfxMessageSuiteV2:第 5 参为平台相关 `va_list`)。
///
/// 真实 ABI 是 C 的 va_listApple arm64 上是结构体按值传递);
/// 声明为不透明指针仅作占位——本表只被插件→C shim 调用,Rust 侧
/// 永不直接调用,指针类型不参与任何实际调用。
#[repr(C)]
pub struct MessageSuiteV2 {
/// messageva_list 版;入口在 C shim
pub message: unsafe extern "C" fn(
*mut c_void,
*const c_char,
*const c_char,
*const c_char,
*mut c_void,
) -> c_int,
}
extern "C" {
/// C shim 的 v1 入口(cbits/ofx_message_shim.c)。
fn ofx_message_shim_v1(
handle: *mut c_void,
type_: *const c_char,
id: *const c_char,
format: *const c_char,
...
) -> c_int;
/// C shim 的 v2 入口(va_list 已由 C 侧消费,Rust 只见不透明值)。
fn ofx_message_shim_v2(
handle: *mut c_void,
type_: *const c_char,
id: *const c_char,
format: *const c_char,
args: *mut c_void,
) -> c_int;
}
/// 静态函数表实例(v1)。
pub fn suite_v1() -> &'static MessageSuiteV1 {
static SUITE: std::sync::OnceLock<MessageSuiteV1> = std::sync::OnceLock::new();
SUITE.get_or_init(|| MessageSuiteV1 {
message: ofx_message_shim_v1,
})
}
/// 静态函数表实例(v2)。
pub fn suite_v2() -> &'static MessageSuiteV2 {
static SUITE: std::sync::OnceLock<MessageSuiteV2> = std::sync::OnceLock::new();
SUITE.get_or_init(|| MessageSuiteV2 {
message: ofx_message_shim_v2,
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
/// 测试共享全局 HANDLER,cargo 默认并行执行——串行化契约用例。
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
/// 捕获型出口:把 (type, message) 记进 userdata 指向的 Vec。
unsafe extern "C" fn capture(
type_: *const c_char,
message: *const c_char,
userdata: *mut c_void,
) -> c_int {
let v = unsafe { &mut *(userdata as *mut Vec<(String, String)>) };
v.push((
unsafe { CStr::from_ptr(type_) }
.to_string_lossy()
.into_owned(),
unsafe { CStr::from_ptr(message) }
.to_string_lossy()
.into_owned(),
));
1
}
/// v1 入口:`...` 在 C shim 侧 vsnprintf 后转发,格式化正确。
#[test]
fn shim_v1_formats_and_forwards() {
let _g = TEST_LOCK.lock().unwrap();
let mut captured: Vec<(String, String)> = Vec::new();
set_handler(Some(capture), &mut captured as *mut _ as *mut c_void);
let s = suite_v1();
let type_ = CString::new("OfxMessageError").unwrap();
let id = CString::new("test-id").unwrap();
let fmt = CString::new("value=%d name=%s").unwrap();
let name = CString::new("oak").unwrap();
let r = unsafe {
(s.message)(
std::ptr::null_mut(),
type_.as_ptr(),
id.as_ptr(),
fmt.as_ptr(),
42,
name.as_ptr(),
)
};
// 出口答 YES → REPLY_YES。
assert_eq!(r, status::REPLY_YES);
assert_eq!(captured.len(), 1);
assert_eq!(captured[0].0, "OfxMessageError");
assert_eq!(captured[0].1, "value=42 name=oak");
set_handler(None, std::ptr::null_mut());
}
/// v1 入口:NULL format → kOfxStatFailedC++ olivehost.cpp:267)。
#[test]
fn shim_v1_null_format_fails() {
let _g = TEST_LOCK.lock().unwrap();
let s = suite_v1();
let type_ = CString::new("OfxMessageError").unwrap();
let id = CString::new("id").unwrap();
let r = unsafe {
(s.message)(
std::ptr::null_mut(),
type_.as_ptr(),
id.as_ptr(),
std::ptr::null(),
)
};
assert_eq!(r, status::FAILED);
}
/// v2 入口:表字段非空且指向 C shim 符号(va_list 路径无法从
/// Rust 构造 va_list 调用,链的其余部分与 v1 共用 forward)。
#[test]
fn shim_v2_table_entry_resolves() {
let _g = TEST_LOCK.lock().unwrap();
let s = suite_v2();
assert!(std::ptr::eq(
s.message as *const (),
ofx_message_shim_v2 as *const ()
));
}
/// 无出口(headless 默认):非 question → OKquestion → REPLY_NO。
#[test]
fn no_handler_headless_defaults() {
let _g = TEST_LOCK.lock().unwrap();
set_handler(None, std::ptr::null_mut());
let s = suite_v1();
let type_ = CString::new("OfxMessageError").unwrap();
let id = CString::new("id").unwrap();
let fmt = CString::new("hello %s").unwrap();
let arg = CString::new("x").unwrap();
let r = unsafe {
(s.message)(
std::ptr::null_mut(),
type_.as_ptr(),
id.as_ptr(),
fmt.as_ptr(),
arg.as_ptr(),
)
};
assert_eq!(r, status::OK);
let q = CString::new("OfxMessageQuestion").unwrap();
let r = unsafe {
(s.message)(
std::ptr::null_mut(),
q.as_ptr(),
id.as_ptr(),
fmt.as_ptr(),
arg.as_ptr(),
)
};
assert_eq!(r, status::REPLY_NO);
}
/// 持久消息按实例 handle 累计(徽标数据源);clear 摘除。
#[test]
fn persistent_messages_accumulate_per_handle() {
let _g = TEST_LOCK.lock().unwrap();
set_handler(None, std::ptr::null_mut());
let props = crate::property::PropertySet::new();
let handle = crate::suites::tag::make(&props, crate::suites::tag::INSTANCE);
let key = crate::suites::tag::strip(handle) as usize;
clear_persistent_messages(key);
let type_ = CString::new("OfxMessageError").unwrap();
let id = CString::new("id").unwrap();
let msg = CString::new("boom").unwrap();
unsafe {
oak_ofx_message_impl(handle, type_.as_ptr(), id.as_ptr(), msg.as_ptr());
oak_ofx_message_impl(handle, type_.as_ptr(), id.as_ptr(), msg.as_ptr());
}
assert_eq!(persistent_message_count(key), 2);
clear_persistent_messages(key);
assert_eq!(persistent_message_count(key), 0);
// 空 handle 不计数。
unsafe {
oak_ofx_message_impl(
std::ptr::null_mut(),
type_.as_ptr(),
id.as_ptr(),
msg.as_ptr(),
);
}
assert_eq!(persistent_message_count(key), 0);
}
}