lv2_atom/
object.rs

1//! An atom containing multiple key-value pairs.
2//!
3//! This module is centered on the [`Object`](struct.Object.html) atom type. An object is the atomized form of an RDF instance: It has an (optional) id, a type and multiple properties declared as URID/Atom pairs. Both the id and the type are URIDs too.
4//!
5//! # Example
6//! ```
7//! use lv2_core::prelude::*;
8//! use lv2_atom::prelude::*;
9//! use urid::*;
10//!
11//! #[uri("urn:object-class")]
12//! struct ObjectClass;
13//!
14//! #[uri("urn:property-a")]
15//! struct PropertyA;
16//!
17//! #[derive(PortCollection)]
18//! struct MyPorts {
19//!     input: InputPort<AtomPort>,
20//!     output: OutputPort<AtomPort>,
21//! }
22//!
23//! #[derive(URIDCollection)]
24//! struct MyURIDs {
25//!     atom: AtomURIDCollection,
26//!     object_class: URID<ObjectClass>,
27//!     property_a: URID<PropertyA>,
28//! }
29//!
30//! fn run(ports: &mut MyPorts, urids: &MyURIDs) {
31//!     // Create the reading handle.
32//!     // We don't need the header now.
33//!     let (_header, object_reader) = ports.input.read(urids.atom.object, ()).unwrap();
34//!
35//!     /// Iterate through all properties of the object.
36//!     for (property_header, atom) in object_reader {
37//!         // If the property is an integer...
38//!         if let Some(integer) = atom.read(urids.atom.int, ()) {
39//!             // Print it!
40//!             println!(
41//!                 "Property No. {} has integer value {}",
42//!                 property_header.key.get(),
43//!                 integer
44//!             );
45//!         } else {
46//!             // Print that is not an integer.
47//!             println!(
48//!                 "Property No. {} is not an integer",
49//!                 property_header.key.get()
50//!             );
51//!         }
52//!     }
53//!
54//!     // Initialize the object.
55//!     let mut object_writer = ports.output.init(
56//!         urids.atom.object,
57//!         ObjectHeader {
58//!             id: None,
59//!             otype: urids.object_class.into_general(),
60//!         }
61//!     ).unwrap();
62//!
63//!     // Write a property to the object.
64//!     object_writer.init(urids.property_a, urids.atom.int, 42).unwrap();
65//! }
66//! ```
67//!
68//! # Specification
69//! [http://lv2plug.in/ns/ext/atom/atom.html#Object](http://lv2plug.in/ns/ext/atom/atom.html#Object).
70use crate::space::*;
71use crate::*;
72use std::convert::TryFrom;
73use std::iter::Iterator;
74use urid::UriBound;
75use urid::URID;
76
77/// An atom containing multiple key-value pairs.
78///
79/// [See also the module documentation.](index.html)
80pub struct Object;
81
82unsafe impl UriBound for Object {
83    const URI: &'static [u8] = sys::LV2_ATOM__Object;
84}
85
86/// Information about an object atom.
87pub struct ObjectHeader {
88    /// The id of the object to distinguish different objects of the same type.
89    ///
90    /// If you don't need it, you should set it to `None`.
91    pub id: Option<URID>,
92    /// The type of the object (same as `rdf:type`).
93    pub otype: URID,
94}
95
96impl<'a, 'b> Atom<'a, 'b> for Object
97where
98    'a: 'b,
99{
100    type ReadParameter = ();
101    type ReadHandle = (ObjectHeader, ObjectReader<'a>);
102    type WriteParameter = ObjectHeader;
103    type WriteHandle = ObjectWriter<'a, 'b>;
104
105    fn read(body: Space<'a>, _: ()) -> Option<(ObjectHeader, ObjectReader<'a>)> {
106        let (header, body) = body.split_type::<sys::LV2_Atom_Object_Body>()?;
107        let header = ObjectHeader {
108            id: URID::try_from(header.id).ok(),
109            otype: URID::try_from(header.otype).ok()?,
110        };
111
112        let reader = ObjectReader { space: body };
113
114        Some((header, reader))
115    }
116
117    fn init(
118        mut frame: FramedMutSpace<'a, 'b>,
119        header: ObjectHeader,
120    ) -> Option<ObjectWriter<'a, 'b>> {
121        {
122            let frame = &mut frame as &mut dyn MutSpace;
123            frame.write(
124                &sys::LV2_Atom_Object_Body {
125                    id: header.id.map(|urid| urid.get()).unwrap_or(0),
126                    otype: header.otype.get(),
127                },
128                true,
129            );
130        }
131        Some(ObjectWriter { frame })
132    }
133}
134
135/// Alias of `Object`, used by older hosts.
136///
137/// A blank object is an object that isn't an instance of a class. The [specification recommends](https://lv2plug.in/ns/ext/atom/atom.html#Blank) to use an [`Object`](struct.Object.html) with an id of `None`, but some hosts still use it and therefore, it's included in this library.
138///
139/// If you want to read an object, you should also support `Blank`s, but if you want to write an object, you should always use `Object`.
140pub struct Blank;
141
142unsafe impl UriBound for Blank {
143    const URI: &'static [u8] = sys::LV2_ATOM__Blank;
144}
145
146impl<'a, 'b> Atom<'a, 'b> for Blank
147where
148    'a: 'b,
149{
150    type ReadParameter = <Object as Atom<'a, 'b>>::ReadParameter;
151    type ReadHandle = <Object as Atom<'a, 'b>>::ReadHandle;
152    type WriteParameter = <Object as Atom<'a, 'b>>::WriteParameter;
153    type WriteHandle = <Object as Atom<'a, 'b>>::WriteHandle;
154
155    #[allow(clippy::unit_arg)]
156    fn read(body: Space<'a>, parameter: Self::ReadParameter) -> Option<Self::ReadHandle> {
157        Object::read(body, parameter)
158    }
159
160    fn init(
161        frame: FramedMutSpace<'a, 'b>,
162        parameter: Self::WriteParameter,
163    ) -> Option<Self::WriteHandle> {
164        Object::init(frame, parameter)
165    }
166}
167
168/// An iterator over all properties in an object.
169///
170/// Each iteration item is the header of the property, as well as the space occupied by the value atom. You can use normal `read` methods on the returned space.
171pub struct ObjectReader<'a> {
172    space: Space<'a>,
173}
174
175impl<'a> Iterator for ObjectReader<'a> {
176    type Item = (PropertyHeader, UnidentifiedAtom<'a>);
177
178    fn next(&mut self) -> Option<(PropertyHeader, UnidentifiedAtom<'a>)> {
179        let (header, value, space) = Property::read_body(self.space)?;
180        self.space = space;
181        Some((header, UnidentifiedAtom::new(value)))
182    }
183}
184
185/// Writing handle for object properties.
186///
187/// This handle is a safeguard to assure that a object is always a series of properties.
188pub struct ObjectWriter<'a, 'b> {
189    frame: FramedMutSpace<'a, 'b>,
190}
191
192impl<'a, 'b> ObjectWriter<'a, 'b> {
193    /// Initialize a new property with a context.
194    ///
195    /// This method does the same as [`init`](#method.init), but also sets the context URID.
196    pub fn init_with_context<'c, K: ?Sized, T: ?Sized, A: Atom<'a, 'c>>(
197        &'c mut self,
198        key: URID<K>,
199        context: URID<T>,
200        child_urid: URID<A>,
201        parameter: A::WriteParameter,
202    ) -> Option<A::WriteHandle> {
203        Property::write_header(&mut self.frame, key.into_general(), Some(context))?;
204        (&mut self.frame as &mut dyn MutSpace).init(child_urid, parameter)
205    }
206
207    /// Initialize a new property.
208    ///
209    /// This method writes out the header of a property and returns a reference to the space, so the property values can be written.
210    ///
211    /// Properties also have a context URID internally, which is rarely used. If you want to add one, use [`init_with_context`](#method.init_with_context).
212    pub fn init<'c, K: ?Sized, A: Atom<'a, 'c>>(
213        &'c mut self,
214        key: URID<K>,
215        child_urid: URID<A>,
216        parameter: A::WriteParameter,
217    ) -> Option<A::WriteHandle> {
218        Property::write_header::<K, ()>(&mut self.frame, key, None)?;
219        (&mut self.frame as &mut dyn MutSpace).init(child_urid, parameter)
220    }
221}
222
223/// An atom containing a key-value pair.
224///
225/// A property represents a single URID -> atom mapping. Additionally and optionally, you may also define a context in which the property is valid. For more information, visit the [specification](http://lv2plug.in/ns/ext/atom/atom.html#Property).
226///
227/// Most of the time, properties are a part of an [`Object`](struct.Object.html) atom and therefore, you don't need to read or write them directly. However, they could in theory appear on their own too, which is why reading and writing methods are still provided.
228pub struct Property;
229
230unsafe impl UriBound for Property {
231    const URI: &'static [u8] = sys::LV2_ATOM__Property;
232}
233
234/// Information about a property atom.
235#[derive(Clone, Copy)]
236pub struct PropertyHeader {
237    /// The key of the property.
238    pub key: URID,
239    /// URID of the context (generally `None`).
240    pub context: Option<URID>,
241}
242
243impl Property {
244    /// Read the body of a property atom from a space.
245    ///
246    /// This method assumes that the space actually contains the body of a property atom, without the header. It returns the property header, containing the key and optional context of the property, the body of the actual atom, and the space behind the atom.
247    fn read_body(space: Space) -> Option<(PropertyHeader, Space, Space)> {
248        #[repr(C)]
249        #[derive(Clone, Copy)]
250        /// A custom version of the property body that does not include the value atom header.
251        ///
252        /// We will retrieve it separately.
253        struct StrippedPropertyBody {
254            key: u32,
255            context: u32,
256        }
257
258        let (header, space) = space.split_type::<StrippedPropertyBody>()?;
259
260        let header = PropertyHeader {
261            key: URID::try_from(header.key).ok()?,
262            context: URID::try_from(header.context).ok(),
263        };
264
265        let (atom, space) = space.split_atom()?;
266        Some((header, atom, space))
267    }
268
269    /// Write out the header of a property atom.
270    ///
271    /// This method simply writes out the content of the header to the space and returns `Some(())` if it's successful.
272    fn write_header<K: ?Sized, C: ?Sized>(
273        space: &mut dyn MutSpace,
274        key: URID<K>,
275        context: Option<URID<C>>,
276    ) -> Option<()> {
277        space.write(&key.get(), true)?;
278        space.write(&context.map(|urid| urid.get()).unwrap_or(0), false)?;
279        Some(())
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use crate::prelude::*;
286    use crate::space::*;
287    use std::mem::size_of;
288    use urid::*;
289
290    #[test]
291    fn test_object() {
292        let map = HashURIDMapper::new();
293        let urids = AtomURIDCollection::from_map(&map).unwrap();
294
295        let object_type = map
296            .map_uri(Uri::from_bytes_with_nul(b"urn:my-type\0").unwrap())
297            .unwrap();
298
299        let first_key = map
300            .map_uri(Uri::from_bytes_with_nul(b"urn:value-a\0").unwrap())
301            .unwrap();
302        let first_value: i32 = 17;
303
304        let second_key = map
305            .map_uri(Uri::from_bytes_with_nul(b"urn:value-b\0").unwrap())
306            .unwrap();
307        let second_value: f32 = 42.0;
308
309        let mut raw_space: Box<[u8]> = Box::new([0; 256]);
310
311        // writing
312        {
313            let mut space = RootMutSpace::new(raw_space.as_mut());
314            let frame = FramedMutSpace::new(&mut space as &mut dyn MutSpace, urids.object).unwrap();
315            let mut writer = Object::init(
316                frame,
317                ObjectHeader {
318                    id: None,
319                    otype: object_type,
320                },
321            )
322            .unwrap();
323            {
324                writer.init(first_key, urids.int, first_value).unwrap();
325            }
326            {
327                writer.init(second_key, urids.float, second_value).unwrap();
328            }
329        }
330
331        // verifying
332        {
333            // Header
334            let (atom, space) = raw_space.split_at(size_of::<sys::LV2_Atom>());
335            let atom = unsafe { &*(atom.as_ptr() as *const sys::LV2_Atom) };
336            assert_eq!(atom.type_, urids.object);
337            assert_eq!(
338                atom.size as usize,
339                size_of::<sys::LV2_Atom_Object_Body>()
340                    + size_of::<sys::LV2_Atom_Property_Body>()
341                    + 2 * size_of::<i32>()
342                    + size_of::<sys::LV2_Atom_Property_Body>()
343                    + size_of::<f32>()
344            );
345
346            // Object.
347            let (object, space) = space.split_at(size_of::<sys::LV2_Atom_Object_Body>());
348            let object = unsafe { &*(object.as_ptr() as *const sys::LV2_Atom_Object_Body) };
349            assert_eq!(object.id, 0);
350            assert_eq!(object.otype, object_type);
351
352            // First property.
353            let (property, space) = space.split_at(size_of::<sys::LV2_Atom_Property_Body>());
354            let property = unsafe { &*(property.as_ptr() as *const sys::LV2_Atom_Property_Body) };
355            assert_eq!(property.key, first_key);
356            assert_eq!(property.context, 0);
357            assert_eq!(property.value.type_, urids.int);
358            assert_eq!(property.value.size as usize, size_of::<i32>());
359
360            let (value, space) = space.split_at(size_of::<i32>());
361            let value = unsafe { *(value.as_ptr() as *const i32) };
362            assert_eq!(value, first_value);
363            let (_, space) = space.split_at(size_of::<i32>());
364
365            // Second property.
366            let (property, space) = space.split_at(size_of::<sys::LV2_Atom_Property_Body>());
367            let property = unsafe { &*(property.as_ptr() as *const sys::LV2_Atom_Property_Body) };
368            assert_eq!(property.key, second_key);
369            assert_eq!(property.context, 0);
370            assert_eq!(property.value.type_, urids.float);
371            assert_eq!(property.value.size as usize, size_of::<f32>());
372
373            let (value, _) = space.split_at(size_of::<f32>());
374            let value = unsafe { *(value.as_ptr() as *const f32) };
375            assert_eq!(value, second_value);
376        }
377
378        // reading
379        {
380            let space = Space::from_slice(raw_space.as_ref());
381            let (body, _) = space.split_atom_body(urids.object).unwrap();
382
383            let (header, iter) = Object::read(body, ()).unwrap();
384            assert_eq!(header.otype, object_type);
385            assert_eq!(header.id, None);
386
387            let properties: Vec<(PropertyHeader, UnidentifiedAtom)> = iter.collect();
388            let (header, atom) = properties[0];
389            assert_eq!(header.key, first_key);
390            assert_eq!(atom.read::<Int>(urids.int, ()).unwrap(), first_value);
391            let (header, atom) = properties[1];
392            assert_eq!(header.key, second_key);
393            assert_eq!(atom.read::<Float>(urids.float, ()).unwrap(), second_value);
394        }
395    }
396}