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 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 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 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#[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}