lv2_core/feature/
mod.rs

1//! Additional host functionalities.
2use urid::{Uri, UriBound};
3
4mod cache;
5mod core_features;
6mod descriptor;
7
8pub use cache::FeatureCache;
9pub use core_features::*;
10pub use descriptor::FeatureDescriptor;
11
12use std::ffi::c_void;
13
14/// All threading contexts of LV2 interface methods.
15///
16/// The [core LV2 specifications](https://lv2plug.in/ns/lv2core/lv2core.html) declare three threading classes: "Discovery", where plugins are discovered by the host, "Instantiation", where plugins are instantiated and (de-)activated, and "Audio", where the actual audio processing happens.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum ThreadingClass {
19    Discovery,
20    Instantiation,
21    Audio,
22    Other,
23}
24
25/// Trait to generalize the feature detection system.
26///
27/// A host that only implements the core LV2 specification does not have much functionality. Instead, hosts can provide extra functionalities, called "host features" or short "features", which a make plugins more useful.
28///
29/// A native plugin written in C would discover a host's features by iterating through an array of URIs and pointers. When it finds the URI of the feature it is looking for, it casts the pointer to the type of the feature interface and uses the information from the interface.
30///
31/// In Rust, most of this behaviour is done internally and instead of simply casting a pointer, a safe feature descriptor, which implements this trait, is constructed using the [`from_raw_data`](#tymethod.from_raw_data) method.
32///
33/// Some host features may only be used in certain threading classes. This is guarded by Rust-LV2 by passing the threading class in which the plugin will be used to the feature, which then may take different actions.
34pub unsafe trait Feature: UriBound + Sized {
35    /// Create an instance of the featurer.
36    ///
37    /// The feature pointer is provided by the host and points to the feature-specific data. If the data is invalid, for one reason or another, the method returns `None`.
38    ///
39    /// # Implementing
40    ///
41    /// If nescessary, you should dereference it and store the reference inside the feature struct in order to use it.
42    ///
43    /// You have to document in which threading classes your feature can be used and should panic if the threading class is not supported. When this happens when the plugin programmer has added your feature to the wrong feature collection, which is considered a programming error and therefore justifies the panic. If you don't panic in this case, the error is handled silently, which may make debugging harder.
44    ///
45    /// You should always allow the [`Other`](enum.ThreadingClass.html#variant.Other) threading class in order to restrict your feature from use cases you might not know.
46    ///
47    /// # Safety
48    ///
49    /// This method is unsafe since it has to de-reference a pointer.
50    unsafe fn from_feature_ptr(feature: *const c_void, class: ThreadingClass) -> Option<Self>;
51}
52
53/// An error created during feature resolution when a required feature is missing.
54#[derive(Copy, Clone, Debug)]
55pub struct MissingFeatureError {
56    pub(crate) uri: &'static Uri,
57}
58
59impl std::fmt::Display for MissingFeatureError {
60    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
61        let uri = self.uri.to_str().unwrap_or("[error while reading URI]");
62        write!(
63            f,
64            "Unable to instantiate plugin: missing required feature: {}",
65            uri
66        )
67    }
68}
69
70/// Convenience trait for feature collections.
71///
72/// The feature cache is only for temporary use; Once a feature is retrieved, it is removed from the cache. Therefore you need a way to properly store features.
73///
74/// You can simply create a struct with features as it's fields and derive `FeatureCollection` for it. A procedural macro will then create a method that populates the struct from the cache, or returns `None` if one of the required features is not in the cache.
75///
76/// An example using the few built-in features:
77///
78///     use lv2_core::plugin::*;
79///     use lv2_core::feature::*;
80///
81///     #[derive(FeatureCollection)]
82///     struct MyCollection {
83///         live: IsLive,
84///         hardrt: Option<HardRTCapable>,
85///     }
86pub trait FeatureCollection<'a>: Sized + 'a {
87    /// Populate a collection with features from the cache for the given threading class.
88    fn from_cache(
89        cache: &mut FeatureCache<'a>,
90        class: ThreadingClass,
91    ) -> Result<Self, MissingFeatureError>;
92}
93
94impl<'a> FeatureCollection<'a> for () {
95    #[inline]
96    fn from_cache(
97        _cache: &mut FeatureCache,
98        _: ThreadingClass,
99    ) -> Result<Self, MissingFeatureError> {
100        Ok(())
101    }
102}
103
104#[cfg(test)]
105#[allow(clippy::float_cmp)]
106mod tests {
107    use crate::feature::FeatureCache;
108    use crate::{feature::*, plugin::*};
109    use std::ffi::c_void;
110    use std::os::raw::c_char;
111    use std::pin::Pin;
112    use urid::UriBound;
113
114    struct FeatureA<'a> {
115        number: &'a i32,
116    }
117
118    struct FeatureB<'a> {
119        number: &'a f32,
120    }
121
122    unsafe impl<'a> UriBound for FeatureA<'a> {
123        const URI: &'static [u8] = b"urn:lv2Feature:A\0";
124    }
125
126    unsafe impl<'a> Feature for FeatureA<'a> {
127        unsafe fn from_feature_ptr(feature: *const c_void, _: ThreadingClass) -> Option<Self> {
128            (feature as *const i32)
129                .as_ref()
130                .map(|number| Self { number })
131        }
132    }
133
134    unsafe impl<'a> UriBound for FeatureB<'a> {
135        const URI: &'static [u8] = b"urn:lv2Feature:B\0";
136    }
137
138    unsafe impl<'a> Feature for FeatureB<'a> {
139        unsafe fn from_feature_ptr(feature: *const c_void, _: ThreadingClass) -> Option<Self> {
140            (feature as *const f32)
141                .as_ref()
142                .map(|number| Self { number })
143        }
144    }
145
146    #[derive(FeatureCollection)]
147    struct Collection<'a> {
148        a: FeatureA<'a>,
149        b: FeatureB<'a>,
150        _c: crate::feature::IsLive,
151    }
152
153    struct FeatureTestSetting<'a> {
154        pub data_a: Pin<Box<i32>>,
155        pub feature_a_sys: Pin<Box<::sys::LV2_Feature>>,
156        pub data_b: Pin<Box<f32>>,
157        pub feature_b_sys: Pin<Box<::sys::LV2_Feature>>,
158        pub feature_c_sys: Pin<Box<::sys::LV2_Feature>>,
159        pub features_cache: FeatureCache<'a>,
160    }
161
162    impl<'a> FeatureTestSetting<'a> {
163        fn new() -> Self {
164            let mut data_a: Pin<Box<i32>> = Box::pin(42);
165            let feature_a_sys = Box::pin(::sys::LV2_Feature {
166                URI: FeatureA::URI.as_ptr() as *const c_char,
167                data: data_a.as_mut().get_mut() as *mut i32 as *mut c_void,
168            });
169
170            let mut data_b: Pin<Box<f32>> = Box::pin(17.0);
171            let feature_b_sys = Box::pin(::sys::LV2_Feature {
172                URI: FeatureB::URI.as_ptr() as *const c_char,
173                data: data_b.as_mut().get_mut() as *mut f32 as *mut c_void,
174            });
175
176            let feature_c_sys = Box::pin(::sys::LV2_Feature {
177                URI: crate::feature::IsLive::URI.as_ptr() as *const c_char,
178                data: std::ptr::null_mut(),
179            });
180
181            let features_list: &[*const sys::LV2_Feature] = &[
182                feature_a_sys.as_ref().get_ref(),
183                feature_b_sys.as_ref().get_ref(),
184                feature_c_sys.as_ref().get_ref(),
185                std::ptr::null(),
186            ];
187
188            // Constructing the cache.
189            let features_cache = unsafe { FeatureCache::from_raw(features_list.as_ptr()) };
190
191            Self {
192                data_a,
193                feature_a_sys,
194                data_b,
195                feature_b_sys,
196                feature_c_sys,
197                features_cache,
198            }
199        }
200    }
201
202    #[test]
203    fn test_feature_cache() {
204        // Constructing the test case.
205        let setting = FeatureTestSetting::new();
206        let mut features_cache = setting.features_cache;
207
208        // Testing the cache.
209        assert!(features_cache.contains::<FeatureA>());
210        assert!(features_cache.contains::<FeatureB>());
211
212        let retrieved_feature_a: FeatureA = features_cache
213            .retrieve_feature(ThreadingClass::Other)
214            .unwrap();
215        assert_eq!(*retrieved_feature_a.number, *(setting.data_a));
216
217        let retrieved_feature_b: FeatureB = features_cache
218            .retrieve_feature(ThreadingClass::Other)
219            .unwrap();
220        assert!(retrieved_feature_b.number - *(setting.data_b) < std::f32::EPSILON);
221    }
222
223    #[test]
224    fn test_feature_descriptor() {
225        // Constructing the test case.
226        let setting = FeatureTestSetting::new();
227        let features_cache = setting.features_cache;
228
229        // Collect all items from the feature iterator.
230        let feature_descriptors: Vec<FeatureDescriptor> = features_cache.into_iter().collect();
231
232        // Test the collected items.
233        assert_eq!(feature_descriptors.len(), 3);
234
235        let mut feature_a_found = false;
236        let mut feature_b_found = false;
237        for descriptor in feature_descriptors {
238            if descriptor.is_feature::<FeatureA>() {
239                if let Ok(retrieved_feature_a) =
240                    descriptor.into_feature::<FeatureA>(ThreadingClass::Other)
241                {
242                    assert!(*retrieved_feature_a.number == *(setting.data_a));
243                } else {
244                    panic!("Feature interpretation failed!");
245                }
246                feature_a_found = true;
247            } else if descriptor.is_feature::<FeatureB>() {
248                if let Ok(retrieved_feature_b) =
249                    descriptor.into_feature::<FeatureB>(ThreadingClass::Other)
250                {
251                    assert_eq!(*retrieved_feature_b.number, *(setting.data_b));
252                } else {
253                    panic!("Feature interpretation failed!");
254                }
255                feature_b_found = true;
256            } else if descriptor.is_feature::<crate::feature::IsLive>() {
257                if descriptor
258                    .into_feature::<IsLive>(ThreadingClass::Other)
259                    .is_err()
260                {
261                    panic!("Feature interpretation failed!");
262                }
263            } else {
264                panic!("Invalid feature in feature iterator!");
265            }
266        }
267        assert!(feature_a_found && feature_b_found);
268    }
269
270    #[test]
271    fn test_feature_collection() {
272        // Construct the setting.
273        let setting = FeatureTestSetting::new();
274        let mut features_cache = setting.features_cache;
275
276        let cache = Collection::from_cache(&mut features_cache, ThreadingClass::Other).unwrap();
277        assert_eq!(*cache.a.number, *setting.data_a);
278        assert_eq!(*cache.b.number, *setting.data_b);
279    }
280}