Skip to main content

aviutl2\filter/
bridge.rs

1use crate::{
2    common::{AnyResult, LeakManager},
3    filter::{
4        AudioObjectInfo, FilterConfigItem, FilterPlugin, FilterPluginTable, FilterProcAudio,
5        FilterProcVideo, FilterUserdata, FilterUserdataHandle, ObjectInfo, SceneInfo,
6        VideoObjectInfo,
7    },
8    utils::catch_unwind_with_panic_info,
9};
10
11type UserdataContainer<T> = std::sync::Arc<parking_lot::RwLock<T>>;
12
13impl<T: FilterUserdata> FilterProcAudio<T> {
14    unsafe fn from_raw(
15        raw_ptr: *const aviutl2_sys::filter2::FILTER_PROC_AUDIO,
16    ) -> FilterProcAudio<T> {
17        let raw = unsafe { &*raw_ptr };
18        FilterProcAudio {
19            scene: unsafe { SceneInfo::from_raw(raw.scene) },
20            object: unsafe { ObjectInfo::from_raw(raw.object) },
21            audio_object: unsafe { AudioObjectInfo::from_raw(raw.object) },
22            read_section: unsafe { crate::generic::ReadSection::from_raw(raw.edit) },
23            param: unsafe { (&*raw.param).into() },
24            userdata: unsafe { userdata_handle_from_raw::<T>(raw.userdata) },
25            inner: raw_ptr,
26        }
27    }
28}
29impl<T: FilterUserdata> FilterProcVideo<T> {
30    unsafe fn from_raw(
31        raw_ptr: *const aviutl2_sys::filter2::FILTER_PROC_VIDEO,
32    ) -> FilterProcVideo<T> {
33        let raw = unsafe { &*raw_ptr };
34        FilterProcVideo {
35            scene: unsafe { SceneInfo::from_raw(raw.scene) },
36            object: unsafe { ObjectInfo::from_raw(raw.object) },
37            video_object: unsafe { VideoObjectInfo::from_raw(raw.object) },
38            param: unsafe { (&*raw.param).into() },
39            read_section: unsafe { crate::generic::ReadSection::from_raw(raw.edit) },
40            userdata: unsafe { userdata_handle_from_raw::<T>(raw.userdata) },
41            prevent_post_effect: false,
42            inner: raw_ptr,
43        }
44    }
45}
46
47unsafe fn userdata_handle_from_raw<T: FilterUserdata>(
48    userdata: *mut std::ffi::c_void,
49) -> FilterUserdataHandle<T> {
50    if std::any::TypeId::of::<T>() == std::any::TypeId::of::<()>() {
51        assert!(
52            userdata.is_null(),
53            "unit filter userdata pointer must be null"
54        );
55        let unit = ();
56        let value = unsafe { std::ptr::read((&raw const unit).cast::<T>()) };
57        return FilterUserdataHandle::new(std::sync::Arc::new(parking_lot::RwLock::new(value)));
58    }
59    assert!(
60        !userdata.is_null(),
61        "filter userdata pointer must not be null"
62    );
63    let userdata = unsafe { &*userdata.cast::<UserdataContainer<T>>() };
64    FilterUserdataHandle::new(std::sync::Arc::clone(userdata))
65}
66
67impl SceneInfo {
68    unsafe fn from_raw(raw: *const aviutl2_sys::filter2::SCENE_INFO) -> SceneInfo {
69        let raw = unsafe { &*raw };
70        SceneInfo {
71            width: raw.width as u32,
72            height: raw.height as u32,
73            frame_rate: num_rational::Rational32::new(raw.rate, raw.scale),
74            sample_rate: raw.sample_rate as u32,
75        }
76    }
77}
78impl ObjectInfo {
79    unsafe fn from_raw(raw: *const aviutl2_sys::filter2::OBJECT_INFO) -> ObjectInfo {
80        let raw = unsafe { &*raw };
81        ObjectInfo {
82            id: raw.id,
83            effect_id: raw.effect_id,
84            layer: raw.layer as u32,
85            frame: raw.frame as u32,
86            frame_total: raw.frame_total as u32,
87            time: raw.time,
88            time_total: raw.time_total,
89            is_filter_object: (raw.flag & aviutl2_sys::filter2::OBJECT_INFO::FLAG_FILTER_OBJECT)
90                != 0,
91            frame_s: raw.frame_s as u32,
92            frame_e: raw.frame_e as u32,
93            effect_layer: raw.effect_layer as u32,
94            origin_frame: raw.origin_frame as u32,
95        }
96    }
97}
98impl VideoObjectInfo {
99    unsafe fn from_raw(raw: *const aviutl2_sys::filter2::OBJECT_INFO) -> VideoObjectInfo {
100        let raw = unsafe { &*raw };
101        VideoObjectInfo {
102            width: raw.width as u32,
103            height: raw.height as u32,
104            index: raw.index as u32,
105            num: if raw.num == 0 {
106                None
107            } else {
108                Some(raw.num as u32)
109            },
110        }
111    }
112}
113impl AudioObjectInfo {
114    unsafe fn from_raw(raw: *const aviutl2_sys::filter2::OBJECT_INFO) -> AudioObjectInfo {
115        let raw = unsafe { &*raw };
116        AudioObjectInfo {
117            sample_index: raw.sample_index as u64,
118            sample_total: raw.sample_total as u64,
119            sample_num: raw.sample_num as u32,
120            channel_num: raw.channel_num as u32,
121        }
122    }
123}
124
125pub struct InternalFilterPluginState<T: Send + Sync + FilterPlugin> {
126    plugin_info: FilterPluginTable,
127    global_leak_manager: LeakManager,
128    leak_manager: LeakManager,
129    config_pointers: Vec<*const aviutl2_sys::filter2::FILTER_ITEM>,
130    config_items: Vec<FilterConfigItem>,
131
132    instance: T,
133}
134unsafe impl<T: Send + Sync + FilterPlugin> Send for InternalFilterPluginState<T> {}
135unsafe impl<T: Send + Sync + FilterPlugin> Sync for InternalFilterPluginState<T> {}
136
137impl<T: Send + Sync + FilterPlugin> InternalFilterPluginState<T> {
138    pub fn new(instance: T) -> Self {
139        let plugin_info = instance.plugin_info();
140        let config_items = plugin_info.config_items.clone();
141        Self {
142            plugin_info,
143            global_leak_manager: LeakManager::new(),
144            leak_manager: LeakManager::new(),
145            config_pointers: Vec::new(),
146            config_items,
147
148            instance,
149        }
150    }
151
152    pub fn should_apply_configs(&self) -> bool {
153        for (item, raw) in self.config_items.iter().zip(self.config_pointers.iter()) {
154            if unsafe { item.should_apply_from_raw(*raw) } {
155                return true;
156            }
157        }
158        false
159    }
160
161    pub fn apply_configs(&mut self) {
162        for (item, raw) in self
163            .config_items
164            .iter_mut()
165            .zip(self.config_pointers.iter())
166        {
167            unsafe { item.apply_from_raw(*raw) };
168        }
169    }
170}
171
172fn update_configs<T: Send + Sync + FilterPlugin>(
173    plugin_state: &std::sync::RwLock<Option<InternalFilterPluginState<T>>>,
174) {
175    // AviUtl2 -> aviutl2-rsの設定の反映は2回行っても特に問題ないはずなので、
176    // read()ロックをアップグレードしてロックが途切れないようにするといった
177    // 高等テクニックは使わない。
178    let plugin_lock = plugin_state.read().unwrap();
179    let plugin = plugin_lock.as_ref().expect("Plugin not initialized");
180    if plugin.should_apply_configs() {
181        drop(plugin_lock);
182        plugin_state
183            .write()
184            .unwrap()
185            .as_mut()
186            .unwrap()
187            .apply_configs();
188    }
189}
190
191pub trait FilterSingleton
192where
193    Self: 'static + Send + Sync + crate::filter::FilterPlugin,
194{
195    fn __get_singleton_state()
196    -> &'static std::sync::RwLock<Option<crate::filter::__bridge::InternalFilterPluginState<Self>>>;
197    fn with_instance<R>(f: impl FnOnce(&Self) -> R) -> R {
198        let lock = Self::__get_singleton_state();
199        let guard = lock.read().unwrap();
200        let state = guard.as_ref().expect("Plugin not initialized");
201        f(&state.instance)
202    }
203    fn with_instance_mut<R>(f: impl FnOnce(&mut Self) -> R) -> R {
204        let lock = Self::__get_singleton_state();
205        let mut guard = lock.write().unwrap();
206        let state = guard.as_mut().expect("Plugin not initialized");
207        f(&mut state.instance)
208    }
209}
210
211pub unsafe fn initialize_plugin_c<T: FilterSingleton>(version: u32) -> bool {
212    match initialize_plugin::<T>(version) {
213        Ok(_) => true,
214        Err(e) => {
215            tracing::error!("Failed to initialize plugin: {}", e);
216            let _ = crate::logger::write_error_log(&format!("{e}"));
217            false
218        }
219    }
220}
221
222pub unsafe fn initialize_plugin_c_unwind<T: FilterSingleton>(version: u32) -> bool {
223    match catch_unwind_with_panic_info(|| unsafe { initialize_plugin_c::<T>(version) }) {
224        Ok(result) => result,
225        Err(panic_info) => {
226            tracing::error!(
227                "Panic occurred during plugin initialization: {}",
228                panic_info
229            );
230            let _ = crate::logger::write_error_log(&panic_info);
231            false
232        }
233    }
234}
235
236pub(crate) fn initialize_plugin<T: FilterSingleton>(version: u32) -> AnyResult<()> {
237    crate::common::ensure_minimum_aviutl2_version(version.into())?;
238    let plugin_state = T::__get_singleton_state();
239    let info = crate::common::AviUtl2Info {
240        version: version.into(),
241    };
242    let internal = T::new(info)?;
243    let plugin = InternalFilterPluginState::new(internal);
244    *plugin_state.write().unwrap() = Some(plugin);
245
246    Ok(())
247}
248pub unsafe fn uninitialize_plugin<T: FilterSingleton>() {
249    let plugin_state = T::__get_singleton_state();
250    let mut plugin_state = plugin_state.write().unwrap();
251    *plugin_state = None;
252}
253
254pub unsafe fn uninitialize_plugin_c_unwind<T: FilterSingleton>() {
255    match crate::utils::catch_unwind_with_panic_info(|| unsafe { uninitialize_plugin::<T>() }) {
256        Ok(()) => {}
257        Err(panic_info) => {
258            tracing::error!(
259                "Panic occurred during plugin uninitialization: {}",
260                panic_info
261            );
262            let _ = crate::logger::write_error_log(&panic_info);
263        }
264    }
265}
266fn create_table_impl<T: FilterSingleton>(
267    unwind: bool,
268) -> *mut aviutl2_sys::filter2::FILTER_PLUGIN_TABLE {
269    let plugin_state = T::__get_singleton_state();
270    let mut plugin_state = plugin_state.write().unwrap();
271    let plugin_state = plugin_state.as_mut().expect("Plugin not initialized");
272    let plugin_info = &plugin_state.plugin_info;
273
274    let name = plugin_info.name.clone();
275    let information = plugin_info.information.clone();
276
277    let config_items = plugin_info
278        .config_items
279        .iter()
280        .map(|item| {
281            plugin_state
282                .global_leak_manager
283                .leak(item.to_raw(&plugin_state.global_leak_manager))
284        })
285        .collect::<Vec<_>>();
286    plugin_state.config_pointers = config_items.to_vec();
287    // null終端
288    plugin_state
289        .config_pointers
290        .push(std::ptr::null::<aviutl2_sys::filter2::FILTER_ITEM>());
291    let config_items = plugin_state.global_leak_manager.leak_value_vec(
292        plugin_state
293            .config_pointers
294            .iter()
295            .map(|p| *p as usize)
296            .collect(),
297    );
298
299    let func_proc_video = if unwind {
300        func_proc_video_unwind::<T>
301    } else {
302        func_proc_video::<T>
303    };
304    let func_proc_audio = if unwind {
305        func_proc_audio_unwind::<T>
306    } else {
307        func_proc_audio::<T>
308    };
309    let uses_userdata = std::any::TypeId::of::<T::Userdata>() != std::any::TypeId::of::<()>();
310    let (func_create, func_destroy) = if uses_userdata {
311        if unwind {
312            (
313                Some(func_create_unwind::<T> as extern "C" fn(i64) -> *mut std::ffi::c_void),
314                Some(func_destroy_unwind::<T> as extern "C" fn(i64, *mut std::ffi::c_void)),
315            )
316        } else {
317            (
318                Some(func_create::<T> as extern "C" fn(i64) -> *mut std::ffi::c_void),
319                Some(func_destroy::<T> as extern "C" fn(i64, *mut std::ffi::c_void)),
320            )
321        }
322    } else {
323        (None, None)
324    };
325    let flag = if uses_userdata {
326        plugin_info.flags.to_bits() | aviutl2_sys::filter2::FILTER_PLUGIN_TABLE::FLAG_USERDATA
327    } else {
328        plugin_info.flags.to_bits()
329    };
330
331    // NOTE: プラグイン名などの文字列はAviUtlが終了するまで解放しない
332    let table = aviutl2_sys::filter2::FILTER_PLUGIN_TABLE {
333        flag,
334        name: plugin_state.global_leak_manager.leak_as_wide_string(&name),
335        information: plugin_state
336            .global_leak_manager
337            .leak_as_wide_string(&information),
338        label: plugin_info.label.as_ref().map_or(std::ptr::null(), |s| {
339            plugin_state.global_leak_manager.leak_as_wide_string(s)
340        }),
341        items: config_items as _,
342        func_proc_video: Some(func_proc_video),
343        func_proc_audio: Some(func_proc_audio),
344        func_create,
345        func_destroy,
346    };
347    let table = Box::new(table);
348    Box::leak(table)
349}
350
351pub unsafe fn create_table<T: FilterSingleton>() -> *mut aviutl2_sys::filter2::FILTER_PLUGIN_TABLE {
352    create_table_impl::<T>(false)
353}
354
355pub unsafe fn create_table_unwind<T: FilterSingleton>()
356-> *mut aviutl2_sys::filter2::FILTER_PLUGIN_TABLE {
357    match crate::utils::catch_unwind_with_panic_info(|| create_table_impl::<T>(true)) {
358        Ok(table) => table,
359        Err(panic_info) => {
360            tracing::error!("Panic occurred during create_table: {}", panic_info);
361            let _ = crate::logger::write_error_log(&panic_info);
362            std::ptr::null_mut()
363        }
364    }
365}
366
367fn proc_video_impl<T: FilterSingleton>(
368    video: *mut aviutl2_sys::filter2::FILTER_PROC_VIDEO,
369) -> AnyResult<bool> {
370    let plugin_lock = T::__get_singleton_state();
371    anyhow::ensure!(!plugin_lock.is_poisoned(), "Plugin state lock is poisoned");
372    update_configs::<T>(plugin_lock);
373    let plugin_state = plugin_lock.read().unwrap();
374    let plugin_state = plugin_state.as_ref().expect("Plugin not initialized");
375
376    plugin_state.leak_manager.free_leaked_memory();
377    let plugin = &plugin_state.instance;
378    let mut video = unsafe { FilterProcVideo::<T::Userdata>::from_raw(video) };
379    plugin.proc_video(&plugin_state.config_items, &mut video)?;
380    video.apply_param();
381    Ok(video.prevent_post_effect)
382}
383
384fn proc_audio_impl<T: FilterSingleton>(
385    audio: *mut aviutl2_sys::filter2::FILTER_PROC_AUDIO,
386) -> AnyResult<()> {
387    let plugin_lock = T::__get_singleton_state();
388    update_configs::<T>(plugin_lock);
389    let plugin_state = plugin_lock.read().unwrap();
390    let plugin_state = plugin_state.as_ref().expect("Plugin not initialized");
391    plugin_state.leak_manager.free_leaked_memory();
392    let plugin = &plugin_state.instance;
393    let mut audio = unsafe { FilterProcAudio::<T::Userdata>::from_raw(audio) };
394    plugin.proc_audio(&plugin_state.config_items, &mut audio)?;
395    audio.apply_param();
396    Ok(())
397}
398
399fn create_userdata_impl<T: FilterSingleton>(effect_id: i64) -> *mut std::ffi::c_void {
400    let userdata = std::sync::Arc::new(parking_lot::RwLock::new(T::Userdata::new(effect_id)));
401    Box::into_raw(Box::new(userdata)).cast()
402}
403
404fn destroy_userdata_impl<T: FilterSingleton>(userdata: *mut std::ffi::c_void) {
405    assert!(
406        !userdata.is_null(),
407        "filter userdata pointer must not be null"
408    );
409    unsafe {
410        drop(Box::from_raw(
411            userdata.cast::<UserdataContainer<T::Userdata>>(),
412        ));
413    }
414}
415
416extern "C" fn func_create<T: FilterSingleton>(effect_id: i64) -> *mut std::ffi::c_void {
417    create_userdata_impl::<T>(effect_id)
418}
419
420extern "C" fn func_create_unwind<T: FilterSingleton>(effect_id: i64) -> *mut std::ffi::c_void {
421    match catch_unwind_with_panic_info(|| create_userdata_impl::<T>(effect_id)) {
422        Ok(userdata) => userdata,
423        Err(panic_info) => {
424            tracing::error!("Panic in filter userdata creation: {}", panic_info);
425            let _ = crate::logger::write_error_log(&panic_info);
426            std::ptr::null_mut()
427        }
428    }
429}
430
431extern "C" fn func_destroy<T: FilterSingleton>(_effect_id: i64, userdata: *mut std::ffi::c_void) {
432    destroy_userdata_impl::<T>(userdata);
433}
434
435extern "C" fn func_destroy_unwind<T: FilterSingleton>(
436    _effect_id: i64,
437    userdata: *mut std::ffi::c_void,
438) {
439    match catch_unwind_with_panic_info(|| destroy_userdata_impl::<T>(userdata)) {
440        Ok(()) => {}
441        Err(panic_info) => {
442            tracing::error!("Panic in filter userdata destruction: {}", panic_info);
443            let _ = crate::logger::write_error_log(&panic_info);
444        }
445    }
446}
447
448extern "C" fn func_proc_video<T: FilterSingleton>(
449    video: *mut aviutl2_sys::filter2::FILTER_PROC_VIDEO,
450) -> bool {
451    match proc_video_impl::<T>(video) {
452        Ok(prevent_post_effect) => !prevent_post_effect,
453        Err(e) => {
454            tracing::error!("Error in proc_video: {}", e);
455            false
456        }
457    }
458}
459extern "C" fn func_proc_video_unwind<T: FilterSingleton>(
460    video: *mut aviutl2_sys::filter2::FILTER_PROC_VIDEO,
461) -> bool {
462    match catch_unwind_with_panic_info(|| proc_video_impl::<T>(video)) {
463        Ok(Ok(prevent_post_effect)) => !prevent_post_effect,
464        Ok(Err(e)) => {
465            tracing::error!("Error in proc_video: {}", e);
466            false
467        }
468        Err(e) => {
469            tracing::error!("Panic in proc_video: {}", e);
470            false
471        }
472    }
473}
474extern "C" fn func_proc_audio<T: FilterSingleton>(
475    audio: *mut aviutl2_sys::filter2::FILTER_PROC_AUDIO,
476) -> bool {
477    match proc_audio_impl::<T>(audio) {
478        Ok(()) => true,
479        Err(e) => {
480            tracing::error!("Error in proc_audio: {}", e);
481            false
482        }
483    }
484}
485extern "C" fn func_proc_audio_unwind<T: FilterSingleton>(
486    audio: *mut aviutl2_sys::filter2::FILTER_PROC_AUDIO,
487) -> bool {
488    match catch_unwind_with_panic_info(|| proc_audio_impl::<T>(audio)) {
489        Ok(Ok(())) => true,
490        Ok(Err(e)) => {
491            tracing::error!("Error in proc_audio: {}", e);
492            false
493        }
494        Err(e) => {
495            tracing::error!("Panic in proc_audio: {}", e);
496            false
497        }
498    }
499}
500
501/// フィルタプラグインを登録するマクロ。
502///
503/// # Arguments
504///
505/// - `unwind`: panic時にunwindするかどうか。デフォルトは`true`。
506#[macro_export]
507macro_rules! register_filter_plugin {
508    ($struct:ident, $($key:ident = $value:expr),* $(,)?) => {
509        $crate::__internal_module! {
510            #[unsafe(no_mangle)]
511            unsafe extern "C" fn RequiredVersion() -> u32 {
512                $crate::MINIMUM_AVIUTL2_VERSION.into()
513            }
514
515            #[unsafe(no_mangle)]
516            unsafe extern "C" fn InitializeLogger(logger: *mut $crate::sys::logger2::LOG_HANDLE) {
517                $crate::comptime_if::comptime_if! {
518                    if unwind where (unwind = true, $( $key = $value ),* ) {
519                        $crate::logger::__initialize_logger_unwind(logger)
520                    } else {
521                        $crate::logger::__initialize_logger(logger)
522                    }
523                }
524            }
525
526            #[unsafe(no_mangle)]
527            unsafe extern "C" fn InitializeConfig(
528                config: *mut $crate::sys::config2::CONFIG_HANDLE
529            ) {
530                $crate::comptime_if::comptime_if! {
531                    if unwind where (unwind = true, $( $key = $value ),* ) {
532                        $crate::config::__initialize_config_handle_unwind(config)
533                    } else {
534                        $crate::config::__initialize_config_handle(config)
535                    }
536                }
537            }
538
539            #[unsafe(no_mangle)]
540            unsafe extern "C" fn InitializeCache(
541                cache: *mut $crate::sys::cache2::CACHE_HANDLE
542            ) {
543                $crate::comptime_if::comptime_if! {
544                    if unwind where (unwind = true, $( $key = $value ),* ) {
545                        $crate::cache::__initialize_cache_unwind(cache)
546                    } else {
547                        $crate::cache::__initialize_cache(cache)
548                    }
549                }
550            }
551
552            #[unsafe(no_mangle)]
553            unsafe extern "C" fn InitializePlugin(version: u32) -> bool {
554                unsafe {
555                    $crate::comptime_if::comptime_if! {
556                        if unwind where (unwind = true, $( $key = $value ),* ) {
557                            $crate::filter::__bridge::initialize_plugin_c_unwind::<$struct>(version)
558                        } else {
559                            $crate::filter::__bridge::initialize_plugin_c::<$struct>(version)
560                        }
561                    }
562                }
563            }
564
565            #[unsafe(no_mangle)]
566            unsafe extern "C" fn UninitializePlugin() {
567                unsafe {
568                    $crate::comptime_if::comptime_if! {
569                        if unwind where (unwind = true, $( $key = $value ),* ) {
570                            $crate::filter::__bridge::uninitialize_plugin_c_unwind::<$struct>()
571                        } else {
572                            $crate::filter::__bridge::uninitialize_plugin::<$struct>()
573                        }
574                    }
575                }
576            }
577
578            #[unsafe(no_mangle)]
579            unsafe extern "C" fn GetFilterPluginTable()
580            -> *mut aviutl2::sys::filter2::FILTER_PLUGIN_TABLE {
581                $crate::comptime_if::comptime_if! {
582                    if unwind where (unwind = true, $( $key = $value ),* ) {
583                        unsafe { $crate::filter::__bridge::create_table_unwind::<$struct>() }
584                    } else {
585                        unsafe { $crate::filter::__bridge::create_table::<$struct>() }
586                    }
587                }
588            }
589        }
590    };
591    ($struct:ident, $($key:ident),* $(,)?) => {
592        $crate::register_filter_plugin!($struct, $( $key = true ),* );
593    };
594    ($struct:ident) => {
595        $crate::register_filter_plugin!($struct, );
596    };
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602
603    static LAST_EFFECT_ID: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1);
604    static DROP_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
605
606    struct TestUserdata {
607        value: i32,
608    }
609
610    impl Drop for TestUserdata {
611        fn drop(&mut self) {
612            DROP_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
613        }
614    }
615
616    impl FilterUserdata for TestUserdata {
617        fn new(effect_id: i64) -> Self {
618            LAST_EFFECT_ID.store(effect_id, std::sync::atomic::Ordering::SeqCst);
619            Self { value: 42 }
620        }
621    }
622
623    struct LockTestUserdata {
624        value: i32,
625    }
626
627    impl FilterUserdata for LockTestUserdata {
628        fn new(_effect_id: i64) -> Self {
629            Self { value: 1 }
630        }
631    }
632
633    struct TestPlugin;
634
635    impl FilterPlugin for TestPlugin {
636        type Userdata = TestUserdata;
637
638        fn new(_info: crate::common::AviUtl2Info) -> crate::common::AnyResult<Self> {
639            Ok(Self)
640        }
641
642        fn plugin_info(&self) -> FilterPluginTable {
643            FilterPluginTable {
644                name: String::new(),
645                label: None,
646                information: String::new(),
647                flags: Default::default(),
648                config_items: Vec::new(),
649            }
650        }
651    }
652
653    impl FilterSingleton for TestPlugin {
654        fn __get_singleton_state()
655        -> &'static std::sync::RwLock<Option<InternalFilterPluginState<Self>>> {
656            static STATE: std::sync::RwLock<Option<InternalFilterPluginState<TestPlugin>>> =
657                std::sync::RwLock::new(None);
658            &STATE
659        }
660    }
661
662    struct UnitPlugin;
663
664    impl FilterPlugin for UnitPlugin {
665        type Userdata = ();
666
667        fn new(_info: crate::common::AviUtl2Info) -> crate::common::AnyResult<Self> {
668            Ok(Self)
669        }
670
671        fn plugin_info(&self) -> FilterPluginTable {
672            FilterPluginTable {
673                name: String::new(),
674                label: None,
675                information: String::new(),
676                flags: Default::default(),
677                config_items: Vec::new(),
678            }
679        }
680    }
681
682    impl FilterSingleton for UnitPlugin {
683        fn __get_singleton_state()
684        -> &'static std::sync::RwLock<Option<InternalFilterPluginState<Self>>> {
685            static STATE: std::sync::RwLock<Option<InternalFilterPluginState<UnitPlugin>>> =
686                std::sync::RwLock::new(None);
687            &STATE
688        }
689    }
690
691    #[test]
692    fn userdata_lifecycle_keeps_value_alive_while_handle_exists() {
693        LAST_EFFECT_ID.store(-1, std::sync::atomic::Ordering::SeqCst);
694        DROP_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
695
696        let raw = create_userdata_impl::<TestPlugin>(123);
697        let handle = unsafe { userdata_handle_from_raw::<TestUserdata>(raw) };
698
699        assert_eq!(
700            LAST_EFFECT_ID.load(std::sync::atomic::Ordering::SeqCst),
701            123
702        );
703        assert_eq!(handle.read().value, 42);
704
705        drop(handle);
706        destroy_userdata_impl::<TestPlugin>(raw);
707        assert_eq!(DROP_COUNT.load(std::sync::atomic::Ordering::SeqCst), 1);
708    }
709
710    #[test]
711    fn userdata_handle_locks_on_demand() {
712        let handle = FilterUserdataHandle::new(std::sync::Arc::new(parking_lot::RwLock::new(
713            LockTestUserdata { value: 1 },
714        )));
715
716        let read = handle.read();
717        assert_eq!(read.value, 1);
718        assert!(handle.try_read().is_some());
719        assert!(handle.try_write().is_none());
720        drop(read);
721
722        {
723            let mut write = handle.write();
724            write.value = 2;
725            assert!(handle.try_read().is_none());
726            assert!(handle.try_write().is_none());
727        }
728        assert_eq!(handle.read().value, 2);
729    }
730
731    #[test]
732    fn unit_userdata_uses_null_sdk_pointer() {
733        let handle = unsafe { userdata_handle_from_raw::<()>(std::ptr::null_mut()) };
734
735        assert_eq!(*handle.read(), ());
736        assert!(handle.try_write().is_some());
737    }
738
739    #[test]
740    fn plugin_table_enables_callbacks_only_for_non_unit_userdata() {
741        *TestPlugin::__get_singleton_state().write().unwrap() =
742            Some(InternalFilterPluginState::new(TestPlugin));
743        let table = create_table_impl::<TestPlugin>(false);
744        let table = unsafe { &*table };
745        assert_ne!(
746            table.flag & aviutl2_sys::filter2::FILTER_PLUGIN_TABLE::FLAG_USERDATA,
747            0
748        );
749        assert!(table.func_create.is_some());
750        assert!(table.func_destroy.is_some());
751        *TestPlugin::__get_singleton_state().write().unwrap() = None;
752
753        *UnitPlugin::__get_singleton_state().write().unwrap() =
754            Some(InternalFilterPluginState::new(UnitPlugin));
755        let table = create_table_impl::<UnitPlugin>(false);
756        let table = unsafe { &*table };
757        assert_eq!(
758            table.flag & aviutl2_sys::filter2::FILTER_PLUGIN_TABLE::FLAG_USERDATA,
759            0
760        );
761        assert!(table.func_create.is_none());
762        assert!(table.func_destroy.is_none());
763        *UnitPlugin::__get_singleton_state().write().unwrap() = None;
764    }
765}