Skip to main content

aviutl2\filter/
binding.rs

1use parking_lot::lock_api::RawRwLock;
2use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
3
4use super::{ErasedFilterConfigData, config};
5use crate::common::Rational32;
6
7/// 入力プラグインの情報を表す構造体。
8#[derive(Debug, Clone)]
9pub struct FilterPluginTable {
10    /// プラグインの名前。
11    pub name: String,
12    /// ラベルの初期値。
13    /// Noneの場合、デフォルトのラベルになります
14    pub label: Option<String>,
15    /// プラグインの情報。
16    /// 「プラグイン情報」ダイアログで表示されます。
17    pub information: String,
18
19    /// 対応している機能のフラグ。
20    pub flags: FilterPluginFlags,
21
22    /// 設定項目。
23    pub config_items: Vec<config::FilterConfigItem>,
24}
25
26define_bitflag! {
27    /// フィルタプラグインのフラグ。
28    ///
29    /// # See Also
30    ///
31    /// - [`crate::bitflag!`]
32    #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
33    #[non_exhaustive]
34    pub struct FilterPluginFlags: i32 {
35        /// 画像フィルタをサポートするかどうか。
36        video: aviutl2_sys::filter2::FILTER_PLUGIN_TABLE::FLAG_VIDEO,
37
38        /// 音声フィルタをサポートするかどうか。
39        audio: aviutl2_sys::filter2::FILTER_PLUGIN_TABLE::FLAG_AUDIO,
40
41        /// 入力として動作するかどうか。
42        /// `true` の場合、カスタムオブジェクトとして動作します。
43        /// `false` の場合、フィルタ効果として動作します。
44        input: aviutl2_sys::filter2::FILTER_PLUGIN_TABLE::FLAG_INPUT,
45
46        /// フィルタオブジェクトをサポートするかどうか。
47        /// `true` の場合、フィルタオブジェクトとして使えるようになります。
48        filter: aviutl2_sys::filter2::FILTER_PLUGIN_TABLE::FLAG_FILTER,
49    }
50}
51
52/// フィルタプラグインのトレイト。
53/// このトレイトを実装し、[`crate::register_filter_plugin!`] マクロを使用してプラグインを登録します。
54pub trait FilterPlugin: Send + Sync + Sized {
55    /// プラグインを初期化する。
56    fn new(info: crate::common::AviUtl2Info) -> crate::common::AnyResult<Self>;
57
58    /// プラグインの情報を返す。
59    fn plugin_info(&self) -> crate::filter::FilterPluginTable;
60
61    /// 画像フィルタ処理関数。
62    ///
63    /// # Note
64    ///
65    /// フィルタオブジェクトの場合、画像サイズは変更できません。
66    fn proc_video(
67        &self,
68        _config: &[crate::filter::FilterConfigItem],
69        _video: &mut crate::filter::FilterProcVideo,
70    ) -> crate::common::AnyResult<()> {
71        anyhow::bail!("proc_video is not implemented");
72    }
73
74    /// 音声フィルタ処理関数。
75    fn proc_audio(
76        &self,
77        _config: &[crate::filter::FilterConfigItem],
78        _audio: &mut crate::filter::FilterProcAudio,
79    ) -> crate::common::AnyResult<()> {
80        anyhow::bail!("proc_audio is not implemented");
81    }
82
83    /// シングルトンインスタンスを参照するためのヘルパーメソッド。
84    ///
85    /// # Panics
86    ///
87    /// プラグインが初期化されていない場合や、二重に呼び出された場合にパニックします。
88    fn with_instance<R>(f: impl FnOnce(&Self) -> R) -> R
89    where
90        Self: crate::filter::__bridge::FilterSingleton,
91    {
92        <Self as crate::filter::__bridge::FilterSingleton>::with_instance(f)
93    }
94
95    /// シングルトンインスタンスを可変参照するためのヘルパーメソッド。
96    ///
97    /// # Panics
98    ///
99    /// プラグインが初期化されていない場合や、二重に呼び出された場合にパニックします。
100    fn with_instance_mut<R>(f: impl FnOnce(&mut Self) -> R) -> R
101    where
102        Self: crate::filter::__bridge::FilterSingleton,
103    {
104        <Self as crate::filter::__bridge::FilterSingleton>::with_instance_mut(f)
105    }
106}
107
108/// シーン情報。
109#[derive(Debug, Clone, Copy)]
110pub struct SceneInfo {
111    /// 解像度(幅)。
112    pub width: u32,
113    /// 解像度(高さ)。
114    pub height: u32,
115    /// フレームレート。
116    pub frame_rate: Rational32,
117    /// サンプリングレート。
118    pub sample_rate: u32,
119}
120
121/// オブジェクト情報。
122#[derive(Debug, Clone, Copy)]
123pub struct ObjectInfo {
124    /// 描画対象のオブジェクトの固有ID。
125    /// アプリ起動ごとの固有IDです。
126    pub id: i64,
127    /// オブジェクトの内の対象エフェクトのID。
128    /// アプリ起動ごとの固有IDです。
129    pub effect_id: i64,
130    /// オブジェクトの現在のフレーム番号。
131    pub frame: u32,
132    /// オブジェクトの総フレーム数。
133    pub frame_total: u32,
134    /// オブジェクトの現在の時間(秒)。
135    pub time: f64,
136    /// オブジェクトの総時間(秒)。
137    pub time_total: f64,
138    /// オブジェクトがフィルタオブジェクトかどうか。
139    pub is_filter_object: bool,
140}
141
142/// 画像フィルタのオブジェクト情報。
143#[derive(Debug, Clone, Copy)]
144pub struct VideoObjectInfo {
145    /// オブジェクトの現在の画像サイズの幅。
146    pub width: u32,
147    /// オブジェクトの現在の画像サイズの高さ。
148    pub height: u32,
149}
150
151/// 音声フィルタのオブジェクト情報。
152#[derive(Debug, Clone, Copy)]
153pub struct AudioObjectInfo {
154    /// オブジェクトの現在の音声サンプル位置。
155    pub sample_index: u64,
156    /// オブジェクトの総サンプル数。
157    pub sample_total: u64,
158    /// オブジェクトの現在の音声サンプル数。
159    pub sample_num: u32,
160    /// オブジェクトの現在の音声チャンネル数。
161    /// 通常2になります。
162    pub channel_num: u32,
163}
164
165/// RGBAのピクセル。
166#[derive(
167    Debug, Default, Clone, Copy, PartialEq, Eq, IntoBytes, FromBytes, Immutable, KnownLayout,
168)]
169pub struct RgbaPixel {
170    /// 赤。
171    pub r: u8,
172    /// 緑。
173    pub g: u8,
174    /// 青。
175    pub b: u8,
176    /// アルファ。
177    pub a: u8,
178}
179
180/// 画像フィルタ処理のための構造体。
181#[derive(Debug)]
182pub struct FilterProcVideo {
183    /// シーン情報。
184    pub scene: SceneInfo,
185    /// オブジェクト情報。
186    pub object: ObjectInfo,
187    /// 画像フィルタ特有のオブジェクト情報。
188    pub video_object: VideoObjectInfo,
189
190    pub(crate) inner: *const aviutl2_sys::filter2::FILTER_PROC_VIDEO,
191}
192unsafe impl Send for FilterProcVideo {}
193unsafe impl Sync for FilterProcVideo {}
194
195impl FilterProcVideo {
196    /// 現在の画像のデータを取得する。
197    /// RGBA32bit で取得されます。
198    ///
199    /// # Panics
200    ///
201    /// `buffer` をバイト列に変換した際の長さが `width * height * 4` と一致しない場合、パニックします。
202    /// 例えば[`u8`] の場合、`buffer` の長さは `width * height * 4` と一致する必要があり、
203    /// [`RgbaPixel`] の場合、`buffer` の長さは `width * height` と一致する必要があります。
204    ///
205    /// # Note
206    ///
207    /// [`FilterPluginFlags::video`] が `true` の場合、この関数は何もせずに 0 を返します。
208    pub fn get_image_data<T>(&mut self, buffer: &mut [T]) -> usize
209    where
210        T: Copy + FromBytes + Immutable,
211    {
212        if self.video_object.width == 0 || self.video_object.height == 0 {
213            tracing::warn!("width or height is 0, perhaps the filter plugin is a custom object");
214            return 0;
215        }
216        assert_eq!(
217            std::mem::size_of_val(buffer),
218            (self.video_object.width * self.video_object.height * 4) as usize,
219            "buffer length as bytes does not match width * height * 4"
220        );
221        assert!(
222            std::mem::align_of::<T>() >= std::mem::align_of::<aviutl2_sys::filter2::PIXEL_RGBA>(),
223            "buffer alignment is not sufficient"
224        );
225        let width = self.video_object.width as usize;
226        let height = self.video_object.height as usize;
227        let inner = unsafe { &*self.inner };
228        unsafe {
229            (inner.get_image_data)(
230                buffer.as_mut_ptr() as *mut u8 as *mut aviutl2_sys::filter2::PIXEL_RGBA
231            )
232        };
233
234        width * height * 4
235    }
236
237    /// 現在の画像のデータを設定する。
238    ///
239    /// # Panics
240    ///
241    /// `data` をバイト列に変換した際の長さが `width * height * 4` と一致しない場合、パニックします。
242    pub fn set_image_data<T: IntoBytes + Immutable>(
243        &mut self,
244        data: &[T],
245        width: u32,
246        height: u32,
247    ) {
248        let bytes = &data.as_bytes();
249        assert_eq!(
250            bytes.len(),
251            (width * height * 4) as usize,
252            "data length does not match width * height * 4"
253        );
254        let inner = unsafe { &*self.inner };
255        unsafe {
256            (inner.set_image_data)(
257                bytes.as_ptr() as *const aviutl2_sys::filter2::PIXEL_RGBA,
258                width as i32,
259                height as i32,
260            )
261        };
262    }
263
264    /// 現在のオブジェクトの画像データのポインタをID3D11Texture2Dのポインタとして取得する。
265    ///
266    /// # Warning
267    ///
268    /// [`Self::set_image_data`] によって現在の画像が変更されるかフィルタ処理の終了まで有効です。
269    pub fn get_image_texture2d(&mut self) -> *mut std::ffi::c_void {
270        let inner = unsafe { &*self.inner };
271        unsafe { (inner.get_image_texture2d)() }
272    }
273
274    /// 現在のフレームバッファの画像データのポインタをID3D11Texture2Dのポインタとして取得する。
275    ///
276    /// # Warning
277    ///
278    /// フィルタ処理の終了まで有効です。
279    pub fn get_framebuffer_texture2d(&mut self) -> *mut std::ffi::c_void {
280        let inner = unsafe { &*self.inner };
281        unsafe { (inner.get_framebuffer_texture2d)() }
282    }
283}
284
285/// 音声フィルタ処理のための構造体。
286#[derive(Debug)]
287pub struct FilterProcAudio {
288    /// シーン情報。
289    pub scene: SceneInfo,
290    /// オブジェクト情報。
291    pub object: ObjectInfo,
292    /// 音声フィルタ特有のオブジェクト情報。
293    pub audio_object: AudioObjectInfo,
294
295    pub(crate) inner: *const aviutl2_sys::filter2::FILTER_PROC_AUDIO,
296}
297
298unsafe impl Send for FilterProcAudio {}
299unsafe impl Sync for FilterProcAudio {}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum AudioChannel {
303    Left,
304    Right,
305    Any(i32),
306}
307impl From<i32> for AudioChannel {
308    fn from(value: i32) -> Self {
309        match value {
310            0 => AudioChannel::Left,
311            1 => AudioChannel::Right,
312            v => AudioChannel::Any(v),
313        }
314    }
315}
316impl From<AudioChannel> for i32 {
317    fn from(value: AudioChannel) -> Self {
318        match value {
319            AudioChannel::Left => 0,
320            AudioChannel::Right => 1,
321            AudioChannel::Any(v) => v,
322        }
323    }
324}
325
326impl FilterProcAudio {
327    /// 現在の音声のデータを取得する。
328    /// `channel` は 0 が左チャンネル、1 が右チャンネルです。
329    ///
330    /// # Panics
331    ///
332    /// `buffer` の長さが `sample_num` と一致しない場合、パニックします。
333    pub fn get_sample_data(&mut self, channel: AudioChannel, buffer: &mut [f32]) -> usize {
334        let sample_num = self.audio_object.sample_num as usize;
335        assert_eq!(
336            buffer.len(),
337            sample_num,
338            "buffer length does not match sample_num"
339        );
340        let inner = unsafe { &*self.inner };
341        unsafe { (inner.get_sample_data)(buffer.as_mut_ptr(), channel.into()) };
342        sample_num
343    }
344
345    /// 現在の音声のデータを設定する。
346    /// `channel` は 0 が左チャンネル、1 が右チャンネルです。
347    ///
348    /// # Panics
349    ///
350    /// `data` の長さが `sample_num` と一致しない場合、パニックします。
351    pub fn set_sample_data(&mut self, channel: AudioChannel, data: &[f32]) {
352        let sample_num = self.audio_object.sample_num as usize;
353        assert_eq!(
354            data.len(),
355            sample_num,
356            "data length does not match sample_num"
357        );
358        let inner = unsafe { &*self.inner };
359        unsafe { (inner.set_sample_data)(data.as_ptr(), channel.into()) };
360    }
361}
362
363/// フィルタプラグインでのデータを使うためのハンドル。
364/// RwLockのような仕組みで安全にデータを扱うことができます。
365#[derive(Debug)]
366pub struct FilterConfigDataHandle<T: Copy> {
367    pub(crate) inner: *mut T,
368}
369
370unsafe impl<T: Send + Sync + Copy> Send for FilterConfigDataHandle<T> {}
371unsafe impl<T: Send + Sync + Copy> Sync for FilterConfigDataHandle<T> {}
372
373static HANDLES: std::sync::LazyLock<dashmap::DashMap<usize, parking_lot::RawRwLock>> =
374    std::sync::LazyLock::new(dashmap::DashMap::new);
375static OWNED_REFERENCES: std::sync::LazyLock<
376    std::sync::Arc<dashmap::DashMap<usize, std::sync::atomic::AtomicUsize>>,
377> = std::sync::LazyLock::new(|| std::sync::Arc::new(dashmap::DashMap::new()));
378
379impl<T: Copy> Clone for FilterConfigDataHandle<T> {
380    fn clone(&self) -> Self {
381        if !self.inner.is_null() {
382            let addr = self.inner as usize;
383            if OWNED_REFERENCES.contains_key(&addr) {
384                let entry = OWNED_REFERENCES.get(&addr).unwrap();
385                entry.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
386            }
387        }
388        Self { inner: self.inner }
389    }
390}
391impl<T: Copy> Drop for FilterConfigDataHandle<T> {
392    fn drop(&mut self) {
393        if !self.inner.is_null() {
394            let addr = self.inner as usize;
395            if let Some(entry) = OWNED_REFERENCES.get(&addr) {
396                let prev = entry.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
397                if prev == 1 {
398                    unsafe {
399                        let _boxed = Box::from_raw(self.inner);
400                    }
401                    // NOTE: ここでdropしないとdeadlockする
402                    drop(entry);
403                    OWNED_REFERENCES.remove(&addr);
404                }
405            }
406        }
407    }
408}
409
410impl<T: Copy> FilterConfigDataHandle<T> {
411    #[doc(hidden)]
412    pub fn __generics_default_value() -> T
413    where
414        T: Default,
415    {
416        T::default()
417    }
418
419    #[doc(hidden)]
420    pub fn __from_erased(erased: &ErasedFilterConfigData) -> Self {
421        Self {
422            inner: erased.value.map_or(std::ptr::null_mut(), |v| v.as_ptr()) as *mut T,
423        }
424    }
425
426    #[doc(hidden)]
427    pub fn __new_owned(value: T) -> Self {
428        let boxed = Box::new(value);
429        let pointer = Box::into_raw(boxed);
430        let addr = pointer as *mut () as usize;
431        OWNED_REFERENCES.insert(addr, std::sync::atomic::AtomicUsize::new(1));
432        Self { inner: pointer }
433    }
434
435    /// データを読み取るためのロックを取得する。
436    pub fn read<'handle>(&'handle self) -> FilterConfigDataReadGuard<'handle, T> {
437        let addr = self.inner as *mut () as usize;
438        let lock = HANDLES
439            .entry(addr)
440            .or_insert_with(|| parking_lot::RawRwLock::INIT);
441        let lock = lock.value();
442
443        lock.lock_shared();
444        FilterConfigDataReadGuard::new(self.inner)
445    }
446
447    /// データを読み取るためのロックの取得を試みる。
448    /// ロックが取得できなかった場合は `None` を返します。
449    pub fn try_read<'handle>(&'handle self) -> Option<FilterConfigDataReadGuard<'handle, T>> {
450        let addr = self.inner as *mut () as usize;
451        let lock = HANDLES
452            .entry(addr)
453            .or_insert_with(|| parking_lot::RawRwLock::INIT);
454        let lock = lock.value();
455
456        if lock.try_lock_shared() {
457            Some(FilterConfigDataReadGuard::new(self.inner))
458        } else {
459            None
460        }
461    }
462
463    /// データを書き込むためのロックを取得する。
464    pub fn write<'handle>(&'handle self) -> FilterConfigDataWriteGuard<'handle, T> {
465        let addr = self.inner as *mut () as usize;
466        let lock = HANDLES
467            .entry(addr)
468            .or_insert_with(|| parking_lot::RawRwLock::INIT);
469        let lock = lock.value();
470        lock.lock_exclusive();
471        FilterConfigDataWriteGuard::new(self.inner)
472    }
473
474    /// データを書き込むためのロックの取得を試みる。
475    /// ロックが取得できなかった場合は `None` を返します。
476    pub fn try_write<'handle>(&'handle self) -> Option<FilterConfigDataWriteGuard<'handle, T>> {
477        let addr = self.inner as *mut () as usize;
478        let lock = HANDLES
479            .entry(addr)
480            .or_insert_with(|| parking_lot::RawRwLock::INIT);
481        let lock = lock.value();
482        if lock.try_lock_exclusive() {
483            Some(FilterConfigDataWriteGuard::new(self.inner))
484        } else {
485            None
486        }
487    }
488
489    /// 内部のポインタを取得する。
490    ///
491    /// # Warning
492    ///
493    /// このポインタを直接操作するとデータ競合が発生する可能性があります。
494    pub fn as_ptr(&self) -> *mut T {
495        self.inner
496    }
497}
498
499#[doc(hidden)]
500#[expect(private_bounds)]
501pub fn __string_to_pathbuf_or_option_pathbuf<T: StringToPathBufOrOptionPathBuf>(s: &str) -> T {
502    T::__string_to_pathbuf_or_option_pathbuf(s)
503}
504
505trait StringToPathBufOrOptionPathBuf: Sized {
506    fn __string_to_pathbuf_or_option_pathbuf(s: &str) -> Self;
507}
508impl StringToPathBufOrOptionPathBuf for std::path::PathBuf {
509    fn __string_to_pathbuf_or_option_pathbuf(s: &str) -> Self {
510        std::path::PathBuf::from(s)
511    }
512}
513impl StringToPathBufOrOptionPathBuf for Option<std::path::PathBuf> {
514    fn __string_to_pathbuf_or_option_pathbuf(s: &str) -> Self {
515        if s.is_empty() {
516            None
517        } else {
518            Some(std::path::PathBuf::from(s))
519        }
520    }
521}
522
523/// フィルタプラグインのデータを読み取るためのガード。
524pub struct FilterConfigDataReadGuard<'handle, T: Copy> {
525    pub(crate) inner: *mut T,
526    _handle: std::marker::PhantomData<&'handle FilterConfigDataHandle<T>>,
527}
528unsafe impl<T: Send + Sync + Copy> Send for FilterConfigDataReadGuard<'_, T> {}
529unsafe impl<T: Send + Sync + Copy> Sync for FilterConfigDataReadGuard<'_, T> {}
530impl<T: Copy> FilterConfigDataReadGuard<'_, T> {
531    fn new<'handle>(inner: *mut T) -> FilterConfigDataReadGuard<'handle, T> {
532        FilterConfigDataReadGuard {
533            inner,
534            _handle: std::marker::PhantomData,
535        }
536    }
537}
538impl<T: Copy> Drop for FilterConfigDataReadGuard<'_, T> {
539    fn drop(&mut self) {
540        let addr = self.inner as *mut () as usize;
541        if let Some(entry) = HANDLES.get(&addr) {
542            let lock = entry.value();
543            unsafe { lock.unlock_shared() };
544        }
545    }
546}
547impl<T: Copy> std::convert::AsRef<T> for FilterConfigDataReadGuard<'_, T> {
548    fn as_ref(&self) -> &T {
549        unsafe { &*self.inner }
550    }
551}
552impl<T: Copy> std::ops::Deref for FilterConfigDataReadGuard<'_, T> {
553    type Target = T;
554
555    fn deref(&self) -> &Self::Target {
556        self.as_ref()
557    }
558}
559
560/// フィルタプラグインのデータを書き込むためのガード。
561pub struct FilterConfigDataWriteGuard<'handle, T: Copy> {
562    pub(crate) inner: *mut T,
563    _handle: std::marker::PhantomData<&'handle FilterConfigDataHandle<T>>,
564}
565
566unsafe impl<T: Send + Sync + Copy> Send for FilterConfigDataWriteGuard<'_, T> {}
567unsafe impl<T: Send + Sync + Copy> Sync for FilterConfigDataWriteGuard<'_, T> {}
568impl<T: Copy> FilterConfigDataWriteGuard<'_, T> {
569    fn new<'handle>(inner: *mut T) -> FilterConfigDataWriteGuard<'handle, T> {
570        FilterConfigDataWriteGuard {
571            inner,
572            _handle: std::marker::PhantomData,
573        }
574    }
575}
576impl<T: Copy> Drop for FilterConfigDataWriteGuard<'_, T> {
577    fn drop(&mut self) {
578        let addr = self.inner as *mut () as usize;
579        if let Some(entry) = HANDLES.get(&addr) {
580            let lock = entry.value();
581            unsafe { lock.unlock_exclusive() };
582        }
583    }
584}
585impl<T: Copy> std::convert::AsMut<T> for FilterConfigDataWriteGuard<'_, T> {
586    fn as_mut(&mut self) -> &mut T {
587        unsafe { &mut *self.inner }
588    }
589}
590impl<T: Copy> std::ops::Deref for FilterConfigDataWriteGuard<'_, T> {
591    type Target = T;
592    fn deref(&self) -> &Self::Target {
593        unsafe { &*self.inner }
594    }
595}
596impl<T: Copy> std::ops::DerefMut for FilterConfigDataWriteGuard<'_, T> {
597    fn deref_mut(&mut self) -> &mut Self::Target {
598        self.as_mut()
599    }
600}
601
602#[cfg(test)]
603mod tests {
604    use super::*;
605
606    #[test]
607    fn filter_config_data_handle_reads_initial_value() {
608        let handle = FilterConfigDataHandle::<u32>::__new_owned(42);
609        let read_guard = handle.read();
610        assert_eq!(*read_guard, 42);
611    }
612
613    #[test]
614    fn filter_config_data_handle_writes_and_reads_updated_value() {
615        let handle = FilterConfigDataHandle::<u32>::__new_owned(42);
616        {
617            let mut write_guard = handle.write();
618            *write_guard = 100;
619        }
620        let read_guard = handle.read();
621        assert_eq!(*read_guard, 100);
622    }
623
624    #[test]
625    fn filter_config_data_handle_try_read_fails_when_locked_for_write() {
626        let handle = FilterConfigDataHandle::<u32>::__new_owned(42);
627        let _write_guard = handle.write();
628        let try_read_guard = handle.try_read();
629        assert!(try_read_guard.is_none());
630    }
631
632    #[test]
633    fn filter_config_data_handle_try_write_fails_when_locked_for_read() {
634        let handle = FilterConfigDataHandle::<u32>::__new_owned(42);
635        let _read_guard = handle.read();
636        let try_write_guard = handle.try_write();
637        assert!(try_write_guard.is_none());
638    }
639
640    #[test]
641    fn filter_config_data_handle_clone_shares_state() {
642        let handle = FilterConfigDataHandle::<u32>::__new_owned(42);
643        let cloned_handle = handle.clone();
644        {
645            let mut write_guard = handle.write();
646            *write_guard = 100;
647        }
648        let read_guard = cloned_handle.read();
649        assert_eq!(*read_guard, 100);
650    }
651
652    #[test]
653    fn filter_config_data_handle_never_drops_data_for_borrowed() {
654        let mut data =
655            crate::filter::ErasedFilterConfigData::with_default_value("test".to_string(), 42);
656        let data_ptr = Box::into_raw(Box::new(42u32));
657        data.value = Some(std::ptr::NonNull::new(data_ptr as _).unwrap());
658        let handle = FilterConfigDataHandle::<u32>::__from_erased(&data);
659        drop(handle);
660
661        assert_eq!(unsafe { *data_ptr }, 42);
662    }
663
664    #[test]
665    fn filter_config_data_handle_reads_value_from_erased_data() {
666        let boxed = Box::new(77u32);
667        let ptr = std::ptr::NonNull::from(boxed.as_ref());
668        let data = crate::filter::FilterConfigData {
669            name: "test".to_string(),
670            value: Some(ptr),
671            default_value: 0,
672        };
673        let erased = data.erase_type();
674        let handle = FilterConfigDataHandle::<u32>::__from_erased(&erased);
675        let read_guard = handle.read();
676
677        assert_eq!(*read_guard, 77);
678        assert_eq!(erased.value.unwrap().as_ptr() as *mut u32, ptr.as_ptr());
679
680        drop(read_guard);
681        drop(handle);
682        drop(boxed);
683    }
684}