diff --git a/atom/Cargo.toml b/atom/Cargo.toml index e23194cb..ca4ad002 100644 --- a/atom/Cargo.toml +++ b/atom/Cargo.toml @@ -16,11 +16,12 @@ maintenance = { status = "passively-maintained" } [dependencies] lv2-sys = "2.0.0" lv2-units = "0.1.3" -urid = "0.1.0" +urid = { version = "0.1.0", default-features = false } [dependencies.lv2-core] version = "3.0.0" optional = true +default-features = false [dev-dependencies] lv2-urid = "2.1.0" diff --git a/atom/src/atoms.rs b/atom/src/atoms.rs new file mode 100644 index 00000000..307c12f8 --- /dev/null +++ b/atom/src/atoms.rs @@ -0,0 +1,51 @@ +pub mod chunk; +pub mod object; +pub mod scalar; +pub mod sequence; +pub mod string; +pub mod tuple; +pub mod vector; + +use urid::*; + +/// An URID collection of all standard atom types, provided for convenience. +#[derive(Clone)] +pub struct AtomURIDCollection { + pub blank: URID, + pub double: URID, + pub float: URID, + pub int: URID, + pub long: URID, + pub urid: URID, + pub bool: URID, + pub vector: URID, + pub chunk: URID, + pub literal: URID, + pub object: URID, + pub property: URID, + pub string: URID, + pub tuple: URID, + pub sequence: URID, +} + +impl URIDCollection for AtomURIDCollection { + fn from_map(map: &M) -> Option { + Some(Self { + blank: map.map_type()?, + double: map.map_type()?, + float: map.map_type()?, + int: map.map_type()?, + long: map.map_type()?, + urid: map.map_type()?, + bool: map.map_type()?, + vector: map.map_type()?, + chunk: map.map_type()?, + literal: map.map_type()?, + object: map.map_type()?, + property: map.map_type()?, + string: map.map_type()?, + tuple: map.map_type()?, + sequence: map.map_type()?, + }) + } +} diff --git a/atom/src/atoms/chunk.rs b/atom/src/atoms/chunk.rs new file mode 100644 index 00000000..e7cf9c31 --- /dev/null +++ b/atom/src/atoms/chunk.rs @@ -0,0 +1,125 @@ +//! An atom containing memory of undefined type. +//! +//! This contents of this atom is considered as a simple blob of data. It used, for example, by the host to transmit the size of a writable atom port. Since it is so simple, it does not need a reading or writing parameter. +//! +//! # Example +//! ``` +//! use lv2_core::prelude::*; +//! use lv2_atom::prelude::*; +//! +//! use lv2_atom::space::{AtomSpace, AtomWriter, SpaceWriter}; +//! +//! #[derive(PortCollection)] +//! struct MyPorts { +//! input: InputPort, +//! output: OutputPort, +//! } +//! +//! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { +//! let in_chunk: &AtomSpace = ports.input.read(urids.chunk).unwrap(); +//! let mut out_chunk: AtomWriter = ports.output.write(urids.chunk).unwrap(); +//! +//! let bytes = in_chunk.as_bytes(); +//! out_chunk.write_bytes(bytes).unwrap(); +//! } +//! ``` +//! +//! # Specification +//! +//! [http://lv2plug.in/ns/ext/atom/atom.html#Chunk](http://lv2plug.in/ns/ext/atom/atom.html#Chunk) +use crate::space::error::{AtomReadError, AtomWriteError}; +use crate::space::*; +use crate::AtomWriter; +use crate::{Atom, AtomHandle}; +use urid::UriBound; + +/// An atom containing an arbitrary byte buffer. +/// +/// [See also the module documentation.](index.html) +pub struct Chunk; + +unsafe impl UriBound for Chunk { + const URI: &'static [u8] = sys::LV2_ATOM__Chunk; +} + +pub struct ChunkReaderHandle; +impl<'a> AtomHandle<'a> for ChunkReaderHandle { + type Handle = &'a AtomSpace; +} + +pub struct ChunkWriterHandle; +impl<'a> AtomHandle<'a> for ChunkWriterHandle { + type Handle = AtomWriter<'a>; +} + +impl Atom for Chunk { + type ReadHandle = ChunkReaderHandle; + type WriteHandle = ChunkWriterHandle; + + #[inline] + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError> { + Ok(body) + } + + #[inline] + fn write( + frame: AtomWriter, + ) -> Result<::Handle, AtomWriteError> { + Ok(frame) + } +} + +#[cfg(test)] +mod tests { + use crate::atoms::chunk::*; + use crate::*; + + #[test] + fn test_chunk_and_slice_writer() { + const SLICE_LENGTH: usize = 42; + + let map = HashURIDMapper::new(); + let urids = crate::atoms::AtomURIDCollection::from_map(&map).unwrap(); + + let mut raw_space = AlignedVec::::new_with_capacity(64); + let raw_space = raw_space.as_space_mut(); + + // writing + { + let mut space = SpaceCursor::new(raw_space.as_bytes_mut()); + let mut writer = space.write_atom(urids.chunk).unwrap(); + let data = writer.allocate(SLICE_LENGTH).unwrap(); + + for (i, value) in data.iter_mut().enumerate() { + *value = i as u8; + } + + space.write_value(41u8).unwrap(); + } + + // verifying + { + let atom = unsafe { raw_space.read().next_atom() }.unwrap(); + assert_eq!(atom.header().size_of_body(), SLICE_LENGTH); + assert_eq!(atom.header().urid(), urids.chunk.get()); + + let data = atom.body().as_bytes(); + for (i, value) in data.iter().enumerate() { + assert_eq!(*value as usize, i); + } + } + + // reading + { + let data = + unsafe { Chunk::read(raw_space.read().next_atom().unwrap().body()) }.unwrap(); + assert_eq!(data.bytes_len(), SLICE_LENGTH); + + for (i, value) in data.as_bytes().iter().enumerate() { + assert_eq!(*value as usize, i); + } + } + } +} diff --git a/atom/src/atoms/object.rs b/atom/src/atoms/object.rs new file mode 100644 index 00000000..0a19a894 --- /dev/null +++ b/atom/src/atoms/object.rs @@ -0,0 +1,467 @@ +//! An atom containing multiple key-value pairs. +//! +//! 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. +//! +//! # Example +//! ``` +//! use lv2_core::prelude::*; +//! use lv2_atom::prelude::*; +//! use urid::*; +//! use lv2_atom::atoms::object::ObjectHeader; +//! +//! #[uri("urn:object-class")] +//! struct ObjectClass; +//! +//! #[uri("urn:property-a")] +//! struct PropertyA; +//! +//! #[derive(PortCollection)] +//! struct MyPorts { +//! input: InputPort, +//! output: OutputPort, +//! } +//! +//! #[derive(URIDCollection)] +//! struct MyURIDs { +//! atom: AtomURIDCollection, +//! object_class: URID, +//! property_a: URID, +//! } +//! +//! fn run(ports: &mut MyPorts, urids: &MyURIDs) { +//! // Create the reading handle. +//! // We don't need the header now. +//! let (_header, object_reader) = ports.input.read(urids.atom.object).unwrap(); +//! +//! /// Iterate through all properties of the object. +//! for (property_header, atom) in object_reader { +//! // If the property is an integer... +//! if let Ok(integer) = atom.read(urids.atom.int) { +//! // Print it! +//! println!( +//! "Property No. {} has integer value {}", +//! property_header.key.get(), +//! integer +//! ); +//! } else { +//! // Print that is not an integer. +//! println!( +//! "Property No. {} is not an integer", +//! property_header.key.get() +//! ); +//! } +//! } +//! +//! // Initialize the object. +//! let mut object_writer = ports.output.write(urids.atom.object) +//! .unwrap() +//! .write_header( +//! ObjectHeader { +//! id: None, +//! otype: urids.object_class.into_general(), +//! } +//! ).unwrap(); +//! +//! // Write a property to the object. +//! object_writer.new_property(urids.property_a, urids.atom.int).unwrap(); +//! } +//! ``` +//! +//! # Specification +//! [http://lv2plug.in/ns/ext/atom/atom.html#Object](http://lv2plug.in/ns/ext/atom/atom.html#Object). +use crate::space::SpaceReader; +use crate::*; +use core::convert::TryFrom; +use core::iter::Iterator; +use urid::UriBound; +use urid::URID; + +/// An atom containing multiple key-value pairs. +/// +/// [See also the module documentation.](index.html) +pub struct Object; + +unsafe impl UriBound for Object { + const URI: &'static [u8] = sys::LV2_ATOM__Object; +} + +/// Information about an object atom. +#[derive(Copy, Clone)] +pub struct ObjectHeader { + /// The id of the object to distinguish different objects of the same type. + /// + /// If you don't need it, you should set it to `None`. + pub id: Option, + /// The type of the object (same as `rdf:type`). + pub otype: URID, +} + +pub struct ObjectReaderHandle; +impl<'a> AtomHandle<'a> for ObjectReaderHandle { + type Handle = (ObjectHeader, ObjectReader<'a>); +} + +pub struct ObjectWriterHandle; +impl<'a> AtomHandle<'a> for ObjectWriterHandle { + type Handle = ObjectHeaderWriter<'a>; +} + +/// A type-state for the Object Writer, that writes the header of an object. +pub struct ObjectHeaderWriter<'a> { + frame: AtomWriter<'a>, +} + +impl<'a> ObjectHeaderWriter<'a> { + /// Initializes the object with the given header. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + pub fn write_header( + mut self, + header: ObjectHeader, + ) -> Result, AtomWriteError> { + self.frame.write_value(sys::LV2_Atom_Object_Body { + id: header.id.map(URID::get).unwrap_or(0), + otype: header.otype.get(), + })?; + + Ok(ObjectWriter { frame: self.frame }) + } +} + +impl Atom for Object { + type ReadHandle = ObjectReaderHandle; + type WriteHandle = ObjectWriterHandle; + + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError> { + let mut reader = body.read(); + let header: &sys::LV2_Atom_Object_Body = reader.next_value()?; + + let header = ObjectHeader { + id: URID::try_from(header.id).ok(), + otype: URID::try_from(header.otype).map_err(|_| AtomReadError::InvalidAtomValue { + reading_type_uri: Self::uri(), + error_message: "Invalid object type URID: 0", + })?, + }; + + let reader = ObjectReader { reader }; + + Ok((header, reader)) + } + + fn write( + frame: AtomWriter, + ) -> Result<::Handle, AtomWriteError> { + Ok(ObjectHeaderWriter { frame }) + } +} + +/// Alias of `Object`, used by older hosts. +/// +/// 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. +/// +/// 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`. +pub struct Blank; + +unsafe impl UriBound for Blank { + const URI: &'static [u8] = sys::LV2_ATOM__Blank; +} + +impl Atom for Blank { + type ReadHandle = ::ReadHandle; + type WriteHandle = ::WriteHandle; + + #[inline] + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError> { + Object::read(body) + } + + #[inline] + fn write( + frame: AtomWriter, + ) -> Result<::Handle, AtomWriteError> { + Object::write(frame) + } +} + +/// An iterator over all properties in an object. +/// +/// 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. +pub struct ObjectReader<'a> { + reader: SpaceReader<'a>, +} + +impl<'a> Iterator for ObjectReader<'a> { + type Item = (PropertyHeader, &'a UnidentifiedAtom); + + fn next(&mut self) -> Option<(PropertyHeader, &'a UnidentifiedAtom)> { + // SAFETY: The fact that this contains a valid property is guaranteed by this type. + self.reader + .try_read(|reader| unsafe { Property::read_body(reader) }) + .ok() + } +} + +/// Writing handle for object properties. +/// +/// This handle is a safeguard to assure that a object is always a series of properties. +pub struct ObjectWriter<'a> { + frame: AtomWriter<'a>, +} + +impl<'a> ObjectWriter<'a> { + /// Initializes a new property, with a given context. + /// + /// This method does the same as [`init`](#method.init), but also sets the context URID. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + pub fn new_property_with_context( + &mut self, + key: URID, + context: URID, + atom_type: URID, + ) -> Result<::Handle, AtomWriteError> { + Property::write_header(&mut self.frame, key.into_general(), Some(context))?; + self.frame.write_atom(atom_type) + } + + /// Initializes a new property. + /// + /// This method writes out the header of a property and returns a reference to the space, so the property values can be written. + /// + /// 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). + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + pub fn new_property( + &mut self, + key: URID, + atom_type: URID, + ) -> Result<::Handle, AtomWriteError> { + Property::write_header(&mut self.frame, key, None::>)?; + self.frame.write_atom(atom_type) + } +} + +/// An atom containing a key-value pair. +/// +/// 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). +/// +/// 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. +pub struct Property; + +unsafe impl UriBound for Property { + const URI: &'static [u8] = sys::LV2_ATOM__Property; +} + +/// Information about a property atom. +#[derive(Clone, Copy)] +pub struct PropertyHeader { + /// The key of the property. + pub key: URID, + /// URID of the context (generally `None`). + pub context: Option, +} + +#[repr(C, align(8))] +#[derive(Clone, Copy)] +/// A custom version of the property body that does not include the value atom header. +/// +/// We will retrieve/store it separately. +struct StrippedPropertyHeader { + key: u32, + context: u32, +} + +impl Property { + /// Read the body of a property atom from a space. + /// + /// It returns the property header, containing the key and optional context of the property, the body of the actual atom, and the remaining space behind the atom. + /// + /// # Safety + /// + /// The caller must ensure that the given Space actually contains a valid property. + unsafe fn read_body<'a>( + reader: &mut SpaceReader<'a>, + ) -> Result<(PropertyHeader, &'a UnidentifiedAtom), AtomReadError> { + let header: &StrippedPropertyHeader = reader.next_value()?; + + let header = PropertyHeader { + key: URID::try_from(header.key).map_err(|_| AtomReadError::InvalidAtomValue { + reading_type_uri: Self::uri(), + error_message: "Invalid object property key URID: 0", + })?, + context: URID::try_from(header.context).ok(), + }; + + let atom = reader.next_atom()?; + + Ok((header, atom)) + } + + /// Write out the header of a property atom. + /// + /// This method simply writes out the content of the header to the space and returns `Some(())` if it's successful. + #[inline] + fn write_header( + space: &mut impl SpaceWriter, + key: URID, + context: Option>, + ) -> Result<(), AtomWriteError> { + let header = StrippedPropertyHeader { + key: key.get(), + context: context.map(URID::get).unwrap_or(0), + }; + + space.write_value(header)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::atoms::object::{ObjectHeader, PropertyHeader}; + use crate::prelude::*; + use crate::space::*; + use crate::AtomHeader; + use std::mem::size_of; + use urid::*; + + #[test] + #[allow(clippy::float_cmp)] + fn test_object() { + let map = HashURIDMapper::new(); + let urids = AtomURIDCollection::from_map(&map).unwrap(); + + let object_type = map + .map_uri(Uri::from_bytes_with_nul(b"urn:my-type\0").unwrap()) + .unwrap(); + + let first_key = map + .map_uri(Uri::from_bytes_with_nul(b"urn:value-a\0").unwrap()) + .unwrap(); + let first_value: i32 = 17; + + let second_key = map + .map_uri(Uri::from_bytes_with_nul(b"urn:value-b\0").unwrap()) + .unwrap(); + let second_value: f32 = 42.0; + + let mut raw_space = AlignedVec::::new_with_capacity(64); + let raw_space = raw_space.as_space_mut(); + + // writing + { + let mut cursor = SpaceCursor::new(raw_space.as_bytes_mut()); + let frame = AtomWriter::write_new(&mut cursor, urids.object).unwrap(); + let mut writer = Object::write(frame) + .unwrap() + .write_header(ObjectHeader { + id: None, + otype: object_type, + }) + .unwrap(); + { + writer + .new_property(first_key, urids.int) + .unwrap() + .set(first_value) + .unwrap(); + } + { + writer + .new_property(second_key, urids.float) + .unwrap() + .set(second_value) + .unwrap(); + } + } + + // Atom header: size: u32, type: u32 + // Object header: id: u32 = None, otype: u32 = object_type + // Object prop header1: key: u32 = first_key, context: u32 = 0 + // Object prop body atom: size: u32 = 4 type: u32 = int + // Int atom value: i32 = 17, padding(4) + // Object prop header12 key: u32 = first_key, context: u32 = 0 + // Object prop body atom: size: u32 = 4 type: u32 = int + // Float atom value: i32 = 69, padding(4) + + // verifying + { + // Header + let atom = unsafe { raw_space.read().next_atom() }.unwrap(); + + assert_eq!(atom.header().urid(), urids.object); + assert_eq!( + atom.header().size_of_body(), + size_of::() + + size_of::() + + 2 * size_of::() + + size_of::() + + 2 * size_of::() + ); + + // Object. + let mut object_reader = atom.body().read(); + let object: &sys::LV2_Atom_Object_Body = unsafe { object_reader.next_value() }.unwrap(); + assert_eq!(object.id, 0); + assert_eq!(object.otype, object_type); + + // First property. + let property: &sys::LV2_Atom_Property_Body = + unsafe { object_reader.next_value() }.unwrap(); + assert_eq!(property.key, first_key); + assert_eq!(property.context, 0); + assert_eq!(property.value.type_, urids.int); + assert_eq!(property.value.size as usize, 2 * size_of::()); + + let value: &i32 = unsafe { object_reader.next_value() }.unwrap(); + assert_eq!(*value, first_value); + + // Second property. + let property: &sys::LV2_Atom_Property_Body = + unsafe { object_reader.next_value() }.unwrap(); + assert_eq!(property.key, second_key); + assert_eq!(property.context, 0); + assert_eq!(property.value.type_, urids.float); + assert_eq!(property.value.size as usize, 2 * size_of::()); + + let value: &f32 = unsafe { object_reader.next_value() }.unwrap(); + assert_eq!(*value, second_value); + assert_eq!(object_reader.remaining_bytes().len(), 4); + } + + // reading + { + let (header, iter) = unsafe { raw_space.read().next_atom() } + .unwrap() + .read(urids.object) + .unwrap(); + + assert_eq!(header.otype, object_type); + assert_eq!(header.id, None); + + let properties: Vec<(PropertyHeader, &UnidentifiedAtom)> = iter.collect(); + + let (header, atom) = properties[0]; + assert_eq!(header.key, first_key); + assert_eq!(*atom.read(urids.int).unwrap(), first_value); + + let (header, atom) = properties[1]; + assert_eq!(header.key, second_key); + assert_eq!(*atom.read(urids.float).unwrap(), second_value); + } + } +} diff --git a/atom/src/scalar.rs b/atom/src/atoms/scalar.rs similarity index 62% rename from atom/src/scalar.rs rename to atom/src/atoms/scalar.rs index 902572cd..2457d3f1 100644 --- a/atom/src/scalar.rs +++ b/atom/src/atoms/scalar.rs @@ -19,69 +19,76 @@ //! /// Something like a plugin's run method. //! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { //! // Scalar atoms don't need a reading parameter. -//! let read_value: f32 = ports.input.read(urids.float, ()).unwrap(); +//! let read_value: &f32 = ports.input.read(urids.float).unwrap(); //! //! // Writing is done with the value of the atom. -//! // You can modify it afterwards. -//! let written_value: &mut f32 = ports.output.init(urids.float, 17.0).unwrap(); +//! ports.output.write(urids.float).unwrap().set(17.0); //! } //! ``` //! //! # Specification //! //! [http://lv2plug.in/ns/ext/atom/atom.html#Number](http://lv2plug.in/ns/ext/atom/atom.html#Number) -use crate::space::*; use crate::*; -use std::marker::Unpin; +use core::marker::{PhantomData, Unpin}; use urid::UriBound; use urid::URID; /// An atom that only contains a single, scalar value. -/// -/// Since scalar values are so simple, the reading and writing methods are exactly the same. pub trait ScalarAtom: UriBound { /// The internal representation of the atom. /// - /// For example, the `Int` atom has the internal type of `i32`, which is `i32` on most platforms. + /// For example, the `Int` atom has the internal type of `i32`. type InternalType: Unpin + Copy + Send + Sync + Sized + 'static; +} - /// Try to read the atom from a space. - /// - /// If the space does not contain the atom or is not big enough, return `None`. The second return value is the space behind the atom. - fn read_scalar(body: Space) -> Option { - body.split_type::() - .map(|(value, _)| *value) - } +pub struct ScalarWriter<'a, T: Copy + 'static>(AtomWriter<'a>, PhantomData); - /// Try to write the atom into a space. +impl<'a, T: Copy + 'static> ScalarWriter<'a, T> { + /// Sets the value of the scalar + /// + /// # Errors /// - /// Write an atom with the value of `value` into the space and return a mutable reference to the written value. If the space is not big enough, return `None`. - fn write_scalar<'a, 'b>( - mut frame: FramedMutSpace<'a, 'b>, - value: Self::InternalType, - ) -> Option<&'a mut Self::InternalType> { - (&mut frame as &mut dyn MutSpace).write(&value, true) + /// Returns an error is the underlying buffer is out of space for the new scalar value. + #[inline] + pub fn set(&mut self, value: T) -> Result<&mut T, AtomWriteError> { + #[repr(align(8))] + #[derive(Copy, Clone)] + struct Padder; + + #[derive(Copy, Clone)] + #[repr(C)] + struct ScalarValue(T, Padder); + + // Scalars have extra padding due to previous header in LV2_ATOM_Int and such + Ok(&mut self.0.write_value(ScalarValue(value, Padder))?.0) } } -impl<'a, 'b, A: ScalarAtom> Atom<'a, 'b> for A -where - 'a: 'b, -{ - type ReadParameter = (); - type ReadHandle = A::InternalType; - type WriteParameter = A::InternalType; - type WriteHandle = &'a mut A::InternalType; - - fn read(body: Space<'a>, _: ()) -> Option { - ::read_scalar(body) +pub struct ScalarReaderHandle(PhantomData); +impl<'a, T: Copy + 'static> AtomHandle<'a> for ScalarReaderHandle { + type Handle = &'a T; +} + +pub struct ScalarWriterHandle(PhantomData); +impl<'a, T: Copy + 'static> AtomHandle<'a> for ScalarWriterHandle { + type Handle = ScalarWriter<'a, T>; +} + +impl Atom for A { + type ReadHandle = ScalarReaderHandle; + type WriteHandle = ScalarWriterHandle; + + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError> { + body.read().next_value() } - fn init( - frame: FramedMutSpace<'a, 'b>, - value: A::InternalType, - ) -> Option<&'a mut A::InternalType> { - ::write_scalar(frame, value) + fn write( + frame: AtomWriter, + ) -> Result<::Handle, AtomWriteError> { + Ok(ScalarWriter(frame, PhantomData)) } } @@ -162,11 +169,11 @@ make_scalar_atom!( #[cfg(test)] mod tests { + use crate::atoms::scalar::ScalarAtom; use crate::prelude::*; - use crate::scalar::ScalarAtom; use crate::space::*; + use crate::AtomHeader; use std::convert::TryFrom; - use std::mem::size_of; use urid::*; fn test_scalar(value: A::InternalType) @@ -177,36 +184,40 @@ mod tests { let map = HashURIDMapper::new(); let urid: URID = map.map_type().unwrap(); - let mut raw_space: Box<[u8]> = Box::new([0; 256]); + let mut raw_space = AlignedVec::::new_with_capacity(64); + let raw_space = raw_space.as_space_mut(); // writing { - let mut space = RootMutSpace::new(raw_space.as_mut()); - (&mut space as &mut dyn MutSpace).init(urid, value).unwrap(); + let mut space = SpaceCursor::new(raw_space.as_bytes_mut()); + space.write_atom(urid).unwrap().set(value).unwrap(); } // verifying { /// Generic version of the scalar atom structs. - #[repr(C)] + #[repr(C, align(8))] struct Scalar { atom: sys::LV2_Atom, body: B, } - let (scalar, _) = raw_space.split_at(size_of::()); + let scalar: &Scalar = + unsafe { raw_space.read().next_value().unwrap() }; - let scalar = unsafe { &*(scalar.as_ptr() as *const Scalar) }; assert_eq!(scalar.atom.type_, urid); - assert_eq!(scalar.atom.size as usize, size_of::()); + assert_eq!(scalar.atom.size as usize, 8); // All are always aligned and padded assert_eq!(scalar.body, value); } // reading { - let space = Space::from_slice(raw_space.as_ref()); - let (body, _) = space.split_atom_body(urid).unwrap(); - assert_eq!(A::read(body, ()).unwrap(), value); + let read_value = unsafe { raw_space.read().next_atom() } + .unwrap() + .read(urid) + .unwrap(); + + assert_eq!(*read_value, value); } } diff --git a/atom/src/atoms/sequence.rs b/atom/src/atoms/sequence.rs new file mode 100644 index 00000000..f078a871 --- /dev/null +++ b/atom/src/atoms/sequence.rs @@ -0,0 +1,402 @@ +//! An atom containing a sequence of time-stamped events. +//! +//! These events are atoms again. Atoms passed in a sequence can be handled with frame-perfect timing and therefore is the prefered way to transmit events like MIDI messages. However, MIDI messages are implemented in separate crate. +//! +//! # Example +//! +//! ``` +//! use lv2_core::prelude::*; +//! use lv2_units::prelude::*; +//! use lv2_atom::prelude::*; +//! use lv2_atom::atoms::sequence::*; +//! use urid::*; +//! +//! #[derive(PortCollection)] +//! struct MyPorts { +//! input: InputPort, +//! output: OutputPort, +//! } +//! +//! #[derive(URIDCollection)] +//! struct MyURIDs { +//! atom: AtomURIDCollection, +//! units: UnitURIDCollection, +//! } +//! +//! /// Something like a plugin's run method. +//! fn run(ports: &mut MyPorts, urids: &MyURIDs) { +//! // Get the read handle to the sequence. +//! // The reading method needs the URID of the BPM unit to tell if the time stamp +//! // is measured in beats or in frames. If the atom doesn't says that it's measured +//! // in beats, it is assumed that it is measured in frames. +//! let input_sequence: SequenceIterator = ports.input +//! .read(urids.atom.sequence) +//! .unwrap() +//! .with_unit(urids.units.frame).unwrap(); +//! +//! // Get the write handle to the sequence. +//! // You have to provide the unit of the time stamps. +//! let mut output_sequence: SequenceWriter = ports.output.write(urids.atom.sequence) +//! .unwrap() +//! .with_unit(urids.units.frame) +//! .unwrap(); +//! +//! // Iterate through all events in the input sequence. +//! // +//! // The specifications don't require the time stamps to be monotonic, your algorithms should +//! // be able to handle older events written after younger events. +//! // +//! // The sequence writer, however, assures that the written time stamps are monotonic. +//! for event in input_sequence { +//! // An event contains a timestamp and an atom. +//! let (timestamp, atom): (i64, &UnidentifiedAtom) = event; +//! // If the read atom is a 32-bit integer... +//! if let Ok(integer) = atom.read(urids.atom.int) { +//! // Multiply it by two and write it to the sequence. +//! output_sequence.new_event(timestamp, urids.atom.int).unwrap().set(*integer * 2).unwrap(); +//! } else { +//! // Forward the atom to the sequence without a change. +//! output_sequence.forward(timestamp, atom).unwrap(); +//! } +//! } +//! } +//! ``` +//! +//! # Specification +//! +//! [http://lv2plug.in/ns/ext/atom/atom.html#Sequence](http://lv2plug.in/ns/ext/atom/atom.html#Sequence) +mod unit; + +use crate::space::SpaceReader; +use crate::*; +use std::marker::PhantomData; +use sys::LV2_Atom_Event__bindgen_ty_1 as RawTimeStamp; +pub use unit::*; +use units::units::Frame; + +#[repr(C, align(8))] +#[derive(Copy, Clone)] +struct SequenceBody(sys::LV2_Atom_Sequence_Body); + +/// An atom containing a sequence of time-stamped events. +/// +/// [See also the module documentation.](index.html) +pub struct Sequence; + +unsafe impl UriBound for Sequence { + const URI: &'static [u8] = sys::LV2_ATOM__Sequence; +} + +pub struct SequenceReadHandle; +impl<'a> AtomHandle<'a> for SequenceReadHandle { + type Handle = SequenceHeaderReader<'a>; +} + +pub struct SequenceWriteHandle; +impl<'a> AtomHandle<'a> for SequenceWriteHandle { + type Handle = SequenceHeaderWriter<'a>; +} + +/// A type-state for the Sequence Reader, that reads the header of a sequence. +#[derive(Clone)] +pub struct SequenceHeaderReader<'a> { + header: &'a sys::LV2_Atom_Sequence_Body, + reader: SpaceReader<'a>, +} + +impl<'a> SequenceHeaderReader<'a> { + /// Tries to read the sequence as having timestamps of the given type. + /// + /// # Errors + /// + /// This method will return an `InvalidUrid` error if the given timestamp type URID does not + /// match the one of the sequence being currently read. + pub fn with_unit( + self, + timestamp_unit_urid: URID, + ) -> Result, AtomReadError> { + if (self.header.unit == 0 && U::TYPE == SequenceUnitType::Frame) + || (self.header.unit == timestamp_unit_urid) + { + Ok(SequenceIterator { + reader: self.reader, + unit_type: PhantomData, + }) + } else { + Err(AtomReadError::InvalidUrid { + expected_uri: U::uri(), + expected_urid: timestamp_unit_urid.into_general(), + found_urid: self.header.unit, + }) + } + } +} + +/// A type-state for the Sequence Writer, that writes the header of a sequence. +pub struct SequenceHeaderWriter<'a> { + writer: AtomWriter<'a>, +} + +impl<'a> SequenceHeaderWriter<'a> { + /// Initializes the sequence with the given timestamp type URID. + /// + /// The timestamp type can be either [`Frame`](lv2_units::units::Frame) or [`Beat`](lv2_units::units::Beat). + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + #[inline] + pub fn with_unit( + self, + timestamp_unit_urid: URID, + ) -> Result, AtomWriteError> { + self.with_unit_raw(timestamp_unit_urid.get()) + } + + /// Initializes the sequence with the [`Frame`](lv2_units::units::Frame) timestamp type. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + #[inline] + pub fn with_frame_unit(self) -> Result, AtomWriteError> { + self.with_unit_raw(0) + } + + fn with_unit_raw( + mut self, + timestamp_unit_urid: u32, + ) -> Result, AtomWriteError> { + let header = SequenceBody(sys::LV2_Atom_Sequence_Body { + unit: timestamp_unit_urid, + pad: 0, + }); + + self.writer.write_value(header)?; + + Ok(SequenceWriter { + writer: self.writer, + last_stamp: None, + }) + } +} + +impl Atom for Sequence { + type ReadHandle = SequenceReadHandle; + type WriteHandle = SequenceWriteHandle; + + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError> { + let mut reader = body.read(); + let header: &sys::LV2_Atom_Sequence_Body = reader.next_value()?; + + Ok(SequenceHeaderReader { reader, header }) + } + + fn write( + frame: AtomWriter, + ) -> Result<::Handle, AtomWriteError> { + Ok(SequenceHeaderWriter { writer: frame }) + } +} + +/// An iterator over all events in a sequence. +pub struct SequenceIterator<'a, U: SequenceUnit> { + reader: SpaceReader<'a>, + unit_type: PhantomData, +} + +impl<'a, U: SequenceUnit> Iterator for SequenceIterator<'a, U> { + type Item = (U::Value, &'a UnidentifiedAtom); + + fn next(&mut self) -> Option<(U::Value, &'a UnidentifiedAtom)> { + self.reader + .try_read(|reader| { + // SAFETY: The validity of the space's contents is guaranteed by this type. + let raw_stamp: &RawTimeStamp = unsafe { reader.next_value()? }; + + // SAFETY: The validity of the unit type is guaranteed by this type. + let stamp = unsafe { U::convert_from_raw(*raw_stamp) }; + + // SAFETY: The validity of the space's contents is guaranteed by this type. + let atom = unsafe { reader.next_atom()? }; + + Ok((stamp, atom)) + }) + .ok() + } +} + +/// The writing handle for sequences. +pub struct SequenceWriter<'a, U: SequenceUnit> { + writer: AtomWriter<'a>, + last_stamp: Option, +} + +impl<'a, U: SequenceUnit> SequenceWriter<'a, U> { + /// Write out the time stamp and update `last_stamp`. + /// + /// # Errors + /// + /// This method will return an error if the given timestamp is smaller than the last written + /// timestamp (if any). + /// + /// This method will also return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + fn write_time_stamp(&mut self, time_stamp: U::Value) -> Result<(), AtomWriteError> { + if let Some(last_stamp) = self.last_stamp { + if last_stamp > time_stamp { + return Err(AtomWriteError::IllegalOperation { + writing_type_uri: Sequence::uri(), + error_message: + "Attempted to write event with an earlier timestamp than the previous event", + }); + } + } + + self.last_stamp = Some(time_stamp); + self.writer.write_value(U::convert_into_raw(time_stamp))?; + + Ok(()) + } + + /// Initialize an event's atom, with the given timestamp. + /// + /// # Errors + /// + /// This method will return an error if the given timestamp is smaller than the last written + /// timestamp (if any). + /// + /// This method will also return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + pub fn new_event( + &mut self, + time_stamp: U::Value, + urid: URID, + ) -> Result<::Handle, AtomWriteError> { + self.write_time_stamp(time_stamp)?; + self.writer.write_atom(urid) + } + + /// Writes an unidentified atom to the sequence, with the given timestamp. + /// + /// # Errors + /// + /// This method will return an error if the given timestamp is smaller than the last written + /// timestamp (if any). + /// + /// This method will also return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + pub fn forward( + &mut self, + time_stamp: U::Value, + atom: &UnidentifiedAtom, + ) -> Result<(), AtomWriteError> { + self.write_time_stamp(time_stamp)?; + + self.writer.copy_atom(atom)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::atoms::sequence::*; + use crate::prelude::*; + use std::mem::size_of; + use units::UnitURIDCollection; + + #[derive(URIDCollection)] + struct TestURIDCollection { + atom: AtomURIDCollection, + units: UnitURIDCollection, + } + + #[test] + fn test_sequence() { + let map = HashURIDMapper::new(); + let urids: TestURIDCollection = TestURIDCollection::from_map(&map).unwrap(); + + let mut raw_space = AlignedVec::::new_with_capacity(64); + let raw_space = raw_space.as_space_mut(); + + // writing + { + let mut space = SpaceCursor::new(raw_space.as_bytes_mut()); + let mut writer = space + .write_atom(urids.atom.sequence) + .unwrap() + .with_unit(urids.units.frame) + .unwrap(); + + writer + .new_event(0, urids.atom.int) + .unwrap() + .set(42) + .unwrap(); + + writer + .new_event(1, urids.atom.long) + .unwrap() + .set(17) + .unwrap(); + } + + // verifying + + { + let mut reader = raw_space.read(); + let sequence: &sys::LV2_Atom_Sequence = unsafe { reader.next_value() }.unwrap(); + assert_eq!(sequence.atom.type_, urids.atom.sequence); + assert_eq!( + sequence.atom.size as usize, + size_of::() + + size_of::() + + size_of::() // Int struct Includes padding + + size_of::() + + size_of::() + ); + assert_eq!(sequence.body.unit, urids.units.frame); + + let stamp: &RawTimeStamp = unsafe { reader.next_value() }.unwrap(); + assert_eq!(unsafe { stamp.frames }, 0); + + let int: &sys::LV2_Atom_Int = unsafe { reader.next_value() }.unwrap(); + assert_eq!(int.atom.type_, urids.atom.int); + assert_eq!(int.atom.size as usize, 2 * size_of::()); + assert_eq!(int.body, 42); + + let stamp: &RawTimeStamp = unsafe { reader.next_value() }.unwrap(); + assert_eq!(unsafe { stamp.frames }, 1); + + let int: &sys::LV2_Atom_Int = unsafe { reader.next_value() }.unwrap(); + assert_eq!(int.atom.type_, urids.atom.long); + assert_eq!(int.atom.size as usize, size_of::()); + assert_eq!(int.body, 17); + } + + // reading + { + let mut reader = unsafe { raw_space.read().next_atom() } + .unwrap() + .read(urids.atom.sequence) + .unwrap() + .with_unit(urids.units.frame) + .unwrap(); + + let (stamp, atom) = reader.next().unwrap(); + assert_eq!(stamp, 0); + assert_eq!(*atom.read::(urids.atom.int).unwrap(), 42); + + let (stamp, atom) = reader.next().unwrap(); + assert_eq!(stamp, 1); + assert_eq!(*atom.read::(urids.atom.long).unwrap(), 17); + + assert!(reader.next().is_none()); + } + } +} diff --git a/atom/src/atoms/sequence/unit.rs b/atom/src/atoms/sequence/unit.rs new file mode 100644 index 00000000..ade27169 --- /dev/null +++ b/atom/src/atoms/sequence/unit.rs @@ -0,0 +1,64 @@ +use lv2_sys::LV2_Atom_Event__bindgen_ty_1; +use lv2_units::units::{Beat, Frame}; +use urid::UriBound; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum SequenceUnitType { + Beat, + Frame, +} + +pub trait SequenceUnit: UriBound + private::Sealed { + type Value: Copy + PartialEq + PartialOrd + 'static; + + const TYPE: SequenceUnitType; + + #[doc(hidden)] + unsafe fn convert_from_raw(raw: sys::LV2_Atom_Event__bindgen_ty_1) -> Self::Value; + + #[doc(hidden)] + fn convert_into_raw(value: Self::Value) -> private::TimestampBody; +} + +impl SequenceUnit for Beat { + type Value = f64; + const TYPE: SequenceUnitType = SequenceUnitType::Beat; + + #[inline] + unsafe fn convert_from_raw(raw: LV2_Atom_Event__bindgen_ty_1) -> Self::Value { + raw.beats + } + + #[inline] + fn convert_into_raw(value: Self::Value) -> private::TimestampBody { + private::TimestampBody(LV2_Atom_Event__bindgen_ty_1 { beats: value }) + } +} + +impl SequenceUnit for Frame { + type Value = i64; + const TYPE: SequenceUnitType = SequenceUnitType::Frame; + + #[inline] + unsafe fn convert_from_raw(raw: LV2_Atom_Event__bindgen_ty_1) -> Self::Value { + raw.frames + } + + #[inline] + fn convert_into_raw(value: Self::Value) -> private::TimestampBody { + private::TimestampBody(LV2_Atom_Event__bindgen_ty_1 { frames: value }) + } +} + +mod private { + use super::*; + + #[repr(C, align(8))] + #[derive(Copy, Clone)] + pub struct TimestampBody(pub LV2_Atom_Event__bindgen_ty_1); + + pub trait Sealed {} + + impl Sealed for Beat {} + impl Sealed for Frame {} +} diff --git a/atom/src/atoms/string.rs b/atom/src/atoms/string.rs new file mode 100644 index 00000000..d5bb52be --- /dev/null +++ b/atom/src/atoms/string.rs @@ -0,0 +1,344 @@ +//! String handling atoms. +//! +//! This module contains two different atoms: The [`String`](struct.String.html) and the [`Literal`](struct.Literal.html). The former is for simple, non-localized UTF-8 strings, like URIs or paths, and the later is either for localized text, e.g. descriptions in the user interface, or RDF literals. +//! +//! Reading and writing these atoms is pretty simple: They don't require a parameter and return a either a `&str` or the literal info and a `&str`. Writing is done with a writing handle which can append strings to the string/literal. When dropped, the handle will append the null character, you therefore don't have to handle it on your own. +//! +//! # Example +//! ``` +//! use lv2_core::prelude::*; +//! use lv2_atom::prelude::*; +//! use lv2_atom::atoms::string::StringWriter; +//! +//! #[derive(PortCollection)] +//! struct MyPorts { +//! input: InputPort, +//! output: OutputPort, +//! } +//! +//! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { +//! let input: &str = ports.input.read(urids.string).unwrap(); +//! let mut writer: StringWriter = ports.output.write(urids.string).unwrap(); +//! writer.append(input).unwrap(); +//! } +//! ``` +//! +//! # Specifications +//! +//! [http://lv2plug.in/ns/ext/atom/atom.html#String](http://lv2plug.in/ns/ext/atom/atom.html#String) +//! [http://lv2plug.in/ns/ext/atom/atom.html#Literal](http://lv2plug.in/ns/ext/atom/atom.html#Literal) +use crate::prelude::*; +use crate::space::error::{AtomReadError, AtomWriteError}; +use crate::space::*; +use crate::AtomHandle; +use std::ffi::CStr; +use urid::*; + +/// An atom containing either a localized string or an RDF literal. +/// +/// [See also the module documentation.](index.html) +pub struct Literal; + +unsafe impl UriBound for Literal { + const URI: &'static [u8] = sys::LV2_ATOM__Literal; +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// The type or language URID of a literal. +pub enum LiteralInfo { + Language(URID), + Datatype(URID), +} + +impl LiteralInfo { + fn try_from_raw(header: &sys::LV2_Atom_Literal_Body) -> Result { + match (URID::new(header.lang), URID::new(header.datatype)) { + (Some(urid), _) => Ok(LiteralInfo::Language(urid)), + (None, Some(urid)) => Ok(LiteralInfo::Datatype(urid)), + (None, None) => Err("Invalid Literal header: neither lang or datatype URIDs are set"), + } + } + + fn into_raw(self) -> sys::LV2_Atom_Literal_Body { + match self { + LiteralInfo::Language(lang) => sys::LV2_Atom_Literal_Body { + lang: lang.get(), + datatype: 0, + }, + LiteralInfo::Datatype(datatype) => sys::LV2_Atom_Literal_Body { + lang: 0, + datatype: datatype.get(), + }, + } + } +} + +/// A type-state for the Literal Writer, that writes the info header of a literal. +pub struct LiteralInfoWriter<'a> { + writer: AtomWriter<'a>, +} + +impl<'a> LiteralInfoWriter<'a> { + /// Initializes the literal with the given info. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + pub fn write_info(mut self, info: LiteralInfo) -> Result, AtomWriteError> { + self.writer.write_value(info.into_raw())?; + + Ok(StringWriter { + writer: self.writer.terminated(0), + }) + } +} + +pub struct LiteralReadHandle; + +impl<'a> AtomHandle<'a> for LiteralReadHandle { + type Handle = (LiteralInfo, &'a str); +} + +pub struct LiteralWriteHandle; + +impl<'a> AtomHandle<'a> for LiteralWriteHandle { + type Handle = LiteralInfoWriter<'a>; +} + +impl Atom for Literal { + type ReadHandle = LiteralReadHandle; + type WriteHandle = LiteralWriteHandle; + + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError> { + let mut reader = body.read(); + let header: &sys::LV2_Atom_Literal_Body = reader.next_value()?; + + let info = + LiteralInfo::try_from_raw(header).map_err(|err| AtomReadError::InvalidAtomValue { + reading_type_uri: Self::uri(), + error_message: err, + })?; + + let data = reader.remaining_bytes(); + + std::str::from_utf8(&data[0..data.len() - 1]) + .or_else(|error| std::str::from_utf8(&data[0..error.valid_up_to()])) + .map_err(|_| AtomReadError::InvalidAtomValue { + reading_type_uri: Self::uri(), + error_message: "Literal contents are invalid UTF-8", + }) + .map(|string| (info, string)) + } + + #[inline] + fn write( + frame: AtomWriter, + ) -> Result<::Handle, AtomWriteError> { + Ok(LiteralInfoWriter { writer: frame }) + } +} + +pub struct StringReadHandle; + +impl<'a> AtomHandle<'a> for StringReadHandle { + type Handle = &'a str; +} + +pub struct StringWriteHandle; + +impl<'a> AtomHandle<'a> for StringWriteHandle { + type Handle = StringWriter<'a>; +} + +/// An atom containing a UTF-8 encoded string. +/// +/// [See also the module documentation.](index.html) +pub struct String; + +unsafe impl UriBound for String { + const URI: &'static [u8] = sys::LV2_ATOM__String; +} + +impl Atom for String { + type ReadHandle = StringReadHandle; + type WriteHandle = StringWriteHandle; + + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError> { + let c_str = CStr::from_bytes_with_nul(body.as_bytes()).map_err(|_| { + AtomReadError::InvalidAtomValue { + reading_type_uri: Self::uri(), + error_message: "String value is not null-terminated", + } + })?; + + let str = c_str + .to_str() + .map_err(|_| AtomReadError::InvalidAtomValue { + reading_type_uri: Self::uri(), + error_message: "String contents are invalid UTF-8", + })?; + + Ok(str) + } + + fn write( + frame: AtomWriter, + ) -> Result<::Handle, AtomWriteError> { + Ok(StringWriter { + writer: frame.terminated(0), + }) + } +} + +/// Handle to append strings to a string or literal. +pub struct StringWriter<'a> { + writer: Terminated>, +} + +impl<'a> StringWriter<'a> { + /// Appends a string to the atom's buffer. + /// + /// This method copies the given string to the end of the string atom, and then returns a + /// mutable reference to the copy. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer for, + /// the given additional string, or if any other write error occurs. + pub fn append(&mut self, string: &str) -> Result<&mut str, AtomWriteError> { + let bytes = self.writer.write_bytes(string.as_bytes())?; + + // SAFETY: We just wrote that string, therefore it is guaranteed to be valid UTF-8 + unsafe { Ok(std::str::from_utf8_unchecked_mut(bytes)) } + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + use crate::space::*; + use crate::AtomHeader; + use std::ffi::CStr; + use std::mem::{size_of, size_of_val}; + use urid::*; + + struct German; + unsafe impl UriBound for German { + const URI: &'static [u8] = b"http://lexvo.org/id/iso639-1/de\0"; + } + + #[derive(URIDCollection)] + pub struct TestURIDs { + atom: AtomURIDCollection, + german: URID, + } + + const SAMPLE0: &str = "Da steh ich nun, ich armer Tor! "; + const SAMPLE1: &str = "Und bin so klug als wie zuvor;"; + + #[test] + fn test_literal() { + let map = HashURIDMapper::new(); + let urids: TestURIDs = TestURIDs::from_map(&map).unwrap(); + + let mut raw_space = AlignedVec::::new_with_capacity(64); + let raw_space = raw_space.as_space_mut(); + + // writing + { + let mut space = SpaceCursor::new(raw_space.as_bytes_mut()); + + let mut writer = space + .write_atom(urids.atom.literal) + .unwrap() + .write_info(LiteralInfo::Language(urids.german.into_general())) + .unwrap(); + + writer.append(SAMPLE0).unwrap(); + writer.append(SAMPLE1).unwrap(); + } + + // verifying + { + let mut reader = raw_space.read(); + let literal: &sys::LV2_Atom_Literal = unsafe { reader.next_value() }.unwrap(); + + assert_eq!(literal.atom.type_, urids.atom.literal.get()); + assert_eq!( + literal.atom.size as usize, + size_of::() + + size_of_val(SAMPLE0) + + size_of_val(SAMPLE1) + + 1 + ); + assert_eq!(literal.body.lang, urids.german.get()); + assert_eq!(literal.body.datatype, 0); + + let size = literal.atom.size as usize - size_of::(); + let string = CStr::from_bytes_with_nul(reader.next_bytes(size).unwrap()) + .unwrap() + .to_str() + .unwrap(); + assert_eq!(SAMPLE0.to_owned() + SAMPLE1, string); + } + + // reading + { + let (info, text) = unsafe { + raw_space + .read() + .next_atom() + .unwrap() + .read(urids.atom.literal) + } + .unwrap(); + + assert_eq!(info, LiteralInfo::Language(urids.german.into_general())); + assert_eq!(text, SAMPLE0.to_owned() + SAMPLE1); + } + } + + #[test] + fn test_string() { + let map = HashURIDMapper::new(); + let urids = crate::atoms::AtomURIDCollection::from_map(&map).unwrap(); + + let mut raw_space = AlignedVec::::new_with_capacity(64); + let raw_space = raw_space.as_space_mut(); + + // writing + { + let mut space = SpaceCursor::new(raw_space.as_bytes_mut()); + + let mut writer = space.write_atom(urids.string).unwrap(); + writer.append(SAMPLE0).unwrap(); + writer.append(SAMPLE1).unwrap(); + } + + // verifying + { + let mut reader = raw_space.read(); + let string: &sys::LV2_Atom_String = unsafe { reader.next_value() }.unwrap(); + assert_eq!(string.atom.type_, urids.string); + assert_eq!(string.atom.size as usize, SAMPLE0.len() + SAMPLE1.len() + 1); + + let string = + std::str::from_utf8(reader.next_bytes(string.atom.size as usize).unwrap()).unwrap(); + assert_eq!(string[..string.len() - 1], SAMPLE0.to_owned() + SAMPLE1); + } + + // reading + { + let string = unsafe { raw_space.read().next_atom() } + .unwrap() + .read(urids.string) + .unwrap(); + assert_eq!(string, SAMPLE0.to_owned() + SAMPLE1); + } + } +} diff --git a/atom/src/atoms/tuple.rs b/atom/src/atoms/tuple.rs new file mode 100644 index 00000000..48fd7622 --- /dev/null +++ b/atom/src/atoms/tuple.rs @@ -0,0 +1,191 @@ +//! An atom containg a series of other atoms. +//! +//! This atom is just like a [sequence](../sequence/index.html), only without time stamps: It contains multiple arbitrary atoms which you can either iterate through or write in sequence. +//! +//! # Example +//! ``` +//! use lv2_core::prelude::*; +//! use lv2_atom::prelude::*; +//! use lv2_atom::atoms::tuple::{TupleIterator, TupleWriter}; +//! +//! #[derive(PortCollection)] +//! struct MyPorts { +//! input: InputPort, +//! output: OutputPort, +//! } +//! +//! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { +//! let input: TupleIterator = ports.input.read(urids.tuple).unwrap(); +//! let mut output: TupleWriter = ports.output.write(urids.tuple).unwrap(); +//! for atom in input { +//! if let Ok(integer) = atom.read(urids.int) { +//! output.init(urids.int).unwrap().set(*integer * 2).unwrap(); +//! } else { +//! output.init(urids.int).unwrap().set(-1).unwrap(); +//! } +//! } +//! } +//! ``` +//! +//! # Specification +//! +//! [http://lv2plug.in/ns/ext/atom/atom.html#Tuple](http://lv2plug.in/ns/ext/atom/atom.html#Tuple) +use crate::space::SpaceReader; +use crate::*; + +/// An atom containing a series of other atoms. +/// +/// [See also the module documentation.](index.html) +pub struct Tuple; + +unsafe impl UriBound for Tuple { + const URI: &'static [u8] = sys::LV2_ATOM__Tuple; +} + +pub struct TupleReadHandle; + +impl<'a> AtomHandle<'a> for TupleReadHandle { + type Handle = TupleIterator<'a>; +} + +pub struct TupleWriteHandle; + +impl<'a> AtomHandle<'a> for TupleWriteHandle { + type Handle = TupleWriter<'a>; +} + +impl Atom for Tuple { + type ReadHandle = TupleReadHandle; + type WriteHandle = TupleWriteHandle; + + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError> { + Ok(TupleIterator { + reader: body.read(), + }) + } + + fn write( + frame: AtomWriter, + ) -> Result<::Handle, AtomWriteError> { + Ok(TupleWriter { frame }) + } +} + +/// An iterator over all atoms in a tuple. +/// +/// The item of this iterator is simply the space a single atom occupies. +pub struct TupleIterator<'a> { + reader: SpaceReader<'a>, +} + +impl<'a> Iterator for TupleIterator<'a> { + type Item = &'a UnidentifiedAtom; + + fn next(&mut self) -> Option<&'a UnidentifiedAtom> { + // SAFETY: the validity of the given space is guaranteed by this type. + unsafe { self.reader.next_atom() }.ok() + } +} + +/// The writing handle to add atoms to a tuple. +pub struct TupleWriter<'a> { + frame: AtomWriter<'a>, +} + +impl<'a> TupleWriter<'a> { + /// Initialize a new tuple element atom of a given type. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + pub fn init( + &mut self, + child_urid: URID, + ) -> Result<::Handle, AtomWriteError> { + self.frame.write_atom(child_urid) + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + use crate::space::*; + use crate::AtomHeader; + use std::mem::size_of; + use urid::*; + + #[test] + fn test_tuple() { + let map = HashURIDMapper::new(); + let urids = crate::atoms::AtomURIDCollection::from_map(&map).unwrap(); + + let mut raw_space = AlignedVec::::new_with_capacity(64); + let raw_space = raw_space.as_space_mut(); + + // writing + { + let mut cursor = raw_space.write(); + let mut writer = cursor.write_atom(urids.tuple).unwrap(); + { + let mut vector_writer = writer + .init(urids.vector) + .unwrap() + .of_type(urids.int) + .unwrap(); + vector_writer.append(&[17; 9]).unwrap(); + } + writer.init(urids.int).unwrap().set(42).unwrap(); + } + + // verifying + { + let atom = unsafe { raw_space.read().next_atom() }.unwrap(); + let header = atom.header(); + assert_eq!(header.urid(), urids.tuple); + assert_eq!( + header.size_of_body(), + size_of::() + + size_of::() * 9 + + 4 + + size_of::() + ); + + let mut reader = atom.body().read(); + let vector: &sys::LV2_Atom_Vector = unsafe { reader.next_value().unwrap() }; + + assert_eq!(vector.atom.type_, urids.vector); + assert_eq!( + vector.atom.size as usize, + size_of::() + size_of::() * 9 + ); + assert_eq!(vector.body.child_size as usize, size_of::()); + assert_eq!(vector.body.child_type, urids.int); + + let vector_items = unsafe { reader.next_values::(9) }.unwrap(); + assert_eq!(vector_items, &[17; 9]); + + let int: &sys::LV2_Atom_Int = unsafe { reader.next_value() }.unwrap(); + assert_eq!(int.atom.type_, urids.int); + assert_eq!(int.atom.size as usize, size_of::()); + assert_eq!(int.body, 42); + } + + // reading + { + let body = unsafe { raw_space.read().next_atom().unwrap().body() }; + let items: Vec<&UnidentifiedAtom> = unsafe { Tuple::read(body) }.unwrap().collect(); + assert_eq!( + items[0] + .read(urids.vector) + .unwrap() + .of_type(urids.int) + .unwrap(), + [17; 9] + ); + assert_eq!(*items[1].read(urids.int).unwrap(), 42); + } + } +} diff --git a/atom/src/atoms/vector.rs b/atom/src/atoms/vector.rs new file mode 100644 index 00000000..5a2571ab --- /dev/null +++ b/atom/src/atoms/vector.rs @@ -0,0 +1,288 @@ +//! An atom containg an array of scalar atom bodies. +//! +//! This atom is able to handle arrays (aka slices) of the internal types of scalar atoms. +//! +//! Reading a vector requires the URID fo the scalar that's been used and the reading process fails if the vector does not contain the requested scalar atom. The return value of the reading process is a slice of the internal type. +//! +//! Writing a vector is done with a writer that appends slices to the atom. +//! +//! # Example +//! ``` +//! use lv2_core::prelude::*; +//! use lv2_atom::prelude::*; +//! use lv2_atom::atoms::vector::VectorWriter; +//! +//! #[derive(PortCollection)] +//! struct MyPorts { +//! input: InputPort, +//! output: OutputPort, +//! } +//! +//! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { +//! let input: &[i32] = ports.input.read(urids.vector).unwrap().of_type(urids.int).unwrap(); +//! let mut output: VectorWriter = ports.output.write(urids.vector).unwrap().of_type(urids.int).unwrap(); +//! output.append(input).unwrap(); +//! } +//! ``` +//! +//! You may note that, unlike other atoms, the vector's URID is retrieved by calling the `vector` method. This is because two vectors with a different item type are considered two different types, and therefore would have the different URIDs. In reality, however, all vectors have the same URID and the `vector` method returns it with the fitting type. +//! +//! # Specification +//! +//! [http://lv2plug.in/ns/ext/atom/atom.html#Vector](http://lv2plug.in/ns/ext/atom/atom.html#Vector) +use crate::atoms::scalar::ScalarAtom; +use crate::space::SpaceReader; +use crate::*; +use std::marker::PhantomData; +use std::mem::{size_of, MaybeUninit}; + +/// An atom containing an homogenous array of scalar atom bodies. +/// +/// [See also the module documentation.](index.html) +pub struct Vector; + +unsafe impl UriBound for Vector { + const URI: &'static [u8] = sys::LV2_ATOM__Vector; +} + +pub struct VectorReadHandle; + +impl<'a> AtomHandle<'a> for VectorReadHandle { + type Handle = VectorReader<'a>; +} + +pub struct VectorWriteHandle; + +impl<'a> AtomHandle<'a> for VectorWriteHandle { + type Handle = VectorTypeWriter<'a>; +} + +/// A type-state for the Vector Reader, that reads the header of a vector to figure out its internal +/// type. +pub struct VectorReader<'a> { + reader: SpaceReader<'a>, + header: &'a sys::LV2_Atom_Vector_Body, +} + +impl<'a> VectorReader<'a> { + /// Attempts to read the vector as containing a given atom type. + /// + /// # Errors + /// + /// This method will return an error if the type or size of the atoms contained do not match the + /// vector being currently read. + pub fn of_type( + self, + atom_type: URID, + ) -> Result<&'a [C::InternalType], AtomReadError> { + if self.header.child_type != atom_type { + let found_urid = + URID::new(self.header.child_type).ok_or(AtomReadError::InvalidAtomValue { + reading_type_uri: Vector::uri(), + error_message: "Invalid child type URID (0)", + })?; + + return Err(AtomReadError::AtomUridMismatch { + found_urid, + expected_urid: atom_type.into_general(), + expected_uri: C::uri(), + }); + } + + if self.header.child_size as usize != size_of::() { + return Err(AtomReadError::InvalidAtomValue { + reading_type_uri: Vector::uri(), + error_message: "child_size value does not match actual size of type", + }); + } + + // SAFETY: The data type has just been checked above, and we can assume this data was + // properly initialized by the host. + Ok(unsafe { self.reader.as_slice() }?) + } + + /// Returns the length, i.e. number of elements in the vector, without knowing their type. + /// + /// This can be figured out thanks to the `child_size` attribute in a vector atom header. + /// + /// This will always return zero if the elements are zero-sized. + #[inline] + pub fn len(&self) -> usize { + self.reader + .remaining_bytes() + .len() + .checked_div(self.header.child_size as usize) + .unwrap_or(0) + } + + /// Returns if the vector is empty, i.e. its `len` is zero. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +pub struct VectorTypeWriter<'a> { + writer: AtomWriter<'a>, +} + +impl<'a> VectorTypeWriter<'a> { + /// Initializes the vector with the given child type URID. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + pub fn of_type( + mut self, + atom_type: URID, + ) -> Result, AtomWriteError> { + let body = sys::LV2_Atom_Vector_Body { + child_type: atom_type.get(), + child_size: size_of::() as u32, + }; + + self.writer.write_value(body)?; + + Ok(VectorWriter { + writer: self.writer, + type_: PhantomData, + }) + } +} + +impl Atom for Vector { + type ReadHandle = VectorReadHandle; + type WriteHandle = VectorWriteHandle; + + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError> { + let mut reader = body.read(); + let header: &sys::LV2_Atom_Vector_Body = reader.next_value()?; + + Ok(VectorReader { reader, header }) + } + + fn write( + writer: AtomWriter, + ) -> Result<::Handle, AtomWriteError> { + Ok(VectorTypeWriter { writer }) + } +} + +/// Handle to append elements to a vector. +/// +/// This works by allocating a slice of memory behind the vector and then writing your data to it. +pub struct VectorWriter<'a, A: ScalarAtom> { + writer: AtomWriter<'a>, + type_: PhantomData, +} + +impl<'a, A: ScalarAtom> VectorWriter<'a, A> { + /// Push a single value to the vector. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + #[inline] + pub fn push(&mut self, child: A::InternalType) -> Result<&mut A::InternalType, AtomWriteError> { + self.writer.write_value(child) + } + + /// Allocates a slice of initialized memory from the vector. + /// + /// This is useful if you need deferred initialization of the vector's contents. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + #[inline] + pub fn allocate_uninit( + &mut self, + count: usize, + ) -> Result<&mut [MaybeUninit], AtomWriteError> { + self.writer.allocate_values(count) + } + + /// Append multiple elements to the vector. + /// + /// # Errors + /// + /// This method will return an error if there is not enough space in the underlying buffer, + /// or if any other write error occurs. + #[inline] + pub fn append( + &mut self, + data: &[A::InternalType], + ) -> Result<&mut [A::InternalType], AtomWriteError> { + self.writer.write_values(data) + } +} + +#[cfg(test)] +mod tests { + use crate::atoms::AtomURIDCollection; + use crate::space::*; + use crate::AtomHeader; + use std::mem::size_of; + use urid::*; + + #[test] + fn test_vector() { + const CHILD_COUNT: usize = 17; + + let map = HashURIDMapper::new(); + let urids: AtomURIDCollection = AtomURIDCollection::from_map(&map).unwrap(); + + let mut raw_space = AlignedVec::::new_with_capacity(64); + let raw_space = raw_space.as_space_mut(); + + // writing + { + let mut space = SpaceCursor::new(raw_space.as_bytes_mut()); + let mut writer = space + .write_atom(urids.vector) + .unwrap() + .of_type(urids.int) + .unwrap(); + + writer.append(&[42; CHILD_COUNT - 1]).unwrap(); + writer.push(1).unwrap(); + } + + // verifying + { + let mut reader = raw_space.read(); + let vector: &sys::LV2_Atom_Vector = unsafe { reader.next_value() }.unwrap(); + assert_eq!(vector.atom.type_, urids.vector.get()); + assert_eq!( + vector.atom.size as usize, + size_of::() + size_of::() * CHILD_COUNT + ); + assert_eq!(vector.body.child_size as usize, size_of::()); + assert_eq!(vector.body.child_type, urids.int.get()); + + let children = unsafe { reader.next_values::(CHILD_COUNT) }.unwrap(); + assert_eq!(children.len(), CHILD_COUNT); + for value in &children[..CHILD_COUNT - 1] { + assert_eq!(*value, 42); + } + assert_eq!(children[children.len() - 1], 1); + } + + // reading + { + let atom = unsafe { raw_space.read().next_atom() }.unwrap(); + let children: &[i32] = atom.read(urids.vector).unwrap().of_type(urids.int).unwrap(); + + assert_eq!(children.len(), CHILD_COUNT); + for i in &children[..CHILD_COUNT - 1] { + assert_eq!(*i, 42); + } + assert_eq!(children[children.len() - 1], 1); + } + } +} diff --git a/atom/src/chunk.rs b/atom/src/chunk.rs deleted file mode 100644 index 86ff77e2..00000000 --- a/atom/src/chunk.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! An atom containing memory of undefined type. -//! -//! This contents of this atom is considered as a simple blob of data. It used, for example, by the host to transmit the size of a writable atom port. Since it is so simple, it does not need a reading or writing parameter. -//! -//! # Example -//! ``` -//! use lv2_core::prelude::*; -//! use lv2_atom::prelude::*; -//! -//! #[derive(PortCollection)] -//! struct MyPorts { -//! input: InputPort, -//! output: OutputPort, -//! } -//! -//! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { -//! let in_chunk: &[u8] = ports.input.read(urids.chunk, ()).unwrap(); -//! let mut out_chunk: FramedMutSpace = ports.output.init(urids.chunk, ()).unwrap(); -//! -//! out_chunk.write_raw(in_chunk, false).unwrap(); -//! } -//! ``` -//! -//! # Specification -//! -//! [http://lv2plug.in/ns/ext/atom/atom.html#Chunk](http://lv2plug.in/ns/ext/atom/atom.html#Chunk) -use crate::space::*; -use crate::Atom; -use urid::UriBound; - -/// An atom containing memory of undefined type. -/// -/// [See also the module documentation.](index.html) -pub struct Chunk; - -unsafe impl UriBound for Chunk { - const URI: &'static [u8] = sys::LV2_ATOM__Chunk; -} - -impl<'a, 'b> Atom<'a, 'b> for Chunk -where - 'a: 'b, -{ - type ReadParameter = (); - type ReadHandle = &'a [u8]; - type WriteParameter = (); - type WriteHandle = FramedMutSpace<'a, 'b>; - - fn read(space: Space<'a>, _: ()) -> Option<&'a [u8]> { - space.data() - } - - fn init(frame: FramedMutSpace<'a, 'b>, _: ()) -> Option> { - Some(frame) - } -} - -#[cfg(test)] -mod tests { - use crate::chunk::*; - use crate::*; - use std::mem::size_of; - use urid::*; - - #[test] - fn test_chunk_and_slice_writer() { - const SLICE_LENGTH: usize = 42; - - let map = HashURIDMapper::new(); - let urids = crate::AtomURIDCollection::from_map(&map).unwrap(); - - let mut raw_space: Box<[u8]> = Box::new([0; 256]); - - // writing - { - let mut space = RootMutSpace::new(raw_space.as_mut()); - let mut writer = (&mut space as &mut dyn MutSpace) - .init(urids.chunk, ()) - .unwrap(); - - for (i, value) in writer - .allocate(SLICE_LENGTH - 1, false) - .map(|(_, data)| data) - .unwrap() - .into_iter() - .enumerate() - { - *value = i as u8; - } - (&mut writer as &mut dyn MutSpace) - .write(&41u8, false) - .unwrap(); - } - - // verifying - { - let raw_space = raw_space.as_ref(); - let (atom, data) = raw_space.split_at(size_of::()); - - let atom = unsafe { &*(atom.as_ptr() as *const sys::LV2_Atom) }; - assert_eq!(atom.size as usize, SLICE_LENGTH); - assert_eq!(atom.type_, urids.chunk.get()); - - let data = data.split_at(SLICE_LENGTH).0; - for i in 0..SLICE_LENGTH { - assert_eq!(data[i] as usize, i); - } - } - - // reading - { - let space = Space::from_reference(raw_space.as_ref()); - - let data = Chunk::read(space.split_atom_body(urids.chunk).unwrap().0, ()).unwrap(); - assert_eq!(data.len(), SLICE_LENGTH); - - for (i, value) in data.iter().enumerate() { - assert_eq!(*value as usize, i); - } - } - } -} diff --git a/atom/src/header.rs b/atom/src/header.rs new file mode 100644 index 00000000..5a0a6eb8 --- /dev/null +++ b/atom/src/header.rs @@ -0,0 +1,66 @@ +use crate::space::error::AtomReadError; +use crate::Atom; +use urid::URID; + +#[repr(C, align(8))] +#[derive(Copy, Clone)] +pub struct AtomHeader { + inner: lv2_sys::LV2_Atom, +} + +impl AtomHeader { + #[inline] + pub(crate) fn new(atom_type: URID) -> Self { + Self { + inner: lv2_sys::LV2_Atom { + size: 0, + type_: atom_type.get(), + }, + } + } + + #[inline] + pub(crate) fn from_raw(raw: &lv2_sys::LV2_Atom) -> &Self { + // SAFETY: AtomHeader is repr(C) and has LV2_Atom as its only field, so transmuting between the two is safe. + unsafe { &*(raw as *const lv2_sys::LV2_Atom as *const _) } + } + + #[inline] + pub(crate) fn from_raw_mut(raw: &mut lv2_sys::LV2_Atom) -> &mut Self { + // SAFETY: AtomHeader is repr(C) and has LV2_Atom as its only field, so transmuting between the two is safe. + unsafe { &mut *(raw as *mut lv2_sys::LV2_Atom as *mut _) } + } + + #[inline] + pub(crate) unsafe fn set_size_of_body(&mut self, size: usize) { + self.inner.size = size as u32; + } + + #[inline] + pub fn size_of_body(self) -> usize { + self.inner.size as usize + } + + #[inline] + pub fn size_of_atom(self) -> usize { + self.size_of_body() + ::core::mem::size_of::() + } + + #[inline] + pub fn urid(self) -> URID { + URID::new(self.inner.type_).expect("Invalid header URID type.") + } + + #[inline] + pub(crate) fn check_urid(self, other: URID) -> Result<(), AtomReadError> { + if other == self.urid() { + Ok(()) + } else { + Err(AtomReadError::AtomUridMismatch { + expected_uri: A::uri(), + expected_urid: other.into_general(), + found_urid: self.urid(), + }) + } + } +} diff --git a/atom/src/lib.rs b/atom/src/lib.rs index a091671d..deeed485 100644 --- a/atom/src/lib.rs +++ b/atom/src/lib.rs @@ -15,6 +15,7 @@ //! use lv2_core::prelude::*; //! use lv2_units::prelude::*; //! use urid::*; +//! use lv2_atom::space::error::AtomError; //! //! #[derive(PortCollection)] //! struct MyPorts { @@ -29,96 +30,92 @@ //! } //! //! /// Something like a plugin's run method. -//! fn run(ports: &mut MyPorts, urids: &MyURIDs) { +//! fn run(ports: &mut MyPorts, urids: &MyURIDs) -> Result<(), AtomError> { //! // Get the read handle to the sequence. -//! let input_sequence = ports.input.read( -//! urids.atom.sequence, -//! urids.units.beat -//! ).unwrap(); +//! let input_sequence = ports.input +//! .read(urids.atom.sequence)? +//! .with_unit(urids.units.frame)?; //! //! // Get the write handle to the sequence. -//! let mut output_sequence = ports.output.init( -//! urids.atom.sequence, -//! TimeStampURID::Frames(urids.units.frame) -//! ).unwrap(); +//! let mut output_sequence = ports.output +//! .write(urids.atom.sequence)? +//! .with_unit(urids.units.frame)?; //! //! // Iterate through all events in the input sequence. -//! for event in input_sequence { -//! // An event contains a timestamp and an atom. -//! let (timestamp, atom) = event; +//! // An event contains a timestamp and an atom. +//! for (timestamp, atom) in input_sequence { //! // If the read atom is a 32-bit integer... -//! if let Some(integer) = atom.read(urids.atom.int, ()) { +//! if let Ok(integer) = atom.read(urids.atom.int) { //! // Multiply it by two and write it to the sequence. -//! output_sequence.init(timestamp, urids.atom.int, integer * 2).unwrap(); +//! output_sequence.new_event(timestamp, urids.atom.int)?.set(*integer * 2)?; //! } else { //! // Forward the atom to the sequence without a change. -//! output_sequence.forward(timestamp, atom).unwrap(); +//! output_sequence.forward(timestamp, atom)?; //! } //! } +//! +//! Ok(()) //! } //! ``` //! //! # Internals //! //! Internally, all atoms are powered by the structs in the [`space`](space/index.html) module. They safely abstract the reading and writing process and assure that no memory is improperly accessed or leaked and that alignments are upheld. If you simply want to use the atoms in this crate, you don't need to deal with. They are only interesting if you want to create your own atom types. + +#![warn(clippy::missing_errors_doc)] +#![warn(clippy::missing_panics_doc)] + extern crate lv2_sys as sys; extern crate lv2_units as units; -pub mod chunk; -pub mod object; -pub mod scalar; -pub mod sequence; -pub mod space; -pub mod string; -pub mod tuple; -pub mod vector; +use crate::space::error::{AtomReadError, AtomWriteError}; +pub use header::AtomHeader; +use space::*; +use urid::*; +pub mod atoms; +mod header; #[cfg(feature = "lv2-core")] pub mod port; +pub mod space; + +mod unidentified; +pub(crate) mod util; +pub use unidentified::UnidentifiedAtom; /// Prelude of `lv2_atom` for wildcard usage. pub mod prelude { - use crate::*; - - pub use crate::{Atom, AtomURIDCollection, UnidentifiedAtom}; - pub use chunk::Chunk; - pub use object::{Object, ObjectHeader, PropertyHeader}; + pub use atoms::{ + chunk::Chunk, + object::{Object, ObjectHeader, PropertyHeader}, + scalar::{AtomURID, Bool, Double, Float, Int, Long}, + sequence::Sequence, + string::{Literal, LiteralInfo, String}, + tuple::Tuple, + vector::Vector, + }; + + #[cfg(feature = "lv2-core")] pub use port::AtomPort; - pub use scalar::{AtomURID, Bool, Double, Float, Int, Long}; - pub use sequence::{Sequence, TimeStamp, TimeStampURID}; - pub use space::{FramedMutSpace, MutSpace, Space}; - pub use string::{Literal, LiteralInfo, String}; - pub use tuple::Tuple; - pub use vector::Vector; + + use crate::*; + pub use crate::{atoms::AtomURIDCollection, Atom, UnidentifiedAtom}; } -use space::*; -use urid::*; +/// A special prelude re-exporting all utilities to implement custom atom types. +pub mod atom_prelude { + pub use crate::prelude::*; -#[derive(Clone, URIDCollection)] -/// Collection with the URIDs of all `UriBound`s in this crate. -pub struct AtomURIDCollection { - pub blank: URID, - pub double: URID, - pub float: URID, - pub int: URID, - pub long: URID, - pub urid: URID, - pub bool: URID, - vector: URID>, - pub chunk: URID, - pub literal: URID, - pub object: URID, - pub property: URID, - pub string: URID, - pub tuple: URID, - pub sequence: URID, + pub use crate::space::{ + error::{AlignmentError, AtomError, AtomReadError, AtomWriteError}, + AlignedSpace, AlignedVec, AtomSpace, AtomWriter, SpaceAllocator, SpaceCursor, SpaceWriter, + Terminated, + }; + pub use crate::{Atom, AtomHandle, AtomHeader, UnidentifiedAtom}; } -impl AtomURIDCollection { - pub fn vector(&self) -> URID> { - unsafe { URID::new_unchecked(self.vector.get()) } - } +pub trait AtomHandle<'a> { + type Handle: 'a; } /// Atom type. @@ -126,87 +123,46 @@ impl AtomURIDCollection { /// This is the foundation of this crate: Types that implement `Atom` define the reading and writing functions for an atom type. However, these types will never be constructed; They are only names to be used for generic type arguments. /// /// This trait has two lifetime parameters: The first one is the lifetime of the atom in memory. In practice, this will often be `'static`, but it's good to keep it generic for testing purposes. The second parameter is the lifetime of the `MutSpace` borrowed by the `FramedMutSpace` parameter in the `write` method. Since the `WriteParameter` may contain this `FramedMutSpace`, it has to be assured that it lives long enough. Since the referenced `MutSpace` also has to borrow the atom, it may not live longer than the atom. -pub trait Atom<'a, 'b>: UriBound -where - 'a: 'b, -{ - /// The atom-specific parameter of the `read` function. - /// - /// If your atom does not need a reading parameter, you may set it to `()`. - type ReadParameter; - +pub trait Atom: UriBound { /// The return value of the `read` function. /// /// It may contain a reference to the atom and therefore may not outlive it. - type ReadHandle: 'a; - - /// The atom-specific parameter of the `write` function. - /// - /// If your atom does not need a writing parameter, you may set it to `()`. - type WriteParameter; + type ReadHandle: for<'a> AtomHandle<'a>; /// The return value of the `write` function. /// /// It may contain a reference to a `MutSpace` and therefore may not outlive it. - type WriteHandle: 'b; + type WriteHandle: for<'a> AtomHandle<'a>; - /// Read the body of the atom. + /// Reads the body of the atom. /// - /// The passed space exactly covers the body of the atom, excluding the header. You may assume that the body is actually of your atom type, since the URID of the atom was checked beforehand. + /// The passed space exactly covers the body of the atom, excluding the header. /// - /// If the atom is malformed, you may not panic and return `None` instead. - fn read(body: Space<'a>, parameter: Self::ReadParameter) -> Option; + /// # Errors + /// This method may return any error if the atom in the given space is somehow malformed, or if + /// there wasn't enough space to read it properly. + /// + /// # Safety + /// + /// The caller needs to ensure that the given [`AtomSpace`] contains a valid instance of this atom, + /// or the resulting `ReadHandle` will be completely invalid, triggering Undefined Behavior. + unsafe fn read( + body: &AtomSpace, + ) -> Result<::Handle, AtomReadError>; /// Initialize the body of the atom. /// /// In this method, the atom is prepared for the writing handle. Usually, the atom will not be - /// valid when initializied; Users have to use the write handle to make it valid. + /// valid when initialized; Users have to use the write handle to make it valid. /// /// The frame of the atom was already initialized, containing the URID. /// - /// If space is insufficient, you may not panic and return `None` instead. The written results are assumed to be malformed. - fn init( - frame: FramedMutSpace<'a, 'b>, - parameter: Self::WriteParameter, - ) -> Option; -} - -/// An atom of yet unknown type. -/// -/// This is used by reading handles that have to return a reference to an atom, but can not check it's type. This struct contains a `Space` containing the header and the body of the atom and can identify/read the atom from it. -#[derive(Clone, Copy)] -pub struct UnidentifiedAtom<'a> { - space: Space<'a>, -} - -impl<'a> UnidentifiedAtom<'a> { - /// Construct a new unidentified atom. - /// - /// The space actually has to contain an atom. If it doesn't, crazy (but not undefined) things can happen. - pub fn new(space: Space<'a>) -> Self { - Self { space } - } - - /// Try to read the atom. + /// # Errors /// - /// To identify the atom, it's URID and an atom-specific parameter is needed. If the atom was identified, a reading handle is returned. - pub fn read<'b, A: Atom<'a, 'b>>( - self, - urid: URID, - parameter: A::ReadParameter, - ) -> Option { - self.space - .split_atom_body(urid) - .map(|(body, _)| body) - .and_then(|body| A::read(body, parameter)) - } - - /// Retrieve the type URID of the atom. + /// This method may return an error if the buffer is out of space, or if any invalid state is + /// observed. In those cases, the written data may be incomplete and should be discarded. /// - /// This can be used to identify atoms without actually reading them. - pub fn type_urid(self) -> Option { - self.space - .split_type::() - .and_then(|(header, _)| URID::new(header.type_)) - } + fn write( + writer: AtomWriter, + ) -> Result<::Handle, AtomWriteError>; } diff --git a/atom/src/object.rs b/atom/src/object.rs deleted file mode 100644 index 1a94d0e1..00000000 --- a/atom/src/object.rs +++ /dev/null @@ -1,396 +0,0 @@ -//! An atom containing multiple key-value pairs. -//! -//! 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. -//! -//! # Example -//! ``` -//! use lv2_core::prelude::*; -//! use lv2_atom::prelude::*; -//! use urid::*; -//! -//! #[uri("urn:object-class")] -//! struct ObjectClass; -//! -//! #[uri("urn:property-a")] -//! struct PropertyA; -//! -//! #[derive(PortCollection)] -//! struct MyPorts { -//! input: InputPort, -//! output: OutputPort, -//! } -//! -//! #[derive(URIDCollection)] -//! struct MyURIDs { -//! atom: AtomURIDCollection, -//! object_class: URID, -//! property_a: URID, -//! } -//! -//! fn run(ports: &mut MyPorts, urids: &MyURIDs) { -//! // Create the reading handle. -//! // We don't need the header now. -//! let (_header, object_reader) = ports.input.read(urids.atom.object, ()).unwrap(); -//! -//! /// Iterate through all properties of the object. -//! for (property_header, atom) in object_reader { -//! // If the property is an integer... -//! if let Some(integer) = atom.read(urids.atom.int, ()) { -//! // Print it! -//! println!( -//! "Property No. {} has integer value {}", -//! property_header.key.get(), -//! integer -//! ); -//! } else { -//! // Print that is not an integer. -//! println!( -//! "Property No. {} is not an integer", -//! property_header.key.get() -//! ); -//! } -//! } -//! -//! // Initialize the object. -//! let mut object_writer = ports.output.init( -//! urids.atom.object, -//! ObjectHeader { -//! id: None, -//! otype: urids.object_class.into_general(), -//! } -//! ).unwrap(); -//! -//! // Write a property to the object. -//! object_writer.init(urids.property_a, urids.atom.int, 42).unwrap(); -//! } -//! ``` -//! -//! # Specification -//! [http://lv2plug.in/ns/ext/atom/atom.html#Object](http://lv2plug.in/ns/ext/atom/atom.html#Object). -use crate::space::*; -use crate::*; -use std::convert::TryFrom; -use std::iter::Iterator; -use urid::UriBound; -use urid::URID; - -/// An atom containing multiple key-value pairs. -/// -/// [See also the module documentation.](index.html) -pub struct Object; - -unsafe impl UriBound for Object { - const URI: &'static [u8] = sys::LV2_ATOM__Object; -} - -/// Information about an object atom. -pub struct ObjectHeader { - /// The id of the object to distinguish different objects of the same type. - /// - /// If you don't need it, you should set it to `None`. - pub id: Option, - /// The type of the object (same as `rdf:type`). - pub otype: URID, -} - -impl<'a, 'b> Atom<'a, 'b> for Object -where - 'a: 'b, -{ - type ReadParameter = (); - type ReadHandle = (ObjectHeader, ObjectReader<'a>); - type WriteParameter = ObjectHeader; - type WriteHandle = ObjectWriter<'a, 'b>; - - fn read(body: Space<'a>, _: ()) -> Option<(ObjectHeader, ObjectReader<'a>)> { - let (header, body) = body.split_type::()?; - let header = ObjectHeader { - id: URID::try_from(header.id).ok(), - otype: URID::try_from(header.otype).ok()?, - }; - - let reader = ObjectReader { space: body }; - - Some((header, reader)) - } - - fn init( - mut frame: FramedMutSpace<'a, 'b>, - header: ObjectHeader, - ) -> Option> { - { - let frame = &mut frame as &mut dyn MutSpace; - frame.write( - &sys::LV2_Atom_Object_Body { - id: header.id.map(|urid| urid.get()).unwrap_or(0), - otype: header.otype.get(), - }, - true, - ); - } - Some(ObjectWriter { frame }) - } -} - -/// Alias of `Object`, used by older hosts. -/// -/// 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. -/// -/// 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`. -pub struct Blank; - -unsafe impl UriBound for Blank { - const URI: &'static [u8] = sys::LV2_ATOM__Blank; -} - -impl<'a, 'b> Atom<'a, 'b> for Blank -where - 'a: 'b, -{ - type ReadParameter = >::ReadParameter; - type ReadHandle = >::ReadHandle; - type WriteParameter = >::WriteParameter; - type WriteHandle = >::WriteHandle; - - #[allow(clippy::unit_arg)] - fn read(body: Space<'a>, parameter: Self::ReadParameter) -> Option { - Object::read(body, parameter) - } - - fn init( - frame: FramedMutSpace<'a, 'b>, - parameter: Self::WriteParameter, - ) -> Option { - Object::init(frame, parameter) - } -} - -/// An iterator over all properties in an object. -/// -/// 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. -pub struct ObjectReader<'a> { - space: Space<'a>, -} - -impl<'a> Iterator for ObjectReader<'a> { - type Item = (PropertyHeader, UnidentifiedAtom<'a>); - - fn next(&mut self) -> Option<(PropertyHeader, UnidentifiedAtom<'a>)> { - let (header, value, space) = Property::read_body(self.space)?; - self.space = space; - Some((header, UnidentifiedAtom::new(value))) - } -} - -/// Writing handle for object properties. -/// -/// This handle is a safeguard to assure that a object is always a series of properties. -pub struct ObjectWriter<'a, 'b> { - frame: FramedMutSpace<'a, 'b>, -} - -impl<'a, 'b> ObjectWriter<'a, 'b> { - /// Initialize a new property with a context. - /// - /// This method does the same as [`init`](#method.init), but also sets the context URID. - pub fn init_with_context<'c, K: ?Sized, T: ?Sized, A: Atom<'a, 'c>>( - &'c mut self, - key: URID, - context: URID, - child_urid: URID, - parameter: A::WriteParameter, - ) -> Option { - Property::write_header(&mut self.frame, key.into_general(), Some(context))?; - (&mut self.frame as &mut dyn MutSpace).init(child_urid, parameter) - } - - /// Initialize a new property. - /// - /// This method writes out the header of a property and returns a reference to the space, so the property values can be written. - /// - /// 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). - pub fn init<'c, K: ?Sized, A: Atom<'a, 'c>>( - &'c mut self, - key: URID, - child_urid: URID, - parameter: A::WriteParameter, - ) -> Option { - Property::write_header::(&mut self.frame, key, None)?; - (&mut self.frame as &mut dyn MutSpace).init(child_urid, parameter) - } -} - -/// An atom containing a key-value pair. -/// -/// 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). -/// -/// 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. -pub struct Property; - -unsafe impl UriBound for Property { - const URI: &'static [u8] = sys::LV2_ATOM__Property; -} - -/// Information about a property atom. -#[derive(Clone, Copy)] -pub struct PropertyHeader { - /// The key of the property. - pub key: URID, - /// URID of the context (generally `None`). - pub context: Option, -} - -impl Property { - /// Read the body of a property atom from a space. - /// - /// 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. - fn read_body(space: Space) -> Option<(PropertyHeader, Space, Space)> { - #[repr(C)] - #[derive(Clone, Copy)] - /// A custom version of the property body that does not include the value atom header. - /// - /// We will retrieve it separately. - struct StrippedPropertyBody { - key: u32, - context: u32, - } - - let (header, space) = space.split_type::()?; - - let header = PropertyHeader { - key: URID::try_from(header.key).ok()?, - context: URID::try_from(header.context).ok(), - }; - - let (atom, space) = space.split_atom()?; - Some((header, atom, space)) - } - - /// Write out the header of a property atom. - /// - /// This method simply writes out the content of the header to the space and returns `Some(())` if it's successful. - fn write_header( - space: &mut dyn MutSpace, - key: URID, - context: Option>, - ) -> Option<()> { - space.write(&key.get(), true)?; - space.write(&context.map(|urid| urid.get()).unwrap_or(0), false)?; - Some(()) - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - use crate::space::*; - use std::mem::size_of; - use urid::*; - - #[test] - fn test_object() { - let map = HashURIDMapper::new(); - let urids = AtomURIDCollection::from_map(&map).unwrap(); - - let object_type = map - .map_uri(Uri::from_bytes_with_nul(b"urn:my-type\0").unwrap()) - .unwrap(); - - let first_key = map - .map_uri(Uri::from_bytes_with_nul(b"urn:value-a\0").unwrap()) - .unwrap(); - let first_value: i32 = 17; - - let second_key = map - .map_uri(Uri::from_bytes_with_nul(b"urn:value-b\0").unwrap()) - .unwrap(); - let second_value: f32 = 42.0; - - let mut raw_space: Box<[u8]> = Box::new([0; 256]); - - // writing - { - let mut space = RootMutSpace::new(raw_space.as_mut()); - let frame = FramedMutSpace::new(&mut space as &mut dyn MutSpace, urids.object).unwrap(); - let mut writer = Object::init( - frame, - ObjectHeader { - id: None, - otype: object_type, - }, - ) - .unwrap(); - { - writer.init(first_key, urids.int, first_value).unwrap(); - } - { - writer.init(second_key, urids.float, second_value).unwrap(); - } - } - - // verifying - { - // Header - let (atom, space) = raw_space.split_at(size_of::()); - let atom = unsafe { &*(atom.as_ptr() as *const sys::LV2_Atom) }; - assert_eq!(atom.type_, urids.object); - assert_eq!( - atom.size as usize, - size_of::() - + size_of::() - + 2 * size_of::() - + size_of::() - + size_of::() - ); - - // Object. - let (object, space) = space.split_at(size_of::()); - let object = unsafe { &*(object.as_ptr() as *const sys::LV2_Atom_Object_Body) }; - assert_eq!(object.id, 0); - assert_eq!(object.otype, object_type); - - // First property. - let (property, space) = space.split_at(size_of::()); - let property = unsafe { &*(property.as_ptr() as *const sys::LV2_Atom_Property_Body) }; - assert_eq!(property.key, first_key); - assert_eq!(property.context, 0); - assert_eq!(property.value.type_, urids.int); - assert_eq!(property.value.size as usize, size_of::()); - - let (value, space) = space.split_at(size_of::()); - let value = unsafe { *(value.as_ptr() as *const i32) }; - assert_eq!(value, first_value); - let (_, space) = space.split_at(size_of::()); - - // Second property. - let (property, space) = space.split_at(size_of::()); - let property = unsafe { &*(property.as_ptr() as *const sys::LV2_Atom_Property_Body) }; - assert_eq!(property.key, second_key); - assert_eq!(property.context, 0); - assert_eq!(property.value.type_, urids.float); - assert_eq!(property.value.size as usize, size_of::()); - - let (value, _) = space.split_at(size_of::()); - let value = unsafe { *(value.as_ptr() as *const f32) }; - assert_eq!(value, second_value); - } - - // reading - { - let space = Space::from_slice(raw_space.as_ref()); - let (body, _) = space.split_atom_body(urids.object).unwrap(); - - let (header, iter) = Object::read(body, ()).unwrap(); - assert_eq!(header.otype, object_type); - assert_eq!(header.id, None); - - let properties: Vec<(PropertyHeader, UnidentifiedAtom)> = iter.collect(); - let (header, atom) = properties[0]; - assert_eq!(header.key, first_key); - assert_eq!(atom.read::(urids.int, ()).unwrap(), first_value); - let (header, atom) = properties[1]; - assert_eq!(header.key, second_key); - assert_eq!(atom.read::(urids.float, ()).unwrap(), second_value); - } - } -} diff --git a/atom/src/port.rs b/atom/src/port.rs index d8f3dd67..155c4df4 100644 --- a/atom/src/port.rs +++ b/atom/src/port.rs @@ -18,12 +18,15 @@ //! /// Something like a plugin's run method. //! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { //! // Read an integer from the port and print it. -//! println!("My input is: {}", ports.input.read(urids.int, ()).unwrap()); +//! println!("My input is: {}", ports.input.read(urids.int).unwrap()); //! // Write the integer `42` to the port. -//! ports.output.init(urids.int, 42).unwrap(); +//! ports.output.write(urids.int).unwrap(); //! } //! ``` +use crate::header::AtomHeader; +use crate::space::error::{AtomReadError, AtomWriteError}; use crate::space::*; +use crate::{AtomHandle, UnidentifiedAtom}; use lv2_core::port::PortType; use std::ffi::c_void; use std::ptr::NonNull; @@ -33,26 +36,29 @@ use urid::URID; /// /// If you add an [`AtomPort`](struct.AtomPort.html) to your ports struct, you will receive an instance of this struct to read atoms. pub struct PortReader<'a> { - space: Space<'a>, + atom: &'a UnidentifiedAtom, } impl<'a> PortReader<'a> { /// Create a new port reader. - fn new(space: Space<'a>) -> Self { - Self { space } + fn new(atom: &'a UnidentifiedAtom) -> Self { + Self { atom } } /// Read an atom. /// /// In order to identify the atom, the reader needs to know it's URID. Also, some atoms require a parameter. However, you can simply pass `()` in most cases. /// - /// This method returns `None` if the atom is malformed or simply isn't of the specified type. - pub fn read<'b, A: crate::Atom<'a, 'b>>( - &'b self, + /// # Errors + /// + /// This method can return any read error if the given URID doesn't match the contained atom, + /// or if any other read error occurred. + #[inline] + pub fn read( + &self, urid: URID, - parameter: A::ReadParameter, - ) -> Option { - A::read(self.space.split_atom_body(urid)?.0, parameter) + ) -> Result<::Handle, AtomReadError> { + self.atom.read(urid) } } @@ -60,16 +66,14 @@ impl<'a> PortReader<'a> { /// /// If you add an [`AtomPort`](struct.AtomPort.html) to your ports struct, you will receive an instance of this struct to write atoms. pub struct PortWriter<'a> { - space: RootMutSpace<'a>, - has_been_written: bool, + space: SpaceCursor<'a>, } impl<'a> PortWriter<'a> { /// Create a new port writer. - fn new(space: RootMutSpace<'a>) -> Self { + fn new(space: &'a mut AtomSpace) -> Self { Self { - space, - has_been_written: false, + space: SpaceCursor::new(space.as_bytes_mut()), } } @@ -79,18 +83,18 @@ impl<'a> PortWriter<'a> { /// /// Please note that you can call this method once only, because any atoms written behind the first one will not be identified. /// - /// This method returns `None` if the space of the port isn't big enough or if the method was called multiple times. - pub fn init<'b, A: crate::Atom<'a, 'b>>( - &'b mut self, + /// # Errors + /// + /// This method can return an error if the buffer isn't big enough to initialize the given atom's header. + pub fn write<'b, 'write, A: crate::Atom>( + &'b mut self, // SAFETY: 'write should be :'a , but for now we have to return 'static arbitrary lifetimes. urid: URID, - parameter: A::WriteParameter, - ) -> Option { - if !self.has_been_written { - self.has_been_written = true; - (&mut self.space as &mut dyn MutSpace).init(urid, parameter) - } else { - None - } + ) -> Result<>::Handle, AtomWriteError> { + // SAFETY: Nope. That's super unsound, but we need it because ports are 'static right now. + let space: &'write mut SpaceCursor<'write> = unsafe { + ::core::mem::transmute::<_, &'write mut SpaceCursor<'write>>(&mut self.space) + }; + space.write_atom(urid) } } @@ -107,14 +111,14 @@ impl PortType for AtomPort { #[inline] unsafe fn input_from_raw(pointer: NonNull, _sample_count: u32) -> PortReader<'static> { - let space = Space::from_atom(pointer.cast().as_ref()); - PortReader::new(space) + let header = AtomHeader::from_raw(pointer.cast().as_ref()); + PortReader::new(UnidentifiedAtom::from_header(header)) } #[inline] unsafe fn output_from_raw(pointer: NonNull, _sample_count: u32) -> PortWriter<'static> { - let space = RootMutSpace::from_atom(pointer.cast().as_mut()); - PortWriter::new(space) + let header = AtomHeader::from_raw_mut(pointer.cast().as_mut()); + PortWriter::new(UnidentifiedAtom::from_header_mut(header).body_mut()) } } @@ -122,6 +126,7 @@ impl PortType for AtomPort { mod tests { use crate::prelude::*; use crate::space::*; + use crate::AtomHeader; use lv2_core::prelude::*; use std::mem::size_of; use std::ptr::NonNull; @@ -132,31 +137,32 @@ mod tests { let map = HashURIDMapper::new(); let urids = AtomURIDCollection::from_map(&map).unwrap(); - let mut raw_space: Box<[u8]> = Box::new([0; 256]); + let mut raw_space = AlignedVec::::new_with_capacity(64); + let raw_space = raw_space.as_space_mut(); // writing a chunk to indicate the size of the space. { - let mut space = RootMutSpace::new(raw_space.as_mut()); - let mut writer = (&mut space as &mut dyn MutSpace) - .init(urids.chunk, ()) - .unwrap(); - writer - .allocate(256 - size_of::(), false) - .unwrap(); + let mut space = SpaceCursor::new(raw_space.as_bytes_mut()); + let mut writer = space.write_atom(urids.chunk).unwrap(); + writer.allocate(256 - size_of::()).unwrap(); } // Getting a writer with the port. { - let mut writer = - unsafe { AtomPort::output_from_raw(NonNull::from(raw_space.as_mut()).cast(), 0) }; - writer.init::(urids.int, 42).unwrap(); + let mut writer = unsafe { + AtomPort::output_from_raw(NonNull::from(raw_space.as_bytes_mut()).cast(), 0) + }; + writer.write::(urids.int).unwrap().set(42).unwrap(); } // Reading { - let reader = - unsafe { AtomPort::input_from_raw(NonNull::from(raw_space.as_mut()).cast(), 0) }; - assert_eq!(reader.read::(urids.int, ()).unwrap(), 42); + let chunk = unsafe { raw_space.read().next_atom() } + .unwrap() + .read(urids.chunk) + .unwrap(); + let reader = unsafe { AtomPort::input_from_raw(NonNull::from(chunk).cast(), 0) }; + assert_eq!(*reader.read::(urids.int).unwrap(), 42); } } } diff --git a/atom/src/sequence.rs b/atom/src/sequence.rs deleted file mode 100644 index 2acb2e17..00000000 --- a/atom/src/sequence.rs +++ /dev/null @@ -1,364 +0,0 @@ -//! An atom containing a sequence of time-stamped events. -//! -//! These events are atoms again. Atoms passed in a sequence can be handled with frame-perfect timing and therefore is the prefered way to transmit events like MIDI messages. However, MIDI messages are implemented in separate crate. -//! -//! # Example -//! -//! ``` -//! use lv2_core::prelude::*; -//! use lv2_units::prelude::*; -//! use lv2_atom::prelude::*; -//! use lv2_atom::sequence::*; -//! use urid::*; -//! -//! #[derive(PortCollection)] -//! struct MyPorts { -//! input: InputPort, -//! output: OutputPort, -//! } -//! -//! #[derive(URIDCollection)] -//! struct MyURIDs { -//! atom: AtomURIDCollection, -//! units: UnitURIDCollection, -//! } -//! -//! /// Something like a plugin's run method. -//! fn run(ports: &mut MyPorts, urids: &MyURIDs) { -//! // Get the read handle to the sequence. -//! // The reading method needs the URID of the BPM unit to tell if the time stamp -//! // is measured in beats or in frames. If the atom doesn't says that it's measured -//! // in beats, it is assumed that it is measured in frames. -//! let input_sequence: SequenceIterator = ports.input.read( -//! urids.atom.sequence, -//! urids.units.beat -//! ).unwrap(); -//! -//! // Get the write handle to the sequence. -//! // You have to provide the unit of the time stamps. -//! let mut output_sequence: SequenceWriter = ports.output.init( -//! urids.atom.sequence, -//! TimeStampURID::Frames(urids.units.frame) -//! ).unwrap(); -//! -//! // Iterate through all events in the input sequence. -//! // -//! // The specifications don't require the time stamps to be monotonic, your algorithms should -//! // be able to handle older events written after younger events. -//! // -//! // The sequence writer, however, assures that the written time stamps are monotonic. -//! for event in input_sequence { -//! // An event contains a timestamp and an atom. -//! let (timestamp, atom): (TimeStamp, UnidentifiedAtom) = event; -//! // If the read atom is a 32-bit integer... -//! if let Some(integer) = atom.read(urids.atom.int, ()) { -//! // Multiply it by two and write it to the sequence. -//! output_sequence.init(timestamp, urids.atom.int, integer * 2).unwrap(); -//! } else { -//! // Forward the atom to the sequence without a change. -//! output_sequence.forward(timestamp, atom).unwrap(); -//! } -//! } -//! } -//! ``` -//! -//! # Specification -//! -//! [http://lv2plug.in/ns/ext/atom/atom.html#Sequence](http://lv2plug.in/ns/ext/atom/atom.html#Sequence) -use crate::space::*; -use crate::*; -use sys::LV2_Atom_Event__bindgen_ty_1 as RawTimeStamp; -use units::prelude::*; -use urid::*; - -/// An atom containing a sequence of time-stamped events. -/// -/// [See also the module documentation.](index.html) -pub struct Sequence; - -unsafe impl UriBound for Sequence { - const URI: &'static [u8] = sys::LV2_ATOM__Sequence; -} - -impl<'a, 'b> Atom<'a, 'b> for Sequence -where - 'a: 'b, -{ - type ReadParameter = URID; - type ReadHandle = SequenceIterator<'a>; - type WriteParameter = TimeStampURID; - type WriteHandle = SequenceWriter<'a, 'b>; - - fn read(body: Space, bpm_urid: URID) -> Option { - let (header, body) = body.split_type::()?; - let unit = if header.unit == bpm_urid { - TimeStampUnit::BeatsPerMinute - } else { - TimeStampUnit::Frames - }; - Some(SequenceIterator { space: body, unit }) - } - - fn init( - mut frame: FramedMutSpace<'a, 'b>, - unit: TimeStampURID, - ) -> Option> { - { - let frame = &mut frame as &mut dyn MutSpace; - let header = sys::LV2_Atom_Sequence_Body { - unit: match unit { - TimeStampURID::BeatsPerMinute(urid) => urid.get(), - TimeStampURID::Frames(urid) => urid.get(), - }, - pad: 0, - }; - frame.write(&header, true)?; - } - Some(SequenceWriter { - frame, - unit: unit.into(), - last_stamp: None, - }) - } -} - -/// The measuring units of time stamps. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum TimeStampUnit { - Frames, - BeatsPerMinute, -} - -/// An event time stamp. -#[derive(Clone, Copy, Debug)] -pub enum TimeStamp { - Frames(i64), - BeatsPerMinute(f64), -} - -/// The measuring units of time stamps, with their URIDs. -#[derive(Clone, Copy)] -pub enum TimeStampURID { - Frames(URID), - BeatsPerMinute(URID), -} - -impl From for TimeStampUnit { - fn from(urid: TimeStampURID) -> TimeStampUnit { - match urid { - TimeStampURID::Frames(_) => TimeStampUnit::Frames, - TimeStampURID::BeatsPerMinute(_) => TimeStampUnit::BeatsPerMinute, - } - } -} - -impl TimeStamp { - pub fn as_frames(self) -> Option { - match self { - Self::Frames(frame) => Some(frame), - _ => None, - } - } - - pub fn as_bpm(self) -> Option { - match self { - Self::BeatsPerMinute(bpm) => Some(bpm), - _ => None, - } - } -} - -/// An iterator over all events in a sequence. -pub struct SequenceIterator<'a> { - space: Space<'a>, - unit: TimeStampUnit, -} - -impl<'a> SequenceIterator<'a> { - pub fn unit(&self) -> TimeStampUnit { - self.unit - } -} - -impl<'a> Iterator for SequenceIterator<'a> { - type Item = (TimeStamp, UnidentifiedAtom<'a>); - - fn next(&mut self) -> Option<(TimeStamp, UnidentifiedAtom<'a>)> { - let (raw_stamp, space) = self.space.split_type::()?; - let stamp = match self.unit { - TimeStampUnit::Frames => unsafe { TimeStamp::Frames(raw_stamp.frames) }, - TimeStampUnit::BeatsPerMinute => unsafe { TimeStamp::BeatsPerMinute(raw_stamp.beats) }, - }; - let (atom, space) = space.split_atom()?; - self.space = space; - Some((stamp, UnidentifiedAtom::new(atom))) - } -} - -/// The writing handle for sequences. -pub struct SequenceWriter<'a, 'b> { - frame: FramedMutSpace<'a, 'b>, - unit: TimeStampUnit, - last_stamp: Option, -} - -impl<'a, 'b> SequenceWriter<'a, 'b> { - /// Write out the time stamp and update `last_stamp`. - /// - /// This method returns `Ǹone` if: - /// * The time stamp is not measured in our unit. - /// * The last time stamp is younger than the time stamp. - /// * Space is insufficient. - fn write_time_stamp(&mut self, stamp: TimeStamp) -> Option<()> { - let raw_stamp = match self.unit { - TimeStampUnit::Frames => { - let frames = stamp.as_frames()?; - if let Some(last_stamp) = self.last_stamp { - if last_stamp.as_frames().unwrap() > frames { - return None; - } - } - RawTimeStamp { frames } - } - TimeStampUnit::BeatsPerMinute => { - let beats = stamp.as_bpm()?; - if let Some(last_stamp) = self.last_stamp { - if last_stamp.as_bpm().unwrap() > beats { - return None; - } - } - RawTimeStamp { beats } - } - }; - self.last_stamp = Some(stamp); - (&mut self.frame as &mut dyn MutSpace) - .write(&raw_stamp, true) - .map(|_| ()) - } - - /// Initialize an event. - /// - /// The time stamp has to be measured in the unit of the sequence. If the time stamp is measured in the wrong unit, is younger than the last written time stamp or space is insufficient, this method returns `None`. - pub fn init<'c, A: Atom<'a, 'c>>( - &'c mut self, - stamp: TimeStamp, - urid: URID, - parameter: A::WriteParameter, - ) -> Option { - self.write_time_stamp(stamp)?; - (&mut self.frame as &mut dyn MutSpace).init(urid, parameter) - } - - /// Forward an unidentified atom to the sequence. - /// - /// If your cannot identify the type of the atom but have to write it, you can simply forward it. - /// - /// The time stamp has to be measured in the unit of the sequence. If the time stamp is measured in the wrong unit, is younger than the last written time stamp or space is insufficient, this method returns `None`. - pub fn forward(&mut self, stamp: TimeStamp, atom: UnidentifiedAtom) -> Option<()> { - let data = atom.space.data()?; - self.write_time_stamp(stamp)?; - self.frame.write_raw(data, true).map(|_| ()) - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - use crate::sequence::*; - use std::mem::size_of; - use sys::LV2_Atom_Event__bindgen_ty_1 as RawTimeStamp; - - #[derive(URIDCollection)] - struct TestURIDCollection { - atom: AtomURIDCollection, - units: UnitURIDCollection, - } - - #[test] - fn test_sequence() { - let map = HashURIDMapper::new(); - let urids = TestURIDCollection::from_map(&map).unwrap(); - - let mut raw_space: Box<[u8]> = Box::new([0; 256]); - - // writing - { - let mut space = RootMutSpace::new(raw_space.as_mut()); - let mut writer = (&mut space as &mut dyn MutSpace) - .init( - urids.atom.sequence, - TimeStampURID::Frames(urids.units.frame), - ) - .unwrap(); - writer - .init::(TimeStamp::Frames(0), urids.atom.int, 42) - .unwrap(); - writer - .init::(TimeStamp::Frames(1), urids.atom.long, 17) - .unwrap(); - } - - // verifying - { - let (sequence, space) = raw_space.split_at(size_of::()); - let sequence = unsafe { &*(sequence.as_ptr() as *const sys::LV2_Atom_Sequence) }; - assert_eq!(sequence.atom.type_, urids.atom.sequence); - assert_eq!( - sequence.atom.size as usize, - size_of::() - + size_of::() - + size_of::() - + 4 - + size_of::() - + size_of::() - ); - assert_eq!(sequence.body.unit, urids.units.frame); - - let (stamp, space) = space.split_at(size_of::()); - let stamp = unsafe { *(stamp.as_ptr() as *const RawTimeStamp) }; - assert_eq!(unsafe { stamp.frames }, 0); - - let (int, space) = space.split_at(size_of::()); - let int = unsafe { &*(int.as_ptr() as *const sys::LV2_Atom_Int) }; - assert_eq!(int.atom.type_, urids.atom.int); - assert_eq!(int.atom.size as usize, size_of::()); - assert_eq!(int.body, 42); - let (_, space) = space.split_at(4); - - let (stamp, space) = space.split_at(size_of::()); - let stamp = unsafe { *(stamp.as_ptr() as *const RawTimeStamp) }; - assert_eq!(unsafe { stamp.frames }, 1); - - let (int, _) = space.split_at(size_of::()); - let int = unsafe { &*(int.as_ptr() as *const sys::LV2_Atom_Long) }; - assert_eq!(int.atom.type_, urids.atom.long); - assert_eq!(int.atom.size as usize, size_of::()); - assert_eq!(int.body, 17); - } - - // reading - { - let space = Space::from_slice(raw_space.as_ref()); - let (body, _) = space.split_atom_body(urids.atom.sequence).unwrap(); - let mut reader = Sequence::read(body, urids.units.beat).unwrap(); - - assert_eq!(reader.unit(), TimeStampUnit::Frames); - - let (stamp, atom) = reader.next().unwrap(); - match stamp { - TimeStamp::Frames(frames) => assert_eq!(frames, 0), - _ => panic!("Invalid time stamp!"), - } - assert_eq!(atom.read::(urids.atom.int, ()).unwrap(), 42); - - let (stamp, atom) = reader.next().unwrap(); - match stamp { - TimeStamp::Frames(frames) => assert_eq!(frames, 1), - _ => panic!("Invalid time stamp!"), - } - assert_eq!(atom.read::(urids.atom.long, ()).unwrap(), 17); - - assert!(reader.next().is_none()); - } - } -} diff --git a/atom/src/space.rs b/atom/src/space.rs index b804582b..d6570bcc 100644 --- a/atom/src/space.rs +++ b/atom/src/space.rs @@ -1,623 +1,19 @@ -//! Smart pointers with safe atom reading and writing methods. -//! -//! # Safety -//! -//! The only unsafe things that happen in this module is when either space is created from a reference to a `sys::LV2_Atom` and when space is re-interpreted as typed data. -//! -//! In the first case, we have to trust that the space behind the atom header is accessible since we have no way to check whether it is or not. Therefore, we have to assume that it is sound. -//! -//! The second case is sound since a) the data is contained in a slice and therefore is accessible, b) generic type parameter bounds assure that the type is plain-old-data and c) 64-bit padding is assured. -use crate::Atom; -use std::cell::Cell; -use std::marker::Unpin; -use std::mem::{size_of, size_of_val}; -use urid::URID; - -/// Specialized smart pointer to retrieve struct instances from a slice of memory. -/// -/// The accessor methods of this struct all behave in a similar way: If the internal slice is big enough, they create a reference to the start of the slice with the desired type and create a new space object that contains the space after the references instance. -#[derive(Clone, Copy)] -pub struct Space<'a> { - data: Option<&'a [u8]>, -} - -impl<'a> Space<'a> { - /// Create a new space from an atom pointer. - /// - /// The method creates a space that contains the atom as well as it's body. - /// - /// # Safety - /// - /// Since the body is not included in the atom reference, this method has to assume that it is valid memory and therefore is unsafe but sound. - #[allow(clippy::trivially_copy_pass_by_ref)] - pub unsafe fn from_atom(atom: &sys::LV2_Atom) -> Self { - let size = atom.size as usize; - let data = std::slice::from_raw_parts( - atom as *const sys::LV2_Atom as *const u8, - size + size_of::(), - ); - Self::from_slice(data) - } - - /// Create a new space from a slice. - /// - /// Since everything regarding atoms is 64-bit-aligned, this method panics if the data slice is not 64-bit-aligned. - pub fn from_slice(data: &'a [u8]) -> Self { - Space { data: Some(data) } - } - - /// Try to retrieve a slice of bytes. - /// - /// This method basically splits off the lower part of the internal bytes slice and creates a new atom space pointer of the upper part. Since atoms have to be 64-bit-aligned, there might be a padding space that's neither in the lower nor in the upper part. - pub fn split_raw(self, size: usize) -> Option<(&'a [u8], Self)> { - let data = self.data?; - - if size > data.len() { - return None; - } - let (lower_space, upper_space) = data.split_at(size); - - // Apply padding. - let padding = if size % 8 == 0 { 0 } else { 8 - size % 8 }; - let upper_space = if padding <= upper_space.len() { - let upper_space = upper_space.split_at(padding).1; - Some(upper_space) - } else { - None - }; - let upper_space = Self { data: upper_space }; - - Some((lower_space, upper_space)) - } - - /// Try to retrieve space. - /// - /// This method calls [`split_raw`](#method.split_raw) and wraps the returned slice in an atom space. The second space is the space after the first one. - pub fn split_space(self, size: usize) -> Option<(Self, Self)> { - self.split_raw(size) - .map(|(data, rhs)| (Self::from_slice(data), rhs)) - } - - /// Try to retrieve a reference to a sized type. - /// - /// This method retrieves a slice of memory using the [`split_raw`](#method.split_raw) method and interprets it as an instance of `T`. Since there is no way to check that the memory is actually a valid instance of `T`, this method is unsafe. The second return value is the space after the instance of `T`. - pub fn split_type(self) -> Option<(&'a T, Self)> - where - T: Unpin + Copy + Send + Sync + Sized + 'static, - { - self.split_raw(size_of::()) - .map(|(data, rhs)| (unsafe { &*(data.as_ptr() as *const T) }, rhs)) - } - - /// Try to retrieve the space occupied by an atom. - /// - /// This method assumes that the space contains an atom and retrieves the space occupied by the atom, including the atom header. The second return value is the rest of the space behind the atom. - /// - /// The difference to [`split_atom_body`](#method.split_atom_body) is that the returned space contains the header of the atom and that the type of the atom is not checked. - pub fn split_atom(self) -> Option<(Self, Self)> { - let (header, _) = self.split_type::()?; - self.split_space(size_of::() + header.size as usize) - } - - /// Try to retrieve the body of the atom. - /// - /// This method retrieves the header of the atom. If the type URID in the header matches the given URID, it returns the body of the atom. If not, it returns `None`. The first space is the body of the atom, the second one is the space behind it. - /// - /// The difference to [`split_atom`](#method.split_atom) is that the returned space does not contain the header of the atom and that the type of the atom is checked. - pub fn split_atom_body(self, urid: URID) -> Option<(Self, Self)> { - let (header, space) = self.split_type::()?; - if header.type_ != urid.get() { - return None; - } - space.split_space(header.size as usize) - } - - /// Create a space from a reference. - pub fn from_reference(instance: &'a T) -> Self { - let data = unsafe { - std::slice::from_raw_parts(instance as *const T as *const u8, size_of_val(instance)) - }; - assert_eq!(data.as_ptr() as usize % 8, 0); - Space { data: Some(data) } - } - - /// Concatenate two spaces. - /// - /// There are situations where a space is split too often and you might want to reunite these two adjacent spaces. This method checks if the given spaces are adjacent, which means that the left space has to end exactly where the right one begins. In this case, the concatenated space is returned. If this is not the case, this method returns `None`. - pub fn concat(lhs: Self, rhs: Self) -> Option { - let lhs_data = match lhs.data { - Some(data) => data, - None => return Some(rhs), - }; - let rhs_data = match rhs.data { - Some(data) => data, - None => return Some(lhs), - }; - if unsafe { lhs_data.as_ptr().add(lhs_data.len()) } == rhs_data.as_ptr() { - Some(Self::from_slice(unsafe { - std::slice::from_raw_parts(lhs_data.as_ptr(), lhs_data.len() + rhs_data.len()) - })) - } else { - None - } - } - - /// Return the internal slice of the space. - pub fn data(&self) -> Option<&'a [u8]> { - self.data - } - - /// Return a mutable reference to the internal slice of the space. - pub fn mut_data(&mut self) -> &mut Option<&'a [u8]> { - &mut self.data - } -} - -/// A smart pointer that writes atom data to an internal slice. -/// -/// The methods provided by this trait are fairly minimalistic. More convenient writing methods are implemented for `dyn MutSpace`. -pub trait MutSpace<'a> { - /// Try to allocate memory on the internal data slice. - /// - /// If `apply_padding` is `true`, the method will assure that the allocated memory is 64-bit-aligned. The first return value is the number of padding bytes that has been used and the second return value is a mutable slice referencing the allocated data. - /// - /// After the memory has been allocated, the `MutSpace` can not allocate it again. The next allocated slice is directly behind it. - fn allocate(&mut self, size: usize, apply_padding: bool) -> Option<(usize, &'a mut [u8])>; - - /// Try to write data to the internal data slice. - /// - /// The method allocates a slice with the [`allocate`](#tymethod.allocate) method and copies the data to the slice. - fn write_raw(&mut self, data: &[u8], apply_padding: bool) -> Option<&'a mut [u8]> { - self.allocate(data.len(), apply_padding).map(|(_, space)| { - space.copy_from_slice(data); - space - }) - } -} - -/// A `MutSpace` that directly manages it's own internal data slice. -pub struct RootMutSpace<'a> { - space: Cell>, - allocated_bytes: usize, -} - -impl<'a> RootMutSpace<'a> { - /// Create new space from an atom. - /// - /// The method creates a space that contains the atom as well as it's body. - /// - /// # Safety - /// - /// Since the body is not included in the atom reference, this method has to assume that it is valid memory and therefore is unsafe. - pub unsafe fn from_atom(atom: &mut sys::LV2_Atom) -> Self { - let space = std::slice::from_raw_parts_mut( - atom as *mut _ as *mut u8, - atom.size as usize + size_of::(), - ); - Self::new(space) - } - - /// Create a new instance. - /// - /// This method takes the space reserved for the value and interprets it as a slice of bytes (`&mut [u8]`). - pub fn new(space: &'a mut [u8]) -> Self { - RootMutSpace { - space: Cell::new(Some(space)), - allocated_bytes: 0, - } - } -} - -impl<'a> MutSpace<'a> for RootMutSpace<'a> { - fn allocate(&mut self, size: usize, apply_padding: bool) -> Option<(usize, &'a mut [u8])> { - if self.space.get_mut().is_none() { - return None; - } - let mut space = self.space.replace(None).unwrap(); - - let padding = if apply_padding { - let alignment = self.allocated_bytes % 8; - let padding = if alignment == 0 { 0 } else { 8 - alignment }; - if padding > space.len() { - return None; - } - space = space.split_at_mut(padding).1; - self.allocated_bytes += padding; - padding - } else { - 0 - }; - - if size > space.len() { - return None; - } - let (lower_slice, upper_slice) = space.split_at_mut(size); - self.allocated_bytes += size; - - self.space.set(Some(upper_slice)); - Some((padding, lower_slice)) - } -} - -/// Linked list element for dynamic atom writing. -/// -/// This struct works in conjunction with [`SpaceHead`](struct.SpaceHead.html) to provide a way to write atoms to dynamically allocated memory. -pub struct SpaceElement { - next: Option<(Box, Box<[u8]>)>, -} - -impl Default for SpaceElement { - fn default() -> Self { - Self { next: None } - } -} - -impl SpaceElement { - /// Append an element to the list. - /// - /// If this is the last element of the list, allocate a slice of the required length and append a new element to the list. If not, do nothing and return `None`. - pub fn allocate(&mut self, size: usize) -> Option<(&mut Self, &mut [u8])> { - if self.next.is_some() { - return None; - } - - let new_data = vec![0u8; size].into_boxed_slice(); - let new_element = Box::new(Self::default()); - self.next = Some((new_element, new_data)); - self.next - .as_mut() - .map(|(new_element, new_data): &mut (Box, Box<[u8]>)| { - (new_element.as_mut(), new_data.as_mut()) - }) - } - - /// Create a vector containing the data from all elements following this one. - pub fn to_vec(&self) -> Vec { - self.iter() - .map(|slice| slice.iter()) - .flatten() - .cloned() - .collect() - } - - /// Return an iterator over the chunks of all elements following this one. - pub fn iter(&self) -> impl Iterator { - std::iter::successors(self.next.as_ref(), |element| element.0.next.as_ref()) - .map(|(_, data)| data.as_ref()) - } -} - -/// A mutable space that dynamically allocates memory. -/// -/// This space uses a linked list of [`SpaceElement`s](struct.SpaceElement.html) to allocate memory. Every time `allocate` is called, a new element is appended to the list and a new byte slice is created. -/// -/// In order to use this space and retrieve the written data once it was written, you create a `SpaceElement` and create a new head with it. Then, you use the head like any other `MutSpace` and when you're done, you retrieve the written data by either calling [`to_vec`](struct.SpaceElement.html#method.to_vec) or [`iter`](struct.SpaceElement.html#iter). -/// -/// # Usage example -/// -/// ``` -/// # use lv2_core::prelude::*; -/// # use lv2_atom::prelude::*; -/// # use lv2_atom::space::*; -/// # use urid::*; -/// # use std::pin::Pin; -/// # let map = HashURIDMapper::new(); -/// // URID cache creation is omitted. -/// let urids: AtomURIDCollection = map.populate_collection().unwrap(); -/// -/// // Creating the first element in the list and the writing head. -/// let mut element = SpaceElement::default(); -/// let mut head = SpaceHead::new(&mut element); -/// -/// // Writing an integer. -/// (&mut head as &mut dyn MutSpace).init(urids.int, 42).unwrap(); -/// -/// // Retrieving a continuos vector with the written data and verifying it's contents. -/// let written_data: Vec = element.to_vec(); -/// let atom = UnidentifiedAtom::new(Space::from_slice(written_data.as_ref())); -/// assert_eq!(42, atom.read(urids.int, ()).unwrap()); -/// ``` -pub struct SpaceHead<'a> { - element: Option<&'a mut SpaceElement>, - allocated_space: usize, -} - -impl<'a> SpaceHead<'a> { - /// Create a new head that references the given element. - pub fn new(element: &'a mut SpaceElement) -> Self { - Self { - element: Some(element), - allocated_space: 0, - } - } - - fn internal_allocate(&mut self, size: usize) -> Option<&'a mut [u8]> { - let element = self.element.take()?; - let (new_element, new_space) = element.allocate(size)?; - self.element = Some(new_element); - self.allocated_space += size; - Some(new_space) - } -} - -impl<'a> MutSpace<'a> for SpaceHead<'a> { - fn allocate(&mut self, size: usize, apply_padding: bool) -> Option<(usize, &'a mut [u8])> { - let padding: usize = if apply_padding { - (8 - self.allocated_space % 8) % 8 - } else { - 0 - }; - - if padding != 0 { - self.internal_allocate(padding); - } - - self.internal_allocate(size) - .map(|new_space| (padding, new_space)) - } -} - -/// A `MutSpace` that notes the amount of allocated space in an atom header. -pub struct FramedMutSpace<'a, 'b> { - atom: &'a mut sys::LV2_Atom, - parent: &'b mut dyn MutSpace<'a>, -} - -impl<'a, 'b> FramedMutSpace<'a, 'b> { - /// Create a new framed space with the given parent and type URID. - pub fn new(parent: &'b mut dyn MutSpace<'a>, urid: URID) -> Option { - let atom = sys::LV2_Atom { - size: 0, - type_: urid.get(), - }; - let atom: &'a mut sys::LV2_Atom = parent.write(&atom, true)?; - Some(Self { atom, parent }) - } -} - -impl<'a, 'b> MutSpace<'a> for FramedMutSpace<'a, 'b> { - fn allocate(&mut self, size: usize, apply_padding: bool) -> Option<(usize, &'a mut [u8])> { - self.parent - .allocate(size, apply_padding) - .map(|(padding, data)| { - self.atom.size += (size + padding) as u32; - (padding, data) - }) - } -} - -impl<'a, 'b> dyn MutSpace<'a> + 'b { - /// Write a sized object to the space. - /// - /// If `apply_padding` is `true`, the method will assure that the written instance is 64-bit-aligned. - pub fn write(&mut self, instance: &T, apply_padding: bool) -> Option<&'a mut T> - where - T: Unpin + Copy + Send + Sync + Sized + 'static, - { - let size = std::mem::size_of::(); - let input_data = - unsafe { std::slice::from_raw_parts(instance as *const T as *const u8, size) }; - - let output_data = self.write_raw(input_data, apply_padding)?; - - assert_eq!(size, output_data.len()); - Some(unsafe { &mut *(output_data.as_mut_ptr() as *mut T) }) - } - - /// Initialize a new atom in the space. - pub fn init<'c, A: Atom<'a, 'c>>( - &'c mut self, - urid: URID, - parameter: A::WriteParameter, - ) -> Option { - let new_space = FramedMutSpace::new(self, urid)?; - A::init(new_space, parameter) - } -} - -#[cfg(test)] -mod tests { - use crate::space::*; - use std::mem::{size_of, size_of_val}; - use urid::*; - - #[test] - fn test_space() { - let mut vector: Vec = vec![0; 256]; - for i in 0..128 { - vector[i] = i as u8; - } - unsafe { - let ptr = vector.as_mut_slice().as_mut_ptr().add(128) as *mut u32; - *(ptr) = 0x42424242; - } - - let space = Space::from_slice(vector.as_slice()); - let (lower_space, space) = space.split_raw(128).unwrap(); - for i in 0..128 { - assert_eq!(lower_space[i], i as u8); - } - - let (integer, _) = space.split_type::().unwrap(); - assert_eq!(*integer, 0x42424242); - } - - #[test] - fn test_split_atom() { - let mut data: Box<[u64]> = Box::new([0; 256]); - let urid: URID = unsafe { URID::new_unchecked(17) }; - - // Writing an integer atom. - unsafe { - *(data.as_mut_ptr() as *mut sys::LV2_Atom_Int) = sys::LV2_Atom_Int { - atom: sys::LV2_Atom { - size: size_of::() as u32, - type_: urid.get(), - }, - body: 42, - } - } - - let space = Space::from_reference(data.as_ref()); - let (atom, _) = space.split_atom().unwrap(); - let (body, _) = atom.split_atom_body(urid).unwrap(); - let body = body.data().unwrap(); - - assert_eq!(size_of::(), size_of_val(body)); - assert_eq!(42, unsafe { *(body.as_ptr() as *const i32) }); - } - - #[test] - fn test_from_reference() { - let value: u64 = 0x42424242; - let space = Space::from_reference(&value); - assert_eq!(value, *space.split_type::().unwrap().0); - } - - #[test] - fn test_concat() { - let data: Box<[u64]> = Box::new([0; 64]); - let space = Space::from_reference(data.as_ref()); - let (lhs, rhs) = space.split_space(8).unwrap(); - let concated_space = Space::concat(lhs, rhs).unwrap(); - assert_eq!( - space.data().unwrap().as_ptr(), - concated_space.data().unwrap().as_ptr() - ); - assert_eq!( - space.data().unwrap().len(), - concated_space.data().unwrap().len() - ); - } - - fn test_mut_space<'a, S: MutSpace<'a>>(mut space: S) { - let map = HashURIDMapper::new(); - let urids = crate::AtomURIDCollection::from_map(&map).unwrap(); - - let mut test_data: Vec = vec![0; 24]; - for i in 0..test_data.len() { - test_data[i] = i as u8; - } - - match space.write_raw(test_data.as_slice(), true) { - Some(written_data) => assert_eq!(test_data.as_slice(), written_data), - None => panic!("Writing failed!"), - } - - let test_atom = sys::LV2_Atom { size: 42, type_: 1 }; - let written_atom = (&mut space as &mut dyn MutSpace) - .write(&test_atom, true) - .unwrap(); - assert_eq!(written_atom.size, test_atom.size); - assert_eq!(written_atom.type_, test_atom.type_); - - let created_space = unsafe { RootMutSpace::from_atom(written_atom) } - .space - .take() - .unwrap(); - assert_eq!( - created_space.as_ptr() as usize, - written_atom as *mut _ as usize - ); - assert_eq!(created_space.len(), size_of::() + 42); - - let mut atom_frame = - FramedMutSpace::new(&mut space as &mut dyn MutSpace, urids.chunk).unwrap(); - - let mut test_data: Vec = vec![0; 24]; - for i in 0..test_data.len() { - test_data[i] = i as u8; - } - - let written_data = atom_frame.write_raw(test_data.as_slice(), true).unwrap(); - assert_eq!(test_data.as_slice(), written_data); - assert_eq!(atom_frame.atom.size, test_data.len() as u32); - - let test_atom = sys::LV2_Atom { size: 42, type_: 1 }; - let borrowed_frame = &mut atom_frame as &mut dyn MutSpace; - let written_atom = borrowed_frame.write(&test_atom, true).unwrap(); - assert_eq!(written_atom.size, test_atom.size); - assert_eq!(written_atom.type_, test_atom.type_); - assert_eq!( - atom_frame.atom.size as usize, - test_data.len() + size_of_val(&test_atom) - ); - } - - #[test] - fn test_root_mut_space() { - const MEMORY_SIZE: usize = 256; - let mut memory: [u64; MEMORY_SIZE] = [0; MEMORY_SIZE]; - let frame: RootMutSpace = RootMutSpace::new(unsafe { - std::slice::from_raw_parts_mut( - (&mut memory).as_mut_ptr() as *mut u8, - MEMORY_SIZE * size_of::(), - ) - }); - - test_mut_space(frame); - } - - #[test] - fn test_space_head() { - let mut space = SpaceElement::default(); - let head = SpaceHead::new(&mut space); - test_mut_space(head); - } - - #[test] - fn test_padding_inside_frame() { - const MEMORY_SIZE: usize = 256; - let mut memory: [u64; MEMORY_SIZE] = [0; MEMORY_SIZE]; - let raw_space: &mut [u8] = unsafe { - std::slice::from_raw_parts_mut( - (&mut memory).as_mut_ptr() as *mut u8, - MEMORY_SIZE * size_of::(), - ) - }; - - // writing - { - let mut root: RootMutSpace = RootMutSpace::new(raw_space); - let mut frame = - FramedMutSpace::new(&mut root as &mut dyn MutSpace, URID::<()>::new(1).unwrap()) - .unwrap(); - { - let frame = &mut frame as &mut dyn MutSpace; - frame.write::(&42, true).unwrap(); - frame.write::(&17, true).unwrap(); - } - } - - // checking - { - let (atom, space) = raw_space.split_at(size_of::()); - let atom = unsafe { &*(atom.as_ptr() as *const sys::LV2_Atom) }; - assert_eq!(atom.type_, 1); - assert_eq!(atom.size as usize, 12); - - let (value, space) = space.split_at(size_of::()); - let value = unsafe { *(value.as_ptr() as *const u32) }; - assert_eq!(value, 42); - let (_, space) = space.split_at(4); - - let (value, _) = space.split_at(size_of::()); - let value = unsafe { *(value.as_ptr() as *const u32) }; - assert_eq!(value, 17); - } - } - - #[test] - fn unaligned_root_write() { - let mut raw_space = Box::new([0u8; 8]); - - { - let mut root_space = RootMutSpace::new(&mut raw_space[3..]); - (&mut root_space as &mut dyn MutSpace) - .write(&42u8, true) - .unwrap(); - } - - assert_eq!(&[0, 0, 0, 42, 0, 0, 0, 0], raw_space.as_ref()); - } -} +//! A collection of tools to assist reading and writing custom Atom types in Atom byte buffers (referred as **Spaces**). +#![deny(missing_docs)] + +mod aligned; +mod allocator; +mod atom_writer; +mod cursor; +pub mod error; +mod reader; +mod terminated; +mod vec; + +pub use aligned::{AlignedSpace, AtomSpace}; +pub use allocator::*; +pub use atom_writer::AtomWriter; +pub use cursor::SpaceCursor; +pub use reader::SpaceReader; +pub use terminated::Terminated; +pub use vec::{AlignedVec, AlignedVecCursor}; diff --git a/atom/src/space/aligned.rs b/atom/src/space/aligned.rs new file mode 100644 index 00000000..82c8428e --- /dev/null +++ b/atom/src/space/aligned.rs @@ -0,0 +1,665 @@ +use crate::header::AtomHeader; +use crate::space::error::{AlignmentError, AlignmentErrorInner, TypeData}; +use crate::space::SpaceCursor; +use crate::space::SpaceReader; +use core::mem::{align_of, size_of}; +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; +use std::mem::{size_of_val, MaybeUninit}; +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +/// An slice of bytes with the alignment of a type `T`. +/// +/// This type is a simple byte slice that guarantees its start is properly aligned for containing a type `T`. +/// This buffer can be split and realigned, effectively allowing to read a stream of arbitrary types +/// from a byte buffer, such as [`Atom`s](crate::Atom). +/// +/// Any operation that may lead to a misaligned `AlignedSpace` is considered unsafe. +/// +/// Note that only the start of the slice is aligned, not the end. This allows having a buffer that +/// is bigger than a multiple of `T`'s alignment. +/// +/// Although they are aligned, `AlignedSpace`s do not consider their contents to be initialized. Therefore, +/// the only safe reading operations will return `MaybeUninit`. Unsafe helper methods that assume +/// the contents are initialized are also provided, for convenience. +/// +/// # Example +/// +/// The following example reads `u64`s from an aligned byte slice. +/// +/// ``` +/// # use lv2_atom::space::AlignedSpace; +/// let values = &[42u64, 69]; +/// // Transmuting to a slice of bytes. +/// let bytes: &[u8] = unsafe { values.align_to().1 }; +/// +/// // --- Imagine those bytes are sent over and then received from an external buffer +/// +/// // Bytes are already aligned, the whole slice will be available +/// let space: &AlignedSpace = AlignedSpace::align_from_bytes(bytes).unwrap(); +/// // SAFETY: we know the slice was initialized with proper u64 values. +/// let read_values = unsafe { space.assume_init_slice() }; +/// assert_eq!(read_values, [42u64, 69]); +/// ``` +/// +#[derive(Eq, PartialEq)] +#[repr(transparent)] +pub struct AlignedSpace { + _type: PhantomData, + data: [u8], +} + +/// A slice of bytes that is properly aligned to contain Atoms (i.e. is 64-bit aligned). +/// +/// See [`AlignedSpace`] for more information. +pub type AtomSpace = AlignedSpace; + +impl AlignedSpace { + /// Creates a new boxed space from an already aligned, potentially uninitialized boxed slice of T. + /// + /// This method provides a lightweight conversion: it does not copy the contents nor perform any + /// (re)allocations. + #[inline] + pub fn from_boxed_uninit_slice(slice: Box<[MaybeUninit]>) -> Box { + // SAFETY: casting MaybeUninit to [u8] is always safe. + // SAFETY: because this is MaybeUninit, the slice is always aligned. + unsafe { core::mem::transmute(slice) } + } + + /// Creates a new space from a slice of bytes. + /// + /// # Errors + /// + /// This method returns an [`AlignmentError`] if the given slice is not aligned + /// (i.e. if it's pointer's value is not a multiple of `align_of::()` bytes). + /// + /// # Example + /// + /// ``` + /// # use lv2_atom::space::AlignedSpace; + /// let values = &[42u64, 69]; + /// // Transmuting to a slice of bytes + /// let bytes: &[u8] = unsafe { values.align_to().1 }; + /// + /// assert!(AlignedSpace::::from_bytes(bytes).is_ok()); + /// assert!(AlignedSpace::::from_bytes(&bytes[1..]).is_err()); + /// ``` + #[inline] + pub fn from_bytes(data: &[u8]) -> Result<&Self, AlignmentError> { + check_alignment::(data)?; + + // SAFETY: We just checked above that the pointer is correctly aligned + Ok(unsafe { AlignedSpace::from_bytes_unchecked(data) }) + } + + /// Creates a new mutable space from a mutable slice of bytes. + /// + /// # Errors + /// + /// This method returns an [`AlignmentError`] if the given slice is not aligned + /// (i.e. if it's pointer's value is not a multiple of `align_of::()` bytes). + /// + /// # Example + /// + /// ``` + /// # use lv2_atom::space::AlignedSpace; + /// let values = &mut [42u64, 69]; + /// // Transmuting to a slice of bytes + /// let bytes: &mut [u8] = unsafe { values.align_to_mut().1 }; + /// + /// assert!(AlignedSpace::::from_bytes_mut(bytes).is_ok()); + /// assert!(AlignedSpace::::from_bytes_mut(&mut bytes[1..]).is_err()); + /// ``` + #[inline] + pub fn from_bytes_mut(data: &mut [u8]) -> Result<&mut Self, AlignmentError> { + check_alignment::(data)?; + + // SAFETY: We just checked above that the pointer is correctly aligned + Ok(unsafe { AlignedSpace::from_bytes_mut_unchecked(data) }) + } + + /// Creates a new space from a slice of bytes, slicing some bytes off its start if necessary. + /// + /// # Errors + /// + /// This method returns an [`AlignmentError`] if the given slice's is too small to contain + /// aligned bytes (e.g. if it's smaller than `align_of::()` bytes). + /// + /// # Example + /// + /// ``` + /// # use lv2_atom::space::{AlignedSpace, error::AtomReadError}; + /// let values = &[42u64, 69]; + /// // Transmuting to a slice of bytes + /// let bytes: &[u8] = unsafe { values.align_to().1 }; + /// + /// // The slice has space for both values + /// assert_eq!(AlignedSpace::::align_from_bytes(bytes).unwrap().values_len(), 2); + /// // The slice now only has space for a single value + /// assert_eq!(AlignedSpace::::align_from_bytes(&bytes[1..]).unwrap().values_len(), 1); + /// // The slice doesn't have space for any value anymore + /// assert_eq!(AlignedSpace::::align_from_bytes(&bytes[9..]).unwrap().values_len(), 0); + /// // The slice cannot be aligned + /// assert!(AlignedSpace::::align_from_bytes(&bytes[10..11]).is_err()); + /// ``` + #[inline] + pub fn align_from_bytes(data: &[u8]) -> Result<&Self, AlignmentError> { + let padding = crate::util::try_padding_for::(data)?; + let data_len = data.len(); + + let data = data.get(padding..).ok_or_else(|| { + AlignmentError(AlignmentErrorInner::NotEnoughSpaceToRealign { + ptr: data.as_ptr(), + available_size: data_len, + required_padding: padding + 1, + type_id: TypeData::of::(), + }) + })?; + + // SAFETY: We just aligned the slice start + Ok(unsafe { AlignedSpace::from_bytes_unchecked(data) }) + } + + /// Creates a new mutable space from a mutable slice of bytes, slicing some bytes off its start if necessary. + /// + /// # Errors + /// + /// This method returns an [`AlignmentError`] if the given slice's is too small to contain + /// aligned bytes (e.g. if no byte in it is properly aligned). + /// + /// # Example + /// + /// ``` + /// # use lv2_atom::space::{AlignedSpace, error::AtomWriteError}; + /// let values = &mut [42u64, 69]; + /// // Transmuting to a slice of bytes + /// let bytes: &mut [u8] = unsafe { values.align_to_mut().1 }; + /// + /// // The slice has space for both values + /// assert_eq!(AlignedSpace::::align_from_bytes_mut(bytes).unwrap().values_len(), 2); + /// // The slice now only has space for a single value + /// assert_eq!(AlignedSpace::::align_from_bytes_mut(&mut bytes[1..]).unwrap().values_len(), 1); + /// // The slice doesn't have space for any value anymore + /// assert_eq!(AlignedSpace::::align_from_bytes_mut(&mut bytes[9..]).unwrap().values_len(), 0); + /// // The slice cannot be aligned + /// assert!(AlignedSpace::::align_from_bytes_mut(&mut bytes[10..11]).is_err()); + /// ``` + #[inline] + pub fn align_from_bytes_mut(data: &mut [u8]) -> Result<&mut Self, AlignmentError> { + let padding = crate::util::try_padding_for::(data)?; + let data_len = data.len(); + let data_ptr = data.as_ptr(); + + let data = data.get_mut(padding..).ok_or_else(|| { + AlignmentError(AlignmentErrorInner::NotEnoughSpaceToRealign { + ptr: data_ptr, + available_size: data_len, + required_padding: padding + 1, + type_id: TypeData::of::(), + }) + })?; + + // SAFETY: We just aligned the slice start + Ok(unsafe { AlignedSpace::from_bytes_mut_unchecked(data) }) + } + + /// Creates a new space from a slice of bytes, without checking for padding correctness. + /// + /// # Safety + /// + /// The caller of this method is responsible for ensuring that the slice's contents are correctly aligned. + /// Calling this method with an unaligned slice will result in Undefined Behavior. + /// + /// For a safe, checked version, see [`AlignedSpace::from_bytes`]. + /// + /// # Example + /// + /// ``` + /// # use lv2_atom::space::{AlignedSpace, error::AtomWriteError}; + /// let values = &[42u64, 69]; + /// // Transmuting to a slice of bytes + /// let bytes: &[u8] = unsafe { values.align_to().1 }; + /// + /// assert_eq!(unsafe { AlignedSpace::::from_bytes_unchecked(bytes) }.values_len(), 2); + /// ``` + // NOTE: This method will always be used internally instead of the constructor, to make sure that + // the unsafety is explicit and accounted for. + #[inline(always)] + pub unsafe fn from_bytes_unchecked(data: &[u8]) -> &AlignedSpace { + // SAFETY: It is safe to transmute, since our type has repr(transparent) with [u8]. + // SAFETY: The caller is responsible to check for slice alignment. + &*(data as *const _ as *const Self) + } + + /// Creates a new mutable space from a slice of bytes, without checking for padding correctness. + /// + /// # Safety + /// + /// The caller of this method is responsible for ensuring that the slice's contents are correctly aligned. + /// Otherwise, reads will be performed unaligned, which are either slow, a CPU crash, or UB depending on platforms. + /// + /// For a safe, checked version, see [`AlignedSpace::from_bytes_mut`]. + /// + /// # Example + /// + /// ``` + /// # use lv2_atom::space::{AlignedSpace, error::AtomWriteError}; + /// let values = &mut [42u64, 69]; + /// // Transmuting to a slice of bytes + /// let bytes: &mut [u8] = unsafe { values.align_to_mut().1 }; + /// + /// assert_eq!(unsafe { AlignedSpace::::from_bytes_mut_unchecked(bytes) }.values_len(), 2); + // NOTE: This method will always be used internally instead of the constructor, to make sure that + // the unsafety is explicit and accounted for. + #[inline(always)] + pub unsafe fn from_bytes_mut_unchecked(data: &mut [u8]) -> &mut AlignedSpace { + // SAFETY: It is safe to transmute, since our type has repr(transparent) with [u8]. + // SAFETY: The caller is responsible to check for slice alignment. + &mut *(data as *mut _ as *mut Self) + } + + /// Creates a new space from an already aligned slice of T values. + /// + /// The slice type guarantees alignment, therefore this operation is infallible. + /// + /// # Example + /// + /// ``` + /// # use lv2_atom::space::{AlignedSpace, error::AtomWriteError}; + /// let values = &[42u64, 69]; + /// + /// let space: &AlignedSpace = AlignedSpace::from_slice(values); + /// assert_eq!(space.values_len(), 2); + /// assert_eq!(space.bytes_len(), 2 * ::core::mem::size_of::()); + /// ``` + #[inline] + pub fn from_slice(slice: &[T]) -> &Self { + // SAFETY: reinterpreting as raw bytes is safe for any value + let bytes = unsafe { from_raw_parts(slice.as_ptr() as *const u8, size_of_val(slice)) }; + // SAFETY: The pointer is a slice of T, therefore it is already correctly aligned + unsafe { Self::from_bytes_unchecked(bytes) } + } + + /// Creates a new mutable space from an already aligned slice of T values. + /// + /// The slice type guarantees alignment, therefore this operation is infallible. + /// + /// # Example + /// + /// ``` + /// # use lv2_atom::space::{AlignedSpace, error::AtomWriteError}; + /// let values = &[42u64, 69]; + /// + /// let space: &AlignedSpace = AlignedSpace::from_slice(values); + /// assert_eq!(space.values_len(), 2); + /// assert_eq!(space.bytes_len(), 2 * ::core::mem::size_of::()); + #[inline] + pub fn from_slice_mut(slice: &mut [T]) -> &mut Self { + // SAFETY: reinterpreting as raw bytes is safe for any value + let bytes = + unsafe { from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, size_of_val(slice)) }; + // SAFETY: The pointer is a slice of T, therefore it is already correctly aligned + unsafe { Self::from_bytes_mut_unchecked(bytes) } + } + + /// Creates a new space from an already aligned, potentially uninitialized slice of T. + #[inline] + pub(crate) fn from_uninit_slice(slice: &[MaybeUninit]) -> &Self { + // SAFETY: reinterpreting as raw bytes is safe for any value + let bytes = unsafe { from_raw_parts(slice.as_ptr() as *const u8, size_of_val(slice)) }; + // SAFETY: The pointer is a slice of T, therefore it is already correctly aligned + unsafe { Self::from_bytes_unchecked(bytes) } + } + + /// Creates a new space from an already aligned, potentially uninitialized slice of T. + #[inline] + pub(crate) fn from_uninit_slice_mut(slice: &mut [MaybeUninit]) -> &mut Self { + // SAFETY: reinterpreting as raw bytes is safe for any value + let bytes = + unsafe { from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, size_of_val(slice)) }; + // SAFETY: The pointer is a slice of T, therefore it is already correctly aligned + unsafe { Self::from_bytes_mut_unchecked(bytes) } + } + + /// A checked version of [`[T]::split_at()`](slice::split_at), which returns the first part as an already-aligned slice. + #[inline] + pub fn split_at(&self, mid: usize) -> Option<(&Self, &[u8])> { + if mid > self.data.len() { + return None; + } + + let (start, end) = self.data.split_at(mid); + // SAFETY: Because this data was the start of an existing Space, it was aligned already. + let start = unsafe { Self::from_bytes_unchecked(start) }; + + Some((start, end)) + } + + /// A checked version of [`[T]::split_at_mut()`](slice::split_at_mut), which returns the first part as an already-aligned mutable slice. + #[inline] + pub fn split_at_mut(&mut self, mid: usize) -> Option<(&mut Self, &mut [u8])> { + if mid > self.data.len() { + return None; + } + + let (start, end) = self.data.split_at_mut(mid); + // SAFETY: Because this data was the start of an existing Space, it was aligned already. + let start = unsafe { Self::from_bytes_mut_unchecked(start) }; + + Some((start, end)) + } + + /// Return the internal slice of the space. + #[inline] + pub fn as_bytes(&self) -> &[u8] { + &self.data + } + + /// Returns the internal mutable slice of the space. + #[inline] + pub fn as_bytes_mut(&mut self) -> &mut [u8] { + &mut self.data + } + + /// Returns the total length of the space, in bytes. + #[inline] + pub fn bytes_len(&self) -> usize { + self.data.len() + } + + /// Returns the number of values that can fit in the space. + /// + /// If `T` is a zero-sized type, this returns [`usize::MAX`](usize::MAX). + #[inline] + pub fn values_len(&self) -> usize { + self.data + .len() + .checked_div(size_of::()) + .unwrap_or(usize::MAX) + } + + /// Gets the contents as a slice of potentially uninitialized `T`s. + /// + /// The resulting slice contains as many values as can fit in the original space. + /// This means there might be less bytes in this slice than in this space, or zero if the space is too small for a single value. + #[inline] + pub fn as_uninit_slice(&self) -> &[MaybeUninit] { + // SAFETY: This type ensures alignment, so casting aligned bytes to uninitialized memory is safe. + unsafe { + ::core::slice::from_raw_parts( + self.data.as_ptr() as *const MaybeUninit, + self.data.len() / size_of::(), + ) + } + } + + /// Gets the contents as a mutable slice of potentially uninitialized `T`s. + /// + /// The resulting slice contains as many values as can fit in the original space. + /// This means there might be less bytes in this slice than in this space, or zero if the space is too small for a single value. + #[inline] + pub fn as_uninit_slice_mut(&mut self) -> &mut [MaybeUninit] { + // SAFETY: This type ensures alignment, so casting aligned bytes to uninitialized memory is safe. + unsafe { + ::core::slice::from_raw_parts_mut( + self.data.as_mut_ptr() as *mut MaybeUninit, + self.data.len() / size_of::(), + ) + } + } + + /// Gets the contents as a slice of `T`s that are all assumed to be initialized. + /// + /// # Safety + /// + /// Calling this when the space's content is not yet fully initialized causes undefined behavior. + /// It is up to the caller to guarantee that the underlying buffer really is in an initialized state. + #[inline] + pub unsafe fn assume_init_slice(&self) -> &[T] { + crate::util::assume_init_slice(self.as_uninit_slice()) + } + + /// Gets the contents as a mutable slice of `T`s that are all assumed to be initialized. + /// + /// # Safety + /// + /// Calling this when the space's content is not yet fully initialized causes undefined behavior. + /// It is up to the caller to guarantee that the underlying buffer really is in an initialized state. + /// + /// All of the safety concerns of [MaybeUninit::assume_init] are also applicable to this method. + #[inline] + pub unsafe fn assume_init_slice_mut(&mut self) -> &mut [T] { + crate::util::assume_init_slice_mut(self.as_uninit_slice_mut()) + } + + /// An helper method that creates a new [`SpaceReader`] from the space's contents. + #[inline] + pub fn read(&self) -> SpaceReader { + SpaceReader::new(self.as_bytes()) + } + + /// An helper method that creates a new [`SpaceCursor`] from the mutable space's contents. + #[inline] + pub fn write(&mut self) -> SpaceCursor { + SpaceCursor::new(self.as_bytes_mut()) + } +} + +impl Debug for AlignedSpace { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.data, f) + } +} + +#[inline] +fn check_alignment(data: &[u8]) -> Result<(), AlignmentError> { + if data.as_ptr() as usize % align_of::() != 0 { + return Err(AlignmentError(AlignmentErrorInner::UnalignedBuffer { + type_id: TypeData::of::(), + ptr: data.as_ptr(), + })); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::space::error::{AlignmentError, AlignmentErrorInner, TypeData}; + use crate::space::*; + use crate::{AtomHeader, UnidentifiedAtom}; + use std::mem::{size_of, size_of_val}; + use urid::*; + + #[test] + fn from_bytes() { + let values = &mut [42u64, 69]; + let bytes = unsafe { values.align_to_mut().1 }; + + assert_eq!( + AlignedSpace::::from_bytes(bytes).unwrap().bytes_len(), + ::core::mem::size_of::() * 2 + ); + assert_eq!( + AlignedSpace::::from_bytes_mut(bytes) + .unwrap() + .bytes_len(), + ::core::mem::size_of::() * 2 + ); + + assert_eq!( + unsafe { AlignedSpace::::from_bytes_unchecked(bytes) }.bytes_len(), + ::core::mem::size_of::() * 2 + ); + + assert_eq!( + unsafe { AlignedSpace::::from_bytes_mut_unchecked(bytes) }.bytes_len(), + ::core::mem::size_of::() * 2 + ); + + assert_eq!( + AlignedSpace::::from_bytes(&bytes[1..]), + Err(AlignmentError(AlignmentErrorInner::UnalignedBuffer { + type_id: TypeData::of::(), + ptr: bytes[1..].as_ptr() + })) + ); + + let ptr = bytes[1..].as_ptr(); + assert_eq!( + AlignedSpace::::from_bytes_mut(&mut bytes[1..]), + Err(AlignmentError(AlignmentErrorInner::UnalignedBuffer { + type_id: TypeData::of::(), + ptr + })) + ); + } + + #[test] + fn align_from_bytes() { + let values = &mut [42u64, 69]; + let bytes = unsafe { values.align_to_mut().1 }; + + let size = ::core::mem::size_of::(); + assert_eq!( + AlignedSpace::::align_from_bytes(bytes) + .unwrap() + .bytes_len(), + size * 2 + ); + + assert_eq!( + AlignedSpace::::align_from_bytes_mut(bytes) + .unwrap() + .bytes_len(), + size * 2 + ); + + assert_eq!( + AlignedSpace::::align_from_bytes(&bytes[1..]) + .unwrap() + .bytes_len(), + size + ); + + assert_eq!( + AlignedSpace::::align_from_bytes_mut(&mut bytes[1..]) + .unwrap() + .bytes_len(), + size + ); + + assert_eq!( + AlignedSpace::::align_from_bytes(&bytes[9..]) + .unwrap() + .bytes_len(), + 0 + ); + + assert_eq!( + AlignedSpace::::align_from_bytes_mut(&mut bytes[9..]) + .unwrap() + .bytes_len(), + 0 + ); + + assert_eq!( + AlignedSpace::::align_from_bytes(&bytes[9..11]), + Err(AlignmentError( + AlignmentErrorInner::NotEnoughSpaceToRealign { + type_id: TypeData::of::(), + ptr: bytes[9..11].as_ptr(), + available_size: 2, + required_padding: 8 + } + )) + ); + + let ptr = bytes[9..11].as_ptr(); + assert_eq!( + AlignedSpace::::align_from_bytes_mut(&mut bytes[9..11]), + Err(AlignmentError( + AlignmentErrorInner::NotEnoughSpaceToRealign { + type_id: TypeData::of::(), + ptr, + available_size: 2, + required_padding: 8 + } + )) + ); + } + + fn test_writer(mut space: impl SpaceWriter) { + let map = HashURIDMapper::new(); + let urids = crate::atoms::AtomURIDCollection::from_map(&map).unwrap(); + + let mut test_data: Vec = vec![0; 128]; + for (i, data) in test_data.iter_mut().enumerate() { + *data = i as u8; + } + + let written_data = space.write_bytes(test_data.as_slice()).unwrap(); + assert_eq!(test_data.as_slice(), written_data); + + let test_atom = sys::LV2_Atom { size: 42, type_: 1 }; + let written_atom = space.write_value(test_atom).unwrap(); + assert_eq!(written_atom.size, test_atom.size); + assert_eq!(written_atom.type_, test_atom.type_); + let written_atom_addr = written_atom as *mut _ as *mut _; + + let created_space = unsafe { + UnidentifiedAtom::from_header(AtomHeader::from_raw(written_atom)).atom_space() + }; + + assert!(::core::ptr::eq( + written_atom_addr, + created_space.as_bytes().as_ptr() + )); + assert_eq!(created_space.bytes_len(), size_of::() + 42); + + { + let space: &mut _ = &mut space; + let mut atom_frame = AtomWriter::write_new(space, urids.chunk).unwrap(); + + let mut test_data: Vec = vec![0; 24]; + for (i, data) in test_data.iter_mut().enumerate() { + *data = i as u8; + } + + let written_data = atom_frame.write_bytes(&test_data).unwrap(); + assert_eq!(test_data.as_slice(), written_data); + assert_eq!(atom_frame.atom_header().size_of_body(), test_data.len()); + + let test_atom = sys::LV2_Atom { size: 42, type_: 1 }; + let written_atom = atom_frame.write_value(test_atom).unwrap(); + assert_eq!(written_atom.size, test_atom.size); + assert_eq!(written_atom.type_, test_atom.type_); + assert_eq!( + atom_frame.atom_header().size_of_body(), + test_data.len() + size_of_val(&test_atom) + ); + } + } + + #[test] + fn test_root_mut_space() { + const MEMORY_SIZE: usize = 256; + let mut memory = [0; MEMORY_SIZE]; + let cursor = SpaceCursor::new(&mut memory[..]); + + test_writer(cursor); + } + + #[test] + fn unaligned_root_write() { + let mut raw_space = Box::new([0u8; 8]); + + { + let mut root_space = SpaceCursor::new(&mut raw_space[3..]); + root_space.write_value(42u8).unwrap(); + } + + assert_eq!(&[0, 0, 0, 42, 0, 0, 0, 0], raw_space.as_ref()); + } +} diff --git a/atom/src/space/allocator.rs b/atom/src/space/allocator.rs new file mode 100644 index 00000000..70786172 --- /dev/null +++ b/atom/src/space/allocator.rs @@ -0,0 +1,347 @@ +use crate::space::{AlignedSpace, AtomWriter}; +use crate::{Atom, AtomHandle, UnidentifiedAtom}; +use urid::URID; + +use crate::space::error::AtomWriteError; +use crate::space::terminated::Terminated; +use core::mem::{size_of, size_of_val, MaybeUninit}; + +/// The result of a [`SpaceAllocator`](SpaceAllocator) allocation. +/// +/// This structure allows simultaneous access to both the newly allocated slice, and all previously +/// allocated bytes. +pub struct SpaceWriterSplitAllocation<'a> { + /// All previously allocated bytes. This is guaranteed to be contiguous with `allocated`. + pub previous: &'a mut [u8], + /// The requested newly-allocated bytes + pub allocated: &'a mut [u8], +} + +/// An object-safe trait to allocate bytes from a contiguous buffer to write Atom data into. +/// +/// Implementors of this trait act like a sort of cursor, each allocation being contiguous with the +/// previous one. +/// +/// This trait is very bare-bones, in order to be trait-object-safe. As an user, you probably want +/// to use the [`SpaceWriter`] trait, an extension trait with many more utilities available, and with a +/// blanket implementation for all types that implement [`SpaceAllocator`]. +/// +/// The term "allocate" is used very loosely here, as even a simple cursor over a mutable byte +/// buffer (e.g. [`SpaceCursor`](crate::space::SpaceCursor)) can "allocate" bytes using this trait. +/// +/// "Allocations" made using this trait dot not imply actual system allocations. However, some +/// implementations (such as [`AlignedVecCursor`](crate::space::AlignedVecCursor)) might perform a +/// system reallocation if the requested space exceeds its internal capacity. +/// +/// Other implementations, such as [`SpaceCursor`](crate::space::SpaceCursor), are guaranteed to +/// never allocate, and will instead return an error when exceeding its capacity. +/// +/// This trait is useful to abstract over many types of buffers, including ones than can track the +/// amount of allocated bytes into an atom header (i.e. [`AtomWriter`]). +pub trait SpaceAllocator { + /// Allocates a new byte buffer of the requested size. A mutable reference to both the newly + /// allocated slice and all previously allocated bytes is returned (through [`SpaceWriterSplitAllocation`]), + /// allowing some implementations to update previous data as well. + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer. + /// + /// # Panics + /// + /// This function may panic if the given size, added to the length of the total allocated bytes, + /// overflows an [`usize`]. + fn allocate_and_split( + &mut self, + size: usize, + ) -> Result; + + /// Rewinds the writer by a given amount of bytes, allowing to overwrite previously allocated + /// bytes. + /// + /// # Errors + /// + /// This method may return an error if `byte_count` is greater than the amount of all already + /// allocated bytes. + /// + /// # Safety + /// + /// Rewinding may allow other atoms to be overwritten, and thus completely invalidate their + /// contents and internal structure. The caller is responsible to ensure that the exposed data + /// is safe to be overwritten. + unsafe fn rewind(&mut self, byte_count: usize) -> Result<(), AtomWriteError>; + + /// Returns a slice pointing to the previously allocated bytes. + fn allocated_bytes(&self) -> &[u8]; + + /// Returns a mutable slice pointing to the previously allocated bytes. + /// + /// # Safety + /// + /// Accessing allocated bytes may allow other atoms to be overwritten, and thus completely + /// invalidate their contents and internal structure. The caller is responsible to ensure that + /// the exposed data is safe to be overwritten. + unsafe fn allocated_bytes_mut(&mut self) -> &mut [u8]; + + /// Returns a slice pointing to the remaining, uninitialized bytes. + fn remaining_bytes(&self) -> &[u8]; +} + +/// +pub trait SpaceWriter: SpaceAllocator + Sized { + /// Allocates and returns a new mutable byte buffer of the requested size, in bytes. + /// + /// # Example + /// + /// ``` + /// use lv2_atom::atom_prelude::*; + /// + /// let mut buffer = vec![0; 64]; + /// let mut writer = SpaceCursor::new(&mut buffer); + /// + /// let allocated = writer.allocate(5).unwrap(); + /// assert_eq!(allocated.len(), 5); + /// ``` + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer. + #[inline] + fn allocate(&mut self, size: usize) -> Result<&mut [u8], AtomWriteError> { + let allocated = self.allocate_and_split(size)?; + assert_eq!(allocated.allocated.len(), size); + Ok(allocated.allocated) + } + + /// Allocates and returns a new aligned mutable byte buffer of the requested size, in bytes. + /// + /// The resulting buffer is guaranteed to be aligned to the alignment requirements of the given + /// type `T`, meaning a value of type `T` can be safely written into it directly. + /// + /// # Example + /// + /// ``` + /// use lv2_atom::atom_prelude::*; + /// + /// let mut buffer = vec![0; 64]; + /// let mut writer = SpaceCursor::new(&mut buffer); + /// + /// let allocated = writer.allocate_aligned::(5).unwrap(); + /// assert_eq!(allocated.bytes_len(), 5); + /// ``` + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer, or if the padding and/or alignment requirements couldn't + /// be met. + #[inline] + fn allocate_aligned( + &mut self, + byte_size: usize, + ) -> Result<&mut AlignedSpace, AtomWriteError> { + let required_padding = crate::util::try_padding_for::(self.remaining_bytes())?; + let raw = self.allocate(byte_size + required_padding)?; + + Ok(AlignedSpace::align_from_bytes_mut(raw)?) + } + + /// Allocates room in the byte buffer for a single value of type `T`. + /// + /// A mutable reference to the allocated buffer is returned as a + /// [`MaybeUninit`](core::mem::MaybeUninit), + /// as the resulting memory buffer is most likely going to be uninitialized. + /// + /// # Example + /// + /// ``` + /// use lv2_atom::atom_prelude::*; + /// use std::mem::MaybeUninit; + /// + /// let mut buffer = vec![0; 64]; + /// let mut writer = SpaceCursor::new(&mut buffer); + /// + /// let allocated = writer.allocate_value().unwrap(); + /// *allocated = MaybeUninit::new(42u32); + /// assert_eq!(unsafe { allocated.assume_init() }, 42); + /// ``` + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer, or if the padding and/or alignment requirements couldn't + /// be met. + #[inline] + fn allocate_value(&mut self) -> Result<&mut MaybeUninit, AtomWriteError> { + let space = self.allocate_aligned(size_of::>())?; + // SAFETY: We used size_of, so we are sure that the allocated space is exactly big enough for T. + Ok(unsafe { space.as_uninit_slice_mut().get_unchecked_mut(0) }) + } + + /// Allocates room in the byte buffer for multiple values of type `T`. + /// + /// A mutable reference to the allocated buffer is returned as a slice of + /// [`MaybeUninit`](core::mem::MaybeUninit)s, + /// as the resulting memory buffer is most likely going to be uninitialized. + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer, or if the padding and/or alignment requirements couldn't + /// be met. + #[inline] + fn allocate_values( + &mut self, + count: usize, + ) -> Result<&mut [MaybeUninit], AtomWriteError> { + let space = self.allocate_aligned(count * std::mem::size_of::())?; + Ok(space.as_uninit_slice_mut()) + } + + /// Writes an atom of a given type into the buffer. + /// + /// This method only initializes the new Atom header with the given type, tracking its + /// size, and returns the [writer](crate::Atom::write) associated to the given Atom type. + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer, or if the padding and/or alignment requirements couldn't + /// be met. + #[inline] + fn write_atom( + &mut self, + atom_type: URID, + ) -> Result<::Handle, AtomWriteError> { + let space = AtomWriter::write_new(self, atom_type)?; + A::write(space) + } + + /// Copies an already fully initialized atom of any type into the buffer. + /// + /// This method will simply copy the atom's bytes into the buffer, unchanged, and unaware of its + /// internal representation. + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer, or if the padding and/or alignment requirements couldn't + /// be met. + #[inline] + fn copy_atom( + &mut self, + atom: &UnidentifiedAtom, + ) -> Result<&mut UnidentifiedAtom, AtomWriteError> { + let resulting_space = self.allocate_aligned(atom.atom_space().bytes_len())?; + resulting_space + .as_bytes_mut() + .copy_from_slice(atom.atom_space().as_bytes()); + + // SAFETY: We just wrote those bytes, we know for sure they're the same and aligned + unsafe { UnidentifiedAtom::from_space_mut(resulting_space) } + } + + /// Writes the given bytes into the buffer. + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer. + #[inline] + fn write_bytes(&mut self, bytes: &[u8]) -> Result<&mut [u8], AtomWriteError> { + let space = self.allocate(bytes.len())?; + space.copy_from_slice(bytes); + Ok(space) + } + + /// Writes the given value into the buffer. + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer, or if the padding and/or alignment requirements couldn't + /// be met. + #[inline] + fn write_value(&mut self, value: T) -> Result<&mut T, AtomWriteError> + where + T: Copy + Sized + 'static, + { + let space = self.allocate_aligned(size_of_val(&value))?; + // SAFETY: We used size_of_val, so we are sure that the allocated space is exactly big enough for T. + let space = unsafe { space.as_uninit_slice_mut().get_unchecked_mut(0) }; + + Ok(crate::util::write_uninit(space, value)) + } + + /// Writes the given values into the buffer. + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer, or if the padding and/or alignment requirements couldn't + /// be met. + fn write_values(&mut self, values: &[T]) -> Result<&mut [T], AtomWriteError> + where + T: Copy + Sized + 'static, + { + let space: &mut AlignedSpace = self.allocate_aligned(size_of_val(values))?; + let space = space.as_uninit_slice_mut(); + + for (dst, src) in space.iter_mut().zip(values.iter()) { + *dst = MaybeUninit::new(*src) + } + + // SAFETY: Assume init: we just initialized the memory above + Ok(unsafe { &mut *(space as *mut [_] as *mut [T]) }) + } + + /// Makes all further operations from this writer write a given terminator byte. + /// + /// This method is a simple helper for [`Terminated::new`](Terminated::new) + /// + /// See the documentation for [`Terminated`](Terminated) for more information. + #[inline] + fn terminated(self, terminator: u8) -> Terminated { + Terminated::new(self, terminator) + } +} + +impl SpaceWriter for H where H: SpaceAllocator {} + +#[cfg(test)] +mod tests { + use crate::atom_prelude::*; + use crate::prelude::*; + use urid::URID; + + #[test] + fn test_write_value() { + let mut space = vec![0; 32]; + + let mut cursor = SpaceCursor::new(&mut space); + let new_value = cursor.write_value(42u8).unwrap(); + + assert_eq!(42, *new_value); + assert_eq!(31, cursor.remaining_bytes().len()); + } + + // SAFETY: this is just for testing, values aren't actually read using this URID. + const INT_URID: URID = unsafe { URID::new_unchecked(5) }; + + #[test] + fn test_write_atom() { + let mut space = vec![0; 32]; + + let mut cursor = SpaceCursor::new(&mut space); + + { + cursor.write_atom(INT_URID).unwrap().set(69).unwrap(); + assert_eq!(16, cursor.remaining_bytes().len()); + } + + assert_eq!(space[0..4], 8u32.to_ne_bytes()); + assert_eq!(space[4..8], 5u32.to_ne_bytes()); + assert_eq!(space[8..12], 69u32.to_ne_bytes()); + } +} diff --git a/atom/src/space/atom_writer.rs b/atom/src/space/atom_writer.rs new file mode 100644 index 00000000..109ff9d1 --- /dev/null +++ b/atom/src/space/atom_writer.rs @@ -0,0 +1,166 @@ +use crate::header::AtomHeader; +use crate::space::{ + error::AtomWriteError, AtomSpace, SpaceAllocator, SpaceWriter, SpaceWriterSplitAllocation, +}; +use urid::URID; + +/// A [`SpaceWriter`] that tracks the amount of allocated space in an atom header. +/// +/// This allows for writing dynamic, variable-size atoms without having to track their size manually. +/// +/// # Example +/// +/// ``` +/// use lv2_atom::atom_prelude::*; +/// use urid::URID; +/// +/// let mut buf = vec![0; 64]; +/// let mut cursor = SpaceCursor::new(&mut buf); +/// +/// let mut writer = AtomWriter::write_new(&mut cursor, URID::new(42).unwrap()).unwrap(); +/// +/// let message = b"Hello, world!"; +/// writer.write_bytes(message).unwrap(); +/// assert_eq!(writer.atom_header().size_of_body(), message.len()); +/// ``` +pub struct AtomWriter<'a> { + atom_header_index: usize, + parent: &'a mut (dyn SpaceAllocator), +} + +impl<'a> AtomWriter<'a> { + /// Retrieves a copy of the header this writer is currently tracking. + #[inline] + pub fn atom_header(&self) -> AtomHeader { + let previous = self + .parent + .allocated_bytes() + .get(self.atom_header_index..) + .expect("Unable to locate atom header"); + + let space = AtomSpace::from_bytes(previous).expect("Atom header location is unaligned"); + + unsafe { space.assume_init_slice()[0] } + } + + #[inline] + fn atom_header_mut(&mut self) -> &mut AtomHeader { + let previous = unsafe { self.parent.allocated_bytes_mut() } + .get_mut(self.atom_header_index..) + .unwrap(); + let space = AtomSpace::from_bytes_mut(previous).unwrap(); + + unsafe { &mut space.assume_init_slice_mut()[0] } + } + + /// Writes an atom header into the given [`SpaceWriter`], and returns a new writer that starts + /// tracking its size. + /// + /// # Errors + /// + /// This method may return an error if the writer ran out of space in its internal buffer, and + /// is unable to reallocate the buffer, or if the padding and/or alignment requirements couldn't + /// be met. + pub fn write_new( + parent: &'a mut impl SpaceWriter, + urid: URID, + ) -> Result { + let atom = AtomHeader::new(urid); + + parent.write_value(atom)?; + let atom_header_index = parent.allocated_bytes().len() - std::mem::size_of::(); + + Ok(Self { + atom_header_index, + parent, + }) + } +} + +impl<'a> SpaceAllocator for AtomWriter<'a> { + #[inline] + fn allocate_and_split( + &mut self, + size: usize, + ) -> Result { + let alloc = self.parent.allocate_and_split(size)?; + + let space = AtomSpace::from_bytes_mut( + // PANIC: We rely on the parent allocator not shifting bytes around + &mut alloc.previous[self.atom_header_index..], + )?; + + // SAFETY: We already initialized that part of the buffer + let header = unsafe { space.assume_init_slice_mut() } + .get_mut(0) + .expect("Unable to locate Atom Header. This is a bug due to an incorrect Allocator implementation"); + + // SAFETY: We just allocated `size` additional bytes for the body, we know they are properly allocated + unsafe { header.set_size_of_body(header.size_of_body() + size) }; + + Ok(alloc) + } + + #[inline] + unsafe fn rewind(&mut self, byte_count: usize) -> Result<(), AtomWriteError> { + self.parent.rewind(byte_count)?; + let header = self.atom_header_mut(); + + // SAFETY: Reducing the size of the atom is fine if rewind was successful + header.set_size_of_body(header.size_of_body() - byte_count); + + Ok(()) + } + + #[inline] + fn allocated_bytes(&self) -> &[u8] { + self.parent.allocated_bytes() + } + + #[inline] + unsafe fn allocated_bytes_mut(&mut self) -> &mut [u8] { + self.parent.allocated_bytes_mut() + } + + #[inline] + fn remaining_bytes(&self) -> &[u8] { + self.parent.remaining_bytes() + } +} + +#[cfg(test)] +mod tests { + use crate::atom_prelude::*; + use core::mem::size_of; + use urid::URID; + + #[test] + fn test_padding_inside_frame() { + let mut space = AlignedVec::::new_with_capacity(64); + let raw_space = space.as_bytes_mut(); + + // writing + { + let mut root = SpaceCursor::new(raw_space); + let mut frame = AtomWriter::write_new(&mut root, URID::new(1).unwrap()).unwrap(); + frame.write_value(42u32).unwrap(); + frame.write_value(17u32).unwrap(); + } + + // checking + { + let (atom, space) = raw_space.split_at(size_of::()); + let atom = unsafe { &*(atom.as_ptr() as *const sys::LV2_Atom) }; + assert_eq!(atom.type_, 1); + assert_eq!(atom.size as usize, 8); + + let (value, space) = space.split_at(size_of::()); + let value = unsafe { *(value.as_ptr() as *const u32) }; + assert_eq!(value, 42); + + let (value, _) = space.split_at(size_of::()); + let value = unsafe { *(value.as_ptr() as *const u32) }; + assert_eq!(value, 17); + } + } +} diff --git a/atom/src/space/cursor.rs b/atom/src/space/cursor.rs new file mode 100644 index 00000000..4224b6a7 --- /dev/null +++ b/atom/src/space/cursor.rs @@ -0,0 +1,83 @@ +use crate::space::error::AtomWriteError; +use crate::space::{SpaceAllocator, SpaceWriterSplitAllocation}; + +/// A lightweight [`SpaceWriter`](crate::space::SpaceWriter) that writes into a mutable byte buffer using a cursor. +/// +/// This cursor is backed by a simple mutable byte slice, and is therefore guaranteed to never +/// allocate. +/// +/// If the capacity of the underlying buffer is exceeded, an [`AtomWriteError::OutOfSpace`] error is +/// returned. +pub struct SpaceCursor<'a> { + data: &'a mut [u8], + allocated_length: usize, +} + +impl<'a> SpaceCursor<'a> { + /// Create a new [`SpaceCursor`] from a given mutable byte buffer. + pub fn new(data: &'a mut [u8]) -> Self { + Self { + data, + allocated_length: 0, + } + } +} + +impl<'a> SpaceAllocator for SpaceCursor<'a> { + #[inline] + fn allocate_and_split( + &mut self, + size: usize, + ) -> Result { + let allocated_length = self.allocated_length; + let data_len = self.data.len(); + let (previous, allocatable) = self.data.split_at_mut(allocated_length); + + let allocated = allocatable + .get_mut(..size) + .ok_or(AtomWriteError::OutOfSpace { + used: allocated_length, + capacity: data_len, + requested: size, + })?; + + self.allocated_length = self + .allocated_length + .checked_add(size) + .expect("Allocation overflow"); + + Ok(SpaceWriterSplitAllocation { + previous, + allocated, + }) + } + + #[inline] + unsafe fn rewind(&mut self, byte_count: usize) -> Result<(), AtomWriteError> { + if self.allocated_length < byte_count { + return Err(AtomWriteError::RewindBeyondAllocated { + requested: byte_count, + allocated: self.allocated_length, + }); + } + + self.allocated_length -= byte_count; + + Ok(()) + } + + #[inline] + fn allocated_bytes(&self) -> &[u8] { + &self.data[..self.allocated_length] + } + + #[inline] + unsafe fn allocated_bytes_mut(&mut self) -> &mut [u8] { + &mut self.data[..self.allocated_length] + } + + #[inline] + fn remaining_bytes(&self) -> &[u8] { + &self.data[self.allocated_length..] + } +} diff --git a/atom/src/space/error.rs b/atom/src/space/error.rs new file mode 100644 index 00000000..7246bbf4 --- /dev/null +++ b/atom/src/space/error.rs @@ -0,0 +1,309 @@ +//! Errors related to the alignment, reading or writing of Atoms. + +use std::error::Error; +use std::fmt::{Display, Formatter}; +use urid::{Uri, URID}; + +/// A Helper struct to store data about a type for alignment error messages +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct TypeData { + name: &'static str, + size: usize, + align: usize, +} + +impl TypeData { + pub(crate) fn of() -> Self { + Self { + name: core::any::type_name::(), + size: core::mem::size_of::(), + align: core::mem::align_of::(), + } + } +} + +impl Display for TypeData { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} (size: {}, align: {})", + self.name, self.size, self.align + ) + } +} + +/// The actual, currently private, alignment error +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub(crate) enum AlignmentErrorInner { + CannotComputeAlignment { + type_id: TypeData, + ptr: *const u8, + }, + UnalignedBuffer { + type_id: TypeData, + ptr: *const u8, + }, + NotEnoughSpaceToRealign { + type_id: TypeData, + ptr: *const u8, + required_padding: usize, + available_size: usize, + }, +} + +/// An alignment error, returned by [`AlignedSpace`](crate::space::AlignedSpace). +/// +/// This error occurs when a byte buffer is unaligned, or could not be aligned. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct AlignmentError(pub(crate) AlignmentErrorInner); + +impl From for AtomWriteError { + #[inline] + fn from(error: AlignmentError) -> Self { + AtomWriteError::AlignmentError(error) + } +} + +impl From for AtomReadError { + #[inline] + fn from(error: AlignmentError) -> Self { + AtomReadError::AlignmentError(error) + } +} + +impl Display for AlignmentError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.0 { + AlignmentErrorInner::CannotComputeAlignment { type_id, ptr } => { + write!(f, "Could not compute alignment for pointer {:?} while trying to align it for type {}", ptr, type_id) + } + AlignmentErrorInner::UnalignedBuffer { type_id, ptr } => { + write!( + f, + "Pointer {:?} is not properly aligned for type {}", + ptr, type_id + ) + } + AlignmentErrorInner::NotEnoughSpaceToRealign { + type_id, + ptr, + required_padding, + available_size, + } => { + write!(f, "Not enough space to realign pointer {:?} for type {} (needs {} padding bytes, but only {} bytes are available)", + ptr, type_id, required_padding, available_size) + } + } + } +} + +impl Error for AlignmentError {} + +/// Errors that can occur while writing atoms to a byte buffer. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum AtomWriteError { + /// A write operation could not proceed because there is not enough space in the allocatable buffer. + OutOfSpace { + /// The amount currently used in the buffer, in bytes. + used: usize, + /// The total capacity of the buffer, in bytes. + capacity: usize, + /// The requested amount of bytes to be allocated in the buffer, in bytes. + /// + /// If this error occurred, most likely this is higher than the remaining amount of bytes available. + requested: usize, + }, + /// An allocator tried to be rewound beyond the amount of already allocated bytes + RewindBeyondAllocated { + /// The amount of already allocated bytes + allocated: usize, + /// The amount of bytes requested to be rewound + /// + /// If this error occurred, most likely this is higher than the amount of allocated bytes + requested: usize, + }, + /// A write operation tried to occur outside of the buffer's bounds + WritingOutOfBounds { + /// The amount of available bytes in the buffer + available: usize, + /// The requested amount of bytes + requested: usize, + }, + /// An error generated by a given atom type while writing + IllegalOperation { + /// The type URI of the atom that raised the error + writing_type_uri: &'static Uri, + /// An user-friendly error message + error_message: &'static str, + }, + /// An alignment error occurred when attempting to write to the underlying buffer. + AlignmentError(AlignmentError), +} + +impl Display for AtomWriteError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AtomWriteError::OutOfSpace { + used, + capacity, + requested, + } => { + write!(f, "Failed to allocate {} bytes: buffer is too small (used: {} / {} bytes, remaining: {} bytes", + requested, used, capacity, capacity - used) + } + AtomWriteError::RewindBeyondAllocated { + allocated, + requested, + } => { + write!(f, "Attempted to rewind {} bytes before the start of buffer (buffer position: {}, requested a rewind of {} bytes", + requested - allocated, allocated, requested) + } + AtomWriteError::WritingOutOfBounds { + requested, + available, + } => { + write!(f, "Attempted to write {} bytes past the end of buffer (buffer size: {}, requested write of {} bytes", + requested - available, available, requested) + } + AtomWriteError::IllegalOperation { + writing_type_uri, + error_message, + } => { + write!( + f, + "Illegal operation when trying to write Atom of type {}: {}", + writing_type_uri.to_string_lossy(), + error_message + ) + } + AtomWriteError::AlignmentError(e) => Display::fmt(e, f), + } + } +} + +impl Error for AtomWriteError {} + +/// Errors that can occur while writing atoms to a byte buffer. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum AtomReadError { + /// The URID of the atom being currently read does not match the requested type's URID. + AtomUridMismatch { + /// The full URI of the requested type + expected_uri: &'static Uri, + /// The URID of the requested type + expected_urid: URID, + /// The atom's actual URID + found_urid: URID, + }, + /// The URID of the atom being currently read is not actually a valid URID (e.g. zero). + InvalidUrid { + /// The full URI of the requested type + expected_uri: &'static Uri, + /// The URID of the requested type + expected_urid: URID, + /// The value found in the Atom's URID header. + found_urid: u32, + }, + /// A read operation tried to occur outside of the buffer's bounds + ReadingOutOfBounds { + /// The amount of available bytes in the buffer + available: usize, + /// The requested amount of bytes + requested: usize, + }, + /// The read Atom value was invalid for the given Atom type. + InvalidAtomValue { + /// The Atom type being read + reading_type_uri: &'static Uri, + /// The Atom-specific error message + error_message: &'static str, + }, + /// An alignment error curred when trying to read from the underlying buffer. + AlignmentError(AlignmentError), +} + +impl Display for AtomReadError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AtomReadError::AtomUridMismatch { + expected_uri, + expected_urid, + found_urid, + } => { + write!( + f, + "Mismatched Atom URID for Type '{}': Expected URID #{}, found #{}", + expected_uri.to_string_lossy(), + expected_urid.get(), + found_urid.get() + ) + } + AtomReadError::InvalidUrid { + expected_uri, + expected_urid, + found_urid, + } => { + write!(f, "Found invalid URID value ({}) while trying to read Atom URID Type {} (URID #{})", + found_urid, expected_uri.to_string_lossy(), expected_urid.get()) + } + AtomReadError::ReadingOutOfBounds { + available, + requested, + } => { + write!(f, "Attempted to read {} bytes past the end of buffer (buffer size: {}, requested write of {} bytes", + requested - available, available, requested) + } + AtomReadError::InvalidAtomValue { + reading_type_uri, + error_message, + } => { + write!( + f, + "Invalid Atom value for type {}: {}", + reading_type_uri.to_string_lossy(), + error_message + ) + } + AtomReadError::AlignmentError(e) => Display::fmt(e, f), + } + } +} + +impl Error for AtomReadError {} + +/// The global atom error type, that encompasses both read and write errors, for convenience. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum AtomError { + /// An error occurred when trying to read an atom from a buffer. + ReadError(AtomReadError), + /// An error occurred when trying to write an atom into a buffer. + WriteError(AtomWriteError), +} + +impl Display for AtomError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AtomError::ReadError(e) => write!(f, "Could not read atom data: {}", e), + AtomError::WriteError(e) => write!(f, "Could not write atom data: {}", e), + } + } +} + +impl Error for AtomError {} + +impl From for AtomError { + #[inline] + fn from(error: AtomReadError) -> Self { + AtomError::ReadError(error) + } +} + +impl From for AtomError { + #[inline] + fn from(error: AtomWriteError) -> Self { + AtomError::WriteError(error) + } +} diff --git a/atom/src/space/reader.rs b/atom/src/space/reader.rs new file mode 100644 index 00000000..118d083c --- /dev/null +++ b/atom/src/space/reader.rs @@ -0,0 +1,224 @@ +use crate::atom_prelude::*; +use std::mem::MaybeUninit; + +/// A cursor-like struct to help read contiguous memory regions for atoms. +#[derive(Clone)] +pub struct SpaceReader<'a> { + space: &'a [u8], +} + +#[inline] +fn split_space( + space: &AlignedSpace, + bytes: usize, +) -> Result<(&AlignedSpace, &[u8]), AtomReadError> { + space + .split_at(bytes) + .ok_or(AtomReadError::ReadingOutOfBounds { + requested: bytes, + available: space.bytes_len(), + }) +} + +impl<'a> SpaceReader<'a> { + /// Creates a new reader from a byte slice. + #[inline] + pub fn new(space: &'a [u8]) -> Self { + SpaceReader { space } + } + + #[inline] + fn next_uninit_value(&mut self) -> Result<&'a MaybeUninit, AtomReadError> { + let space = AlignedSpace::align_from_bytes(self.space)?; + let value_size = ::core::mem::size_of::(); + let (value, remaining) = split_space(space, value_size)?; + + self.space = remaining; + + // PANIC: We just split_at the right amount of bytes for a value of T, there should be enough space + Ok(value + .as_uninit_slice() + .get(0) + .expect("Not enough space for an uninit value")) + } + + #[inline] + fn next_uninit_value_slice( + &mut self, + length: usize, + ) -> Result<&'a [MaybeUninit], AtomReadError> { + let space = AlignedSpace::align_from_bytes(self.space)?; + + let split_point = crate::util::value_index_to_byte_index::(length); + let (data, remaining) = split_space(space, split_point)?; + + self.space = remaining; + + Ok(data.as_uninit_slice()) + } + + #[inline] + fn as_uninit_slice(&self) -> Result<&'a [MaybeUninit], AlignmentError> { + let space = AlignedSpace::align_from_bytes(self.space)?; + Ok(space.as_uninit_slice()) + } + + /// Returns the remaining bytes as a slice of type a given type `T`. + /// + /// # Errors + /// + /// This methods returns an error if the slice couldn't get correctly aligned for the type `T`. + /// + /// # Safety + /// + /// The caller of this method has to ensure the buffer is filled with properly initialized + /// values of type `T`. + #[inline] + pub unsafe fn as_slice(&self) -> Result<&'a [T], AlignmentError> { + self.as_uninit_slice() + .map(|s| crate::util::assume_init_slice(s)) + } + + /// Returns the next remaining bytes as a slice of type a given type `T` and length. + /// + /// # Errors + /// + /// This methods returns an error if the slice couldn't get correctly aligned for the type `T`, + /// or if `length` is out of bounds. + /// + /// # Safety + /// + /// The caller of this method has to ensure the requested slice is filled with properly + /// initialized values of type `T`. + #[inline] + pub unsafe fn next_values( + &mut self, + length: usize, + ) -> Result<&'a [T], AtomReadError> { + self.next_uninit_value_slice(length) + .map(|s| crate::util::assume_init_slice(s)) + } + + /// Returns the next `length` bytes. + /// + /// # Errors + /// + /// Returns an error if `length` is out of bounds. + #[inline] + pub fn next_bytes(&mut self, length: usize) -> Result<&'a [u8], AtomReadError> { + let bytes = self + .space + .get(..length) + .ok_or_else(|| AtomReadError::ReadingOutOfBounds { + requested: length, + available: self.space.len(), + })?; + + self.space = self.space.get(length..).unwrap_or(&[]); + + Ok(bytes) + } + + /// Returns the next value as a given type `T`. + /// + /// # Errors + /// + /// Returns an error if the value is too big for the remaining buffer, or if the buffer cannot + /// be aligned to match the value's alignment requirements. + /// + /// # Safety + /// + /// The caller is responsible to ensure that a properly initialized value of type `T` is present. + #[inline] + pub unsafe fn next_value(&mut self) -> Result<&'a T, AtomReadError> { + self.next_uninit_value() + .map(|v| crate::util::assume_init_ref(v)) + } + + /// Returns the next atom. + /// + /// This method reads the next atom header, and then returns both the header and the associated + /// body as an [`UnidentifiedAtom`]. + /// + /// # Errors + /// + /// Returns an error if the value is too big for the remaining buffer, if the buffer cannot + /// be aligned to match the value's alignment requirements, or if the atom's body side is out + /// of bounds. + /// + /// # Safety + /// + /// The caller is responsible to ensure that a properly initialized atom is present. + #[inline] + pub unsafe fn next_atom(&mut self) -> Result<&'a UnidentifiedAtom, AtomReadError> { + let space = AlignedSpace::::align_from_bytes(self.space)?; + let header = space + .assume_init_slice() + .get(0) + .ok_or(AtomReadError::ReadingOutOfBounds { + available: space.bytes_len(), + requested: core::mem::size_of::(), + })?; + let (_, rest) = split_space(space, header.size_of_atom())?; + + let atom = UnidentifiedAtom::from_header(header); + self.space = rest; + + Ok(atom) + } + + /// Returns the currently remaining underlying bytes. + #[inline] + pub fn remaining_bytes(&self) -> &'a [u8] { + self.space + } + + /// Performs a given reading operation, but only advance the cursor if the operation is successful. + /// + /// # Errors + /// + /// Returns whichever errors the given operation handler returned. + #[inline] + pub fn try_read(&mut self, read_handler: F) -> Result + where + F: FnOnce(&mut Self) -> Result, + { + let mut reader = self.clone(); + let value = read_handler(&mut reader)?; + self.space = reader.remaining_bytes(); + + Ok(value) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::space::AlignedVec; + use std::mem::{size_of, size_of_val}; + use urid::URID; + + #[test] + fn test_read_atom() { + let mut space = AlignedVec::::new_with_capacity(64); + let space = space.as_space_mut(); + let urid: URID = unsafe { URID::new_unchecked(17) }; + + // Writing an integer atom. + unsafe { + *(space.as_bytes_mut().as_mut_ptr() as *mut sys::LV2_Atom_Int) = sys::LV2_Atom_Int { + atom: sys::LV2_Atom { + size: size_of::() as u32, + type_: urid.get(), + }, + body: 42, + }; + + let atom = space.read().next_atom().unwrap(); + let body = atom.body().as_bytes(); + + assert_eq!(size_of::(), size_of_val(body)); + assert_eq!(42, *(body.as_ptr() as *const i32)); + } + } +} diff --git a/atom/src/space/terminated.rs b/atom/src/space/terminated.rs new file mode 100644 index 00000000..d8427d5d --- /dev/null +++ b/atom/src/space/terminated.rs @@ -0,0 +1,92 @@ +use crate::space::error::AtomWriteError; +use crate::space::{SpaceAllocator, SpaceWriterSplitAllocation}; + +/// An helper space writer, that wraps an existing writer and makes sure all writes are +/// terminated with a given terminator byte. +/// +/// Further writes overwrite the added terminator byte, and append a new one. +/// +/// This helper is useful to implement Atom writer for null-terminated strings, for instance. +/// +/// # Example +/// +/// ``` +/// use lv2_atom::space::{SpaceCursor, SpaceWriter, SpaceAllocator, Terminated}; +/// let mut buffer = [0; 20]; +/// // Our underlying allocator +/// let cursor = SpaceCursor::new(&mut buffer); +/// +/// let mut writer = Terminated::new(cursor, 0x42); // Alternative: use cursor.terminated(). +/// +/// writer.write_bytes(b"Hello, world!").unwrap(); +/// assert_eq!(writer.allocated_bytes(), b"Hello, world!\x42"); +/// +/// writer.write_bytes(b" Boop!").unwrap(); +/// assert_eq!(&buffer, b"Hello, world! Boop!\x42"); +/// +/// ``` +pub struct Terminated { + inner: W, + terminator: u8, + wrote_terminator_byte: bool, +} + +impl Terminated { + /// Creates a new Terminated writer, from an inner writer and a given terminator byte. + pub fn new(inner: W, terminator: u8) -> Self { + Self { + inner, + terminator, + wrote_terminator_byte: false, + } + } + + /// Unwraps the `Terminated` helper and returns the underlying allocator. + pub fn into_inner(self) -> W { + self.inner + } +} + +impl SpaceAllocator for Terminated { + fn allocate_and_split( + &mut self, + size: usize, + ) -> Result { + if self.wrote_terminator_byte { + // SAFETY: We checked we already wrote the terminator byte, and it is safe to be overwritten + unsafe { self.inner.rewind(1)? }; + } + + let SpaceWriterSplitAllocation { + previous, + allocated, + } = self.inner.allocate_and_split(size + 1)?; + allocated[size] = self.terminator; + self.wrote_terminator_byte = true; + + Ok(SpaceWriterSplitAllocation { + previous, + allocated: &mut allocated[..size], + }) + } + + #[inline] + unsafe fn rewind(&mut self, byte_count: usize) -> Result<(), AtomWriteError> { + self.inner.rewind(byte_count) + } + + #[inline] + fn allocated_bytes(&self) -> &[u8] { + self.inner.allocated_bytes() + } + + #[inline] + unsafe fn allocated_bytes_mut(&mut self) -> &mut [u8] { + self.inner.allocated_bytes_mut() + } + + #[inline] + fn remaining_bytes(&self) -> &[u8] { + self.inner.remaining_bytes() + } +} diff --git a/atom/src/space/vec.rs b/atom/src/space/vec.rs new file mode 100644 index 00000000..643387a6 --- /dev/null +++ b/atom/src/space/vec.rs @@ -0,0 +1,257 @@ +#![deny(unsafe_code)] + +use crate::space::error::AtomWriteError; +use crate::space::{AlignedSpace, SpaceAllocator, SpaceWriterSplitAllocation}; +use std::mem::MaybeUninit; +use std::ops::Range; + +/// A heap-allocated growable byte buffer with the alignment of a type `T`. +/// +/// This type is useful to create heap-allocated [`AlignedSpace`s](crate::space::AlignedSpace), i.e. heap-allocated +/// aligned byte buffers, to be used for e.g. safely writing properly-aligned atoms. +/// +/// # Example +/// +/// ``` +/// # use lv2_atom::space::{AlignedVec, SpaceWriter}; +/// use lv2_atom::AtomHeader; +/// +/// let mut buffer = AlignedVec::::new_with_capacity(64); +/// +/// // This buffer is always aligned! +/// assert_eq!(buffer.as_bytes().as_ptr() as usize % core::mem::align_of::(), 0); +/// +/// // We can now safely write atoms into it. +/// let mut cursor = buffer.write(); +/// // ... +/// # core::mem::drop(cursor) +/// ``` +pub struct AlignedVec { + inner: Vec>, +} + +impl Default for AlignedVec { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Clone for AlignedVec { + #[inline] + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl AlignedVec { + /// Creates a new, empty buffer. + #[inline] + pub fn new() -> Self { + Self { inner: Vec::new() } + } + + /// Creates a new buffer, with an internal capacity a given amount of `T` items. + /// + /// Note that `capacity` is a number of `T` items, *not* a size in bytes. + #[inline] + pub fn new_with_capacity(capacity: usize) -> Self { + Self { + inner: vec![MaybeUninit::zeroed(); capacity], + } + } + + /// Resizes the buffer to a new internal capacity a given amount of `T` items. + /// + /// Note that `capacity` is a number of `T` items, *not* a size in bytes. + #[inline] + pub fn resize(&mut self, new_len: usize) { + self.inner.resize(new_len, MaybeUninit::zeroed()) + } + + /// Returns the contents of the buffer as an aligned byte slice. + #[inline] + pub fn as_space(&self) -> &AlignedSpace { + AlignedSpace::from_uninit_slice(&self.inner) + } + + /// Returns the contents of the buffer as a mutable aligned byte slice. + #[inline] + pub fn as_space_mut(&mut self) -> &mut AlignedSpace { + AlignedSpace::from_uninit_slice_mut(&mut self.inner) + } + + /// Returns the contents of the buffer as a byte slice. + #[inline] + pub fn as_bytes(&self) -> &[u8] { + self.as_space().as_bytes() + } + + /// Returns the contents of the buffer as a mutable byte slice. + #[inline] + pub fn as_bytes_mut(&mut self) -> &mut [u8] { + self.as_space_mut().as_bytes_mut() + } + + #[inline] + fn reallocate_bytes_mut( + &mut self, + byte_range: Range, + ) -> Result { + let byte_len = self.inner.len() * std::mem::size_of::(); + let max = byte_range.start.max(byte_range.end); + + if max > byte_len { + let new_size = crate::util::byte_index_to_value_index::(max); + self.inner.resize(new_size, MaybeUninit::zeroed()); + } + + let bytes = self.as_bytes_mut(); + + // PANIC: We just resized to the accommodate the maximum value in the given range. + let (previous, allocatable) = bytes.split_at_mut(byte_range.start); + + Ok(SpaceWriterSplitAllocation { + previous, + allocated: &mut allocatable[..byte_range.end - byte_range.start], + }) + } + + /// Returns a new writer to write into the contents of the buffer. + /// + /// Unlike other [`SpaceWriter`](crate::space::SpaceWriter) implementations, this cursor grows the underlying + /// [`AlignedVec`] buffer if it runs out of space, instead of failing. + #[inline] + pub fn write(&mut self) -> AlignedVecCursor { + AlignedVecCursor { + vec: self, + allocated_length: 0, + } + } + + ///! Converts the aligned buffer into an [`AlignedSpace`](crate::space::AlignedSpace). + /// + /// This is similar in spirit to [`Vec::into_boxed_slice`], but keeps the alignment invariants + /// enforced. + #[inline] + pub fn into_boxed_space(self) -> Box> { + AlignedSpace::from_boxed_uninit_slice(self.inner.into_boxed_slice()) + } + + /// Converts the aligned buffer into the inner `Vec>`. + #[inline] + pub fn into_vec(self) -> Vec> { + self.inner + } + + /// Creates a new aligned buffer by wrapping an existing `Vec>`. + #[inline] + pub fn from_vec(vec: Vec>) -> Self { + Self { inner: vec } + } +} + +/// A lightweight [`SpaceWriter`](crate::space::SpaceWriter) that writes into a growable byte buffer (backed by [`AlignedVec`]) using a cursor. +/// +/// Unlike other [`SpaceWriter`](crate::space::SpaceWriter) implementations, this cursor grows the underlying +/// [`AlignedVec`] buffer if it runs out of space, instead of failing. +/// +/// This cursor is obtained through the [`AlignedVec::write`] method. +pub struct AlignedVecCursor<'vec, T> { + vec: &'vec mut AlignedVec, + allocated_length: usize, +} + +impl<'vec, T: Copy + 'static> SpaceAllocator for AlignedVecCursor<'vec, T> { + fn allocate_and_split( + &mut self, + size: usize, + ) -> Result { + let end = self + .allocated_length + .checked_add(size) + .expect("Allocation overflow"); + + let result = AlignedVec::::reallocate_bytes_mut(self.vec, self.allocated_length..end); + + if result.is_ok() { + self.allocated_length = end; + } + + result + } + + #[inline] + #[allow(unsafe_code)] + unsafe fn rewind(&mut self, byte_count: usize) -> Result<(), AtomWriteError> { + if self.allocated_length < byte_count { + return Err(AtomWriteError::RewindBeyondAllocated { + requested: byte_count, + allocated: self.allocated_length, + }); + } + + self.allocated_length -= byte_count; + + Ok(()) + } + + #[inline] + fn allocated_bytes(&self) -> &[u8] { + &self.vec.as_bytes()[..self.allocated_length] + } + + #[inline] + #[allow(unsafe_code)] + unsafe fn allocated_bytes_mut(&mut self) -> &mut [u8] { + &mut self.vec.as_bytes_mut()[..self.allocated_length] + } + + #[inline] + fn remaining_bytes(&self) -> &[u8] { + self.vec + .as_bytes() + .get(self.allocated_length..) + .unwrap_or(&[]) + } +} + +#[cfg(test)] +mod tests { + use crate::space::{AlignedVec, SpaceWriter}; + use crate::AtomHeader; + + #[test] + pub fn test_lifetimes() { + let mut buffer = AlignedVec::::new_with_capacity(16); + + { + let mut cursor = buffer.write(); + let buf1 = cursor.allocate(2).unwrap(); + buf1[0] = 5 + } + + let _other_cursor = buffer.write(); + let _other_cursor2 = buffer.write(); + } + + #[test] + pub fn test_alignment() { + fn aligned_vec() { + let space = AlignedVec::::new_with_capacity(4); + assert_eq!( + space.as_bytes().as_ptr() as usize % ::core::mem::align_of::(), + 0 + ); + } + + // Testing with some random types with different alignments + aligned_vec::(); + aligned_vec::(); + aligned_vec::(); + aligned_vec::(); + aligned_vec::(); + } +} diff --git a/atom/src/string.rs b/atom/src/string.rs deleted file mode 100644 index 1c9b39c2..00000000 --- a/atom/src/string.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! String handling atoms. -//! -//! This module contains two different atoms: The [`String`](struct.String.html) and the [`Literal`](struct.Literal.html). The former is for simple, non-localized UTF-8 strings, like URIs or paths, and the later is either for localized text, e.g. descriptions in the user interface, or RDF literals. -//! -//! Reading and writing these atoms is pretty simple: They don't require a parameter and return a either a `&str` or the literal info and a `&str`. Writing is done with a writing handle which can append strings to the string/literal. When dropped, the handle will append the null character, you therefore don't have to handle it on your own. -//! -//! # Example -//! ``` -//! use lv2_core::prelude::*; -//! use lv2_atom::prelude::*; -//! use lv2_atom::string::StringWriter; -//! -//! #[derive(PortCollection)] -//! struct MyPorts { -//! input: InputPort, -//! output: OutputPort, -//! } -//! -//! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { -//! let input: &str = ports.input.read(urids.string, ()).unwrap(); -//! let mut writer: StringWriter = ports.output.init(urids.string, ()).unwrap(); -//! writer.append(input).unwrap(); -//! } -//! ``` -//! -//! # Specifications -//! -//! [http://lv2plug.in/ns/ext/atom/atom.html#String](http://lv2plug.in/ns/ext/atom/atom.html#String) -//! [http://lv2plug.in/ns/ext/atom/atom.html#Literal](http://lv2plug.in/ns/ext/atom/atom.html#Literal) -use crate::prelude::*; -use crate::space::*; -use urid::*; - -/// An atom containing either a localized string or an RDF literal. -/// -/// [See also the module documentation.](index.html) -pub struct Literal; - -unsafe impl UriBound for Literal { - const URI: &'static [u8] = sys::LV2_ATOM__Literal; -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -/// The type or language URID of a literal. -pub enum LiteralInfo { - Language(URID), - Datatype(URID), -} - -impl<'a, 'b> Atom<'a, 'b> for Literal -where - 'a: 'b, -{ - type ReadParameter = (); - type ReadHandle = (LiteralInfo, &'a str); - type WriteParameter = LiteralInfo; - type WriteHandle = StringWriter<'a, 'b>; - - fn read(body: Space<'a>, _: ()) -> Option<(LiteralInfo, &'a str)> { - let (header, body) = body.split_type::()?; - let info = if header.lang != 0 && header.datatype == 0 { - LiteralInfo::Language(URID::new(header.lang)?) - } else if header.lang == 0 && header.datatype != 0 { - LiteralInfo::Datatype(URID::new(header.datatype)?) - } else { - return None; - }; - let data = body.data()?; - std::str::from_utf8(&data[0..data.len() - 1]) - .or_else(|error| std::str::from_utf8(&data[0..error.valid_up_to()])) - .ok() - .map(|string| (info, string)) - } - - fn init(mut frame: FramedMutSpace<'a, 'b>, info: LiteralInfo) -> Option> { - (&mut frame as &mut dyn MutSpace).write( - &match info { - LiteralInfo::Language(lang) => sys::LV2_Atom_Literal_Body { - lang: lang.get(), - datatype: 0, - }, - LiteralInfo::Datatype(datatype) => sys::LV2_Atom_Literal_Body { - lang: 0, - datatype: datatype.get(), - }, - }, - true, - )?; - Some(StringWriter { frame }) - } -} - -/// An atom containing a UTF-8 encoded string. -/// -/// [See also the module documentation.](index.html) -pub struct String; - -unsafe impl UriBound for String { - const URI: &'static [u8] = sys::LV2_ATOM__String; -} - -impl<'a, 'b> Atom<'a, 'b> for String -where - 'a: 'b, -{ - type ReadParameter = (); - type ReadHandle = &'a str; - type WriteParameter = (); - type WriteHandle = StringWriter<'a, 'b>; - - fn read(body: Space<'a>, _: ()) -> Option<&'a str> { - body.data() - .and_then(|data| std::str::from_utf8(data).ok()) - .map(|string| &string[..string.len() - 1]) // removing the null-terminator - } - - fn init(frame: FramedMutSpace<'a, 'b>, _: ()) -> Option> { - Some(StringWriter { frame }) - } -} - -/// Handle to append strings to a string or literal. -pub struct StringWriter<'a, 'b> { - frame: FramedMutSpace<'a, 'b>, -} - -impl<'a, 'b> StringWriter<'a, 'b> { - /// Append a string. - /// - /// This method copies the given string to the end of the string atom/literal and then returns a mutable reference to the copy. - /// - /// If the internal space for the atom is not big enough, this method returns `None`. - pub fn append(&mut self, string: &str) -> Option<&mut str> { - let data = string.as_bytes(); - let space = self.frame.write_raw(data, false)?; - unsafe { Some(std::str::from_utf8_unchecked_mut(space)) } - } -} - -impl<'a, 'b> Drop for StringWriter<'a, 'b> { - fn drop(&mut self) { - // Null terminator. - (&mut self.frame as &mut dyn MutSpace).write(&0u8, false); - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - use crate::space::*; - use std::ffi::CStr; - use std::mem::{size_of, size_of_val}; - use urid::*; - - struct German; - unsafe impl UriBound for German { - const URI: &'static [u8] = b"http://lexvo.org/id/iso639-1/de\0"; - } - - #[derive(URIDCollection)] - pub struct TestURIDs { - atom: AtomURIDCollection, - german: URID, - } - - const SAMPLE0: &str = "Da steh ich nun, ich armer Tor! "; - const SAMPLE1: &str = "Und bin so klug als wie zuvor;"; - - #[test] - fn test_literal() { - let map = HashURIDMapper::new(); - let urids = TestURIDs::from_map(&map).unwrap(); - - let mut raw_space: Box<[u8]> = Box::new([0; 256]); - - // writing - { - let mut space = RootMutSpace::new(raw_space.as_mut()); - let mut writer = (&mut space as &mut dyn MutSpace) - .init( - urids.atom.literal, - LiteralInfo::Language(urids.german.into_general()), - ) - .unwrap(); - writer.append(SAMPLE0).unwrap(); - writer.append(SAMPLE1).unwrap(); - } - - // verifying - { - let (atom, space) = raw_space.split_at(size_of::()); - - let literal = unsafe { &*(atom.as_ptr() as *const sys::LV2_Atom_Literal) }; - assert_eq!(literal.atom.type_, urids.atom.literal.get()); - assert_eq!( - literal.atom.size as usize, - size_of::() - + size_of_val(SAMPLE0) - + size_of_val(SAMPLE1) - + 1 - ); - assert_eq!(literal.body.lang, urids.german.get()); - assert_eq!(literal.body.datatype, 0); - - let size = literal.atom.size as usize - size_of::(); - let string = CStr::from_bytes_with_nul(space.split_at(size).0) - .unwrap() - .to_str() - .unwrap(); - assert_eq!(SAMPLE0.to_owned() + SAMPLE1, string); - } - - // reading - { - let space = Space::from_slice(raw_space.as_ref()); - let (body, _) = space.split_atom_body(urids.atom.literal).unwrap(); - let (info, text) = Literal::read(body, ()).unwrap(); - - assert_eq!(info, LiteralInfo::Language(urids.german.into_general())); - assert_eq!(text, SAMPLE0.to_owned() + SAMPLE1); - } - } - - #[test] - fn test_string() { - let map = HashURIDMapper::new(); - let urids = crate::AtomURIDCollection::from_map(&map).unwrap(); - - let mut raw_space: Box<[u8]> = Box::new([0; 256]); - - // writing - { - let mut space = RootMutSpace::new(raw_space.as_mut()); - let mut writer = (&mut space as &mut dyn MutSpace) - .init(urids.string, ()) - .unwrap(); - writer.append(SAMPLE0).unwrap(); - writer.append(SAMPLE1).unwrap(); - } - - // verifying - { - let (string, space) = raw_space.split_at(size_of::()); - - let string = unsafe { &*(string.as_ptr() as *const sys::LV2_Atom_String) }; - assert_eq!(string.atom.type_, urids.string); - assert_eq!(string.atom.size as usize, SAMPLE0.len() + SAMPLE1.len() + 1); - - let string = std::str::from_utf8(space.split_at(string.atom.size as usize).0).unwrap(); - assert_eq!(string[..string.len() - 1], SAMPLE0.to_owned() + SAMPLE1); - } - - // reading - { - let space = Space::from_slice(raw_space.as_ref()); - let (body, _) = space.split_atom_body(urids.string).unwrap(); - let string = String::read(body, ()).unwrap(); - assert_eq!(string, SAMPLE0.to_owned() + SAMPLE1); - } - } -} diff --git a/atom/src/tuple.rs b/atom/src/tuple.rs deleted file mode 100644 index 001c0be8..00000000 --- a/atom/src/tuple.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! An atom containg a series of other atoms. -//! -//! This atom is just like a [sequence](../sequence/index.html), only without time stamps: It contains multiple arbitrary atoms which you can either iterate through or write in sequence. -//! -//! # Example -//! ``` -//! use lv2_core::prelude::*; -//! use lv2_atom::prelude::*; -//! use lv2_atom::tuple::{TupleIterator, TupleWriter}; -//! -//! #[derive(PortCollection)] -//! struct MyPorts { -//! input: InputPort, -//! output: OutputPort, -//! } -//! -//! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { -//! let input: TupleIterator = ports.input.read(urids.tuple, ()).unwrap(); -//! let mut output: TupleWriter = ports.output.init(urids.tuple, ()).unwrap(); -//! for atom in input { -//! if let Some(integer) = atom.read(urids.int, ()) { -//! output.init(urids.int, integer * 2).unwrap(); -//! } else { -//! output.init(urids.int, -1).unwrap(); -//! } -//! } -//! } -//! ``` -//! -//! # Specification -//! -//! [http://lv2plug.in/ns/ext/atom/atom.html#Tuple](http://lv2plug.in/ns/ext/atom/atom.html#Tuple) -use crate::space::*; -use crate::*; -use urid::*; - -/// An atom containing a series of other atoms. -/// -/// [See also the module documentation.](index.html) -pub struct Tuple; - -unsafe impl UriBound for Tuple { - const URI: &'static [u8] = sys::LV2_ATOM__Tuple; -} - -impl<'a, 'b> Atom<'a, 'b> for Tuple -where - 'a: 'b, -{ - type ReadParameter = (); - type ReadHandle = TupleIterator<'a>; - type WriteParameter = (); - type WriteHandle = TupleWriter<'a, 'b>; - - fn read(body: Space<'a>, _: ()) -> Option> { - Some(TupleIterator { space: body }) - } - - fn init(frame: FramedMutSpace<'a, 'b>, _: ()) -> Option> { - Some(TupleWriter { frame }) - } -} - -/// An iterator over all atoms in a tuple. -/// -/// The item of this iterator is simply the space a single atom occupies. -pub struct TupleIterator<'a> { - space: Space<'a>, -} - -impl<'a> Iterator for TupleIterator<'a> { - type Item = UnidentifiedAtom<'a>; - - fn next(&mut self) -> Option> { - let (atom, space) = self.space.split_atom()?; - self.space = space; - Some(UnidentifiedAtom::new(atom)) - } -} - -/// The writing handle to add atoms to a tuple. -pub struct TupleWriter<'a, 'b> { - frame: FramedMutSpace<'a, 'b>, -} - -impl<'a, 'b> TupleWriter<'a, 'b> { - /// Initialize a new tuple element. - pub fn init<'c, A: Atom<'a, 'c>>( - &'c mut self, - child_urid: URID, - child_parameter: A::WriteParameter, - ) -> Option { - (&mut self.frame as &mut dyn MutSpace).init(child_urid, child_parameter) - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - use crate::space::*; - use std::mem::size_of; - use urid::*; - - #[test] - fn test_tuple() { - let map = HashURIDMapper::new(); - let urids = crate::AtomURIDCollection::from_map(&map).unwrap(); - - let mut raw_space: Box<[u8]> = Box::new([0; 256]); - - // writing - { - let mut space = RootMutSpace::new(raw_space.as_mut()); - let mut writer = (&mut space as &mut dyn MutSpace) - .init(urids.tuple, ()) - .unwrap(); - { - let mut vector_writer = - writer.init::>(urids.vector, urids.int).unwrap(); - vector_writer.append(&[17; 9]).unwrap(); - } - writer.init::(urids.int, 42).unwrap(); - } - - // verifying - { - let (atom, space) = raw_space.split_at(size_of::()); - let atom = unsafe { &*(atom.as_ptr() as *const sys::LV2_Atom) }; - assert_eq!(atom.type_, urids.tuple); - assert_eq!( - atom.size as usize, - size_of::() - + size_of::() * 9 - + 4 - + size_of::() - ); - - let (vector, space) = space.split_at(size_of::()); - let vector = unsafe { &*(vector.as_ptr() as *const sys::LV2_Atom_Vector) }; - assert_eq!(vector.atom.type_, urids.vector); - assert_eq!( - vector.atom.size as usize, - size_of::() + size_of::() * 9 - ); - assert_eq!(vector.body.child_size as usize, size_of::()); - assert_eq!(vector.body.child_type, urids.int); - - let (vector_items, space) = space.split_at(size_of::() * 9); - let vector_items = - unsafe { std::slice::from_raw_parts(vector_items.as_ptr() as *const i32, 9) }; - assert_eq!(vector_items, &[17; 9]); - let (_, space) = space.split_at(4); - - let (int, _) = space.split_at(size_of::()); - let int = unsafe { &*(int.as_ptr() as *const sys::LV2_Atom_Int) }; - assert_eq!(int.atom.type_, urids.int); - assert_eq!(int.atom.size as usize, size_of::()); - assert_eq!(int.body, 42); - } - - // reading - { - let space = Space::from_slice(raw_space.as_ref()); - let (body, _) = space.split_atom_body(urids.tuple).unwrap(); - let items: Vec = Tuple::read(body, ()).unwrap().collect(); - assert_eq!(items[0].read(urids.vector, urids.int).unwrap(), [17; 9]); - assert_eq!(items[1].read(urids.int, ()).unwrap(), 42); - } - } -} diff --git a/atom/src/unidentified.rs b/atom/src/unidentified.rs new file mode 100644 index 00000000..10b294bf --- /dev/null +++ b/atom/src/unidentified.rs @@ -0,0 +1,147 @@ +use crate::space::error::{AtomReadError, AtomWriteError}; +use crate::space::AtomSpace; +use crate::{Atom, AtomHandle, AtomHeader}; +use urid::URID; + +/// An atom of yet unknown type. +/// +/// This is used by reading handles that have to return a reference to an atom, but cannot check its type. +/// This struct contains a `Space` containing the header and the body of the atom and can identify/read the atom from it. +#[repr(C)] +pub struct UnidentifiedAtom { + header: AtomHeader, +} + +impl UnidentifiedAtom { + /// Construct a new unidentified atom. + /// + /// # Errors + /// This methods returns a read error if the given space is too small to accommodate the atom header. + /// + /// # Safety + /// + /// The caller has to ensure that the given space actually contains both a valid atom header, and a valid corresponding atom body. + #[inline] + pub unsafe fn from_space(space: &AtomSpace) -> Result<&Self, AtomReadError> { + Ok(Self::from_header(space.read().next_value()?)) + } + + /// Construct a new unidentified atom. + /// + /// # Errors + /// + /// This method will return an error if the atom's header is out of bounds of the given buffer. + /// + /// # Safety + /// + /// The caller has to ensure that the given space actually contains both a valid atom header, and a valid corresponding atom body. + #[inline] + pub unsafe fn from_space_mut(space: &mut AtomSpace) -> Result<&mut Self, AtomWriteError> { + let available = space.bytes_len(); + + Ok(Self::from_header_mut( + space + .assume_init_slice_mut() + .get_mut(0) + .ok_or(AtomWriteError::WritingOutOfBounds { + available, + requested: ::core::mem::size_of::(), + })?, + )) + } + + #[inline] + pub(crate) unsafe fn from_header(header: &AtomHeader) -> &Self { + // SAFETY: UnidentifiedAtom is repr(C) and has AtomHeader as its only field, so transmuting between the two is safe. + &*(header as *const _ as *const _) + } + + #[inline] + pub(crate) unsafe fn from_header_mut(header: &mut AtomHeader) -> &mut Self { + // SAFETY: UnidentifiedAtom is repr(C) and has AtomHeader as its only field, so transmuting between the two is safe. + &mut *(header as *mut _ as *mut _) + } + + /// Try to read the atom as being of a given type. + /// + /// If the atom was identified, a reading handle is returned. + /// + /// # Errors + /// + /// This method will return an error if the atom's type does not match the given URID. + /// + /// An error will also be returned if the atom's header is out of bounds, or if any other + /// read error occurs. + pub fn read( + &self, + urid: URID, + ) -> Result<::Handle, AtomReadError> { + self.header.check_urid(urid)?; + + // SAFETY: the fact that this contains a valid instance of A is checked above. + unsafe { A::read(self.body()) } + } + + #[inline] + pub fn header(&self) -> &AtomHeader { + &self.header + } + + #[inline] + fn body_bytes(&self) -> &[u8] { + if self.header.size_of_body() == 0 { + &[] + } else { + // SAFETY: This type's constructor ensures the atom's body is valid + // The edge case of an empty body is also checked above. + let ptr = unsafe { (self as *const UnidentifiedAtom).add(1) }; + + // SAFETY: This type's constructor ensures the atom's body is valid + unsafe { ::core::slice::from_raw_parts(ptr.cast(), self.header.size_of_body()) } + } + } + + #[inline] + fn body_bytes_mut(&mut self) -> &mut [u8] { + if self.header.size_of_body() == 0 { + &mut [] + } else { + // SAFETY: This type's constructor ensures the atom's body is valid + // The edge case of an empty body is also checked above. + let ptr = unsafe { (self as *mut UnidentifiedAtom).add(1) }; + + // SAFETY: This type's constructor ensures the atom's body is valid + unsafe { ::core::slice::from_raw_parts_mut(ptr.cast(), self.header.size_of_body()) } + } + } + + #[inline] + pub fn atom_space(&self) -> &AtomSpace { + let ptr = self as *const UnidentifiedAtom as *const u8; + let bytes = unsafe { ::core::slice::from_raw_parts(ptr, self.header.size_of_atom()) }; + + // SAFETY: the bytes are necessarily aligned, since they point to the aligned AtomHeader + unsafe { AtomSpace::from_bytes_unchecked(bytes) } + } + + #[inline] + pub fn atom_space_mut(&mut self) -> &mut AtomSpace { + let ptr = self as *mut UnidentifiedAtom as *mut u8; + let bytes = unsafe { ::core::slice::from_raw_parts_mut(ptr, self.header.size_of_atom()) }; + + // SAFETY: the bytes are necessarily aligned, since they point to the aligned AtomHeader + unsafe { AtomSpace::from_bytes_mut_unchecked(bytes) } + } + + #[inline] + pub fn body(&self) -> &AtomSpace { + // SAFETY: the bytes are necessarily aligned, since they are right after the aligned AtomHeader + unsafe { AtomSpace::from_bytes_unchecked(self.body_bytes()) } + } + + #[inline] + pub fn body_mut(&mut self) -> &mut AtomSpace { + // SAFETY: the bytes are necessarily aligned, since they are right after the aligned AtomHeader + unsafe { AtomSpace::from_bytes_mut_unchecked(self.body_bytes_mut()) } + } +} diff --git a/atom/src/util.rs b/atom/src/util.rs new file mode 100644 index 00000000..623220be --- /dev/null +++ b/atom/src/util.rs @@ -0,0 +1,63 @@ +use crate::space::error::{AlignmentError, AlignmentErrorInner, TypeData}; +use std::mem::MaybeUninit; + +// This function is separate to ensure proper lifetimes +#[inline] +pub(crate) unsafe fn assume_init_ref(s: &MaybeUninit) -> &T { + // SAFETY: the caller must guarantee that `self` is initialized. + &*s.as_ptr() +} + +// This function is separate to ensure proper lifetimes +#[inline] +pub(crate) unsafe fn assume_init_mut(s: &mut MaybeUninit) -> &mut T { + // SAFETY: the caller must guarantee that `self` is initialized. + &mut *s.as_mut_ptr() +} + +#[inline] +pub(crate) unsafe fn assume_init_slice(slice: &[MaybeUninit]) -> &[T] { + &*(slice as *const _ as *const [T]) +} + +#[inline] +pub(crate) unsafe fn assume_init_slice_mut(slice: &mut [MaybeUninit]) -> &mut [T] { + &mut *(slice as *mut _ as *mut [T]) +} + +#[inline] +pub(crate) fn write_uninit(uninit: &mut MaybeUninit, value: T) -> &mut T { + *uninit = MaybeUninit::new(value); + // SAFETY: we just wrote the value, therefore it is initialized now + unsafe { assume_init_mut(uninit) } +} + +#[inline] +pub(crate) fn value_index_to_byte_index(size: usize) -> usize { + size * ::core::mem::size_of::() +} + +#[inline] +pub(crate) fn byte_index_to_value_index(size: usize) -> usize { + let type_size = ::core::mem::size_of::(); + if type_size == 0 { + 0 + } else { + size / type_size + if size % type_size > 0 { 1 } else { 0 } + } +} + +#[inline] +pub(crate) fn try_padding_for(data: &[u8]) -> Result { + let value = data.as_ptr().align_offset(::core::mem::align_of::()); + if value == usize::MAX { + Err(AlignmentError( + AlignmentErrorInner::CannotComputeAlignment { + type_id: TypeData::of::(), + ptr: data.as_ptr(), + }, + )) + } else { + Ok(value) + } +} diff --git a/atom/src/vector.rs b/atom/src/vector.rs deleted file mode 100644 index 10643fd2..00000000 --- a/atom/src/vector.rs +++ /dev/null @@ -1,200 +0,0 @@ -//! An atom containg an array of scalar atom bodies. -//! -//! This atom is able to handle arrays (aka slices) of the internal types of scalar atoms. -//! -//! Reading a vector requires the URID fo the scalar that's been used and the reading process fails if the vector does not contain the requested scalar atom. The return value of the reading process is a slice of the internal type. -//! -//! Writing a vector is done with a writer that appends slices to the atom. -//! -//! # Example -//! ``` -//! use lv2_core::prelude::*; -//! use lv2_atom::prelude::*; -//! use lv2_atom::vector::VectorWriter; -//! -//! #[derive(PortCollection)] -//! struct MyPorts { -//! input: InputPort, -//! output: OutputPort, -//! } -//! -//! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) { -//! let input: &[i32] = ports.input.read(urids.vector(), urids.int).unwrap(); -//! let mut output: VectorWriter = ports.output.init(urids.vector(), urids.int).unwrap(); -//! output.append(input).unwrap(); -//! } -//! ``` -//! -//! You may note that, unlike other atoms, the vector's URID is retrieved by calling the `vector` method. This is because two vectors with a different item type are considered two different types, and therefore would have the different URIDs. In reality, however, all vectors have the same URID and the `vector` method returns it with the fitting type. -//! -//! # Specification -//! -//! [http://lv2plug.in/ns/ext/atom/atom.html#Vector](http://lv2plug.in/ns/ext/atom/atom.html#Vector) -use crate::scalar::ScalarAtom; -use crate::space::*; -use crate::*; -use std::marker::PhantomData; -use std::mem::size_of; -use urid::*; - -/// An atom containg an array of scalar atom bodies. -/// -/// [See also the module documentation.](index.html) -pub struct Vector { - child: PhantomData, -} - -unsafe impl UriBound for Vector { - const URI: &'static [u8] = sys::LV2_ATOM__Vector; -} - -impl<'a, 'b, C: ScalarAtom> Atom<'a, 'b> for Vector -where - 'a: 'b, - C: 'b, -{ - type ReadParameter = URID; - type ReadHandle = &'a [C::InternalType]; - type WriteParameter = URID; - type WriteHandle = VectorWriter<'a, 'b, C>; - - fn read(body: Space<'a>, child_urid: URID) -> Option<&'a [C::InternalType]> { - let (header, body) = body.split_type::()?; - - if header.child_type != child_urid - || header.child_size as usize != size_of::() - { - return None; - } - - let data = body.data()?; - - assert_eq!(data.len() % size_of::(), 0); - let children_count = data.len() / size_of::(); - - let children = unsafe { - std::slice::from_raw_parts(data.as_ptr() as *const C::InternalType, children_count) - }; - Some(children) - } - - fn init( - mut frame: FramedMutSpace<'a, 'b>, - child_urid: URID, - ) -> Option> { - let body = sys::LV2_Atom_Vector_Body { - child_type: child_urid.get(), - child_size: size_of::() as u32, - }; - (&mut frame as &mut dyn MutSpace).write(&body, false)?; - - Some(VectorWriter { - frame, - type_: PhantomData, - }) - } -} - -/// Handle to append elements to a vector. -/// -/// This works by allocating a slice of memory behind the vector and then writing your data to it. -pub struct VectorWriter<'a, 'b, A: ScalarAtom> { - frame: FramedMutSpace<'a, 'b>, - type_: PhantomData, -} - -impl<'a, 'b, A: ScalarAtom> VectorWriter<'a, 'b, A> { - /// Push a single value to the vector. - pub fn push(&mut self, child: A::InternalType) -> Option<&mut A::InternalType> { - (&mut self.frame as &mut dyn MutSpace).write(&child, false) - } - - /// Append a slice of undefined memory to the vector. - /// - /// Using this method, you don't need to have the elements in memory before you can write them. - pub fn allocate(&mut self, size: usize) -> Option<&mut [A::InternalType]> { - self.frame - .allocate(size_of::() * size, false) - .map(|(_, data)| unsafe { - std::slice::from_raw_parts_mut(data.as_mut_ptr() as *mut A::InternalType, size) - }) - } - - /// Append multiple elements to the vector. - pub fn append(&mut self, data: &[A::InternalType]) -> Option<&mut [A::InternalType]> { - let raw_data = unsafe { - std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) - }; - self.frame - .allocate(raw_data.len(), false) - .map(|(_, space)| unsafe { - space.copy_from_slice(raw_data); - std::slice::from_raw_parts_mut( - space.as_mut_ptr() as *mut A::InternalType, - data.len(), - ) - }) - } -} - -#[cfg(test)] -mod tests { - use crate::prelude::*; - use crate::space::*; - use std::mem::size_of; - use urid::*; - - #[test] - fn test_vector() { - const CHILD_COUNT: usize = 17; - - let map = HashURIDMapper::new(); - let urids = crate::AtomURIDCollection::from_map(&map).unwrap(); - - let mut raw_space: Box<[u8]> = Box::new([0; 256]); - - // writing - { - let mut space = RootMutSpace::new(raw_space.as_mut()); - let mut writer = (&mut space as &mut dyn MutSpace) - .init(urids.vector(), urids.int) - .unwrap(); - writer.append(&[42; CHILD_COUNT - 1]); - writer.push(1); - } - - // verifying - { - let (vector, children) = raw_space.split_at(size_of::()); - - let vector = unsafe { &*(vector.as_ptr() as *const sys::LV2_Atom_Vector) }; - assert_eq!(vector.atom.type_, urids.vector.get()); - assert_eq!( - vector.atom.size as usize, - size_of::() + size_of::() * CHILD_COUNT - ); - assert_eq!(vector.body.child_size as usize, size_of::()); - assert_eq!(vector.body.child_type, urids.int.get()); - - let children = - unsafe { std::slice::from_raw_parts(children.as_ptr() as *const i32, CHILD_COUNT) }; - for value in &children[0..children.len() - 1] { - assert_eq!(*value, 42); - } - assert_eq!(children[children.len() - 1], 1); - } - - // reading - { - let space = Space::from_slice(raw_space.as_ref()); - let (body, _) = space.split_atom_body(urids.vector).unwrap(); - let children: &[i32] = Vector::::read(body, urids.int).unwrap(); - - assert_eq!(children.len(), CHILD_COUNT); - for i in 0..children.len() - 1 { - assert_eq!(children[i], 42); - } - assert_eq!(children[children.len() - 1], 1); - } - } -} diff --git a/atom/tests/atom_integration.rs b/atom/tests/atom_integration.rs index 1b47fbb0..9f6e6e0e 100644 --- a/atom/tests/atom_integration.rs +++ b/atom/tests/atom_integration.rs @@ -4,6 +4,7 @@ extern crate lv2_sys as sys; extern crate lv2_units as units; use atom::prelude::*; +use atom::AtomHeader; use core::prelude::*; use lv2_urid::*; use units::prelude::*; @@ -45,24 +46,28 @@ impl Plugin for AtomPlugin { fn run(&mut self, ports: &mut Ports, _: &mut (), _: u32) { let sequence_reader = ports .input - .read::(self.urids.atom.sequence, self.urids.units.beat) + .read(self.urids.atom.sequence) + .unwrap() + .with_unit(self.urids.units.frame) .unwrap(); + let mut sequence_writer = ports .output - .init::( - self.urids.atom.sequence, - TimeStampURID::Frames(self.urids.units.frame), - ) + .write(self.urids.atom.sequence) + .unwrap() + .with_unit(self.urids.units.frame) .unwrap(); for (time_stamp, atom) in sequence_reader { - match atom.read(self.urids.atom.int, ()) { - Some(number) => { + match atom.read(self.urids.atom.int) { + Ok(number) => { sequence_writer - .init::(time_stamp, self.urids.atom.int, number * 2) + .new_event(time_stamp, self.urids.atom.int) + .unwrap() + .set(number * 2) .unwrap(); } - None => { + Err(_) => { sequence_writer.forward(time_stamp, atom).unwrap(); } } @@ -98,34 +103,40 @@ fn main() { let urids: URIDs = map.populate_collection().unwrap(); // Preparing the input atom. - let mut input_atom_space: Box<[u8]> = Box::new([0; 256]); + let mut input_atom_space = AlignedVec::::new_with_capacity(64); + let input_atom_space = input_atom_space.as_space_mut(); { - let mut space = RootMutSpace::new(input_atom_space.as_mut()); - let mut writer = (&mut space as &mut dyn MutSpace) - .init( - urids.atom.sequence, - TimeStampURID::Frames(urids.units.frame), - ) - .unwrap(); - writer - .init(TimeStamp::Frames(0), urids.atom.int, 42) - .unwrap(); - writer - .init(TimeStamp::Frames(1), urids.atom.long, 17) + let mut space = SpaceCursor::new(input_atom_space.as_bytes_mut()); + let mut writer = space + .write_atom(urids.atom.sequence) + .unwrap() + .with_unit(urids.units.frame) .unwrap(); + { + let _ = writer + .new_event(0, urids.atom.int) + .unwrap() + .set(42) + .unwrap(); + } writer - .init(TimeStamp::Frames(2), urids.atom.int, 3) + .new_event(1, urids.atom.long) + .unwrap() + .set(17) .unwrap(); + + writer.new_event(2, urids.atom.int).unwrap().set(3).unwrap(); } // preparing the output atom. - let mut output_atom_space: Box<[u8]> = Box::new([0; 256]); + let mut output_atom_space = AlignedVec::::new_with_capacity(64); + let output_atom_space = output_atom_space.as_space_mut(); { - let mut space = RootMutSpace::new(output_atom_space.as_mut()); - (&mut space as &mut dyn MutSpace) - .init(urids.atom.chunk, ()) + let mut space = SpaceCursor::new(output_atom_space.as_bytes_mut()); + space + .write_atom(urids.atom.chunk) .unwrap() - .allocate(256 - size_of::(), false) + .allocate(256 - size_of::()) .unwrap(); } @@ -149,12 +160,12 @@ fn main() { (plugin_descriptor.connect_port.unwrap())( plugin, 0, - input_atom_space.as_mut_ptr() as *mut c_void, + input_atom_space.as_bytes_mut().as_mut_ptr() as *mut c_void, ); (plugin_descriptor.connect_port.unwrap())( plugin, 1, - output_atom_space.as_mut_ptr() as *mut c_void, + output_atom_space.as_bytes_mut().as_mut_ptr() as *mut c_void, ); // Activate, run, deactivate. @@ -166,16 +177,24 @@ fn main() { (plugin_descriptor.cleanup.unwrap())(plugin); } + let chunk = unsafe { output_atom_space.read().next_atom() } + .unwrap() + .read(urids.atom.chunk) + .unwrap(); + // Asserting the result - let (sequence, _) = Space::from_slice(output_atom_space.as_ref()) - .split_atom_body(urids.atom.sequence) + let sequence = unsafe { chunk.read().next_atom() } + .unwrap() + .read(urids.atom.sequence) + .unwrap() + .with_unit(urids.units.frame) .unwrap(); - for (stamp, atom) in Sequence::read(sequence, urids.units.beat).unwrap() { - let stamp = stamp.as_frames().unwrap(); + + for (stamp, atom) in sequence { match stamp { - 0 => assert_eq!(atom.read(urids.atom.int, ()).unwrap(), 84), - 1 => assert_eq!(atom.read(urids.atom.long, ()).unwrap(), 17), - 2 => assert_eq!(atom.read(urids.atom.int, ()).unwrap(), 6), + 0 => assert_eq!(*atom.read(urids.atom.int).unwrap(), 84), + 1 => assert_eq!(*atom.read(urids.atom.long).unwrap(), 17), + 2 => assert_eq!(*atom.read(urids.atom.int).unwrap(), 6), _ => panic!("Invalid time stamp in sequence!"), } } diff --git a/core/Cargo.toml b/core/Cargo.toml index 9f733552..96a6465e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,6 +14,9 @@ travis-ci = { repository = "RustAudio/rust-lv2", branch = "master" } maintenance = { status = "passively-maintained" } [dependencies] -urid = "0.1.0" +urid = { version = "0.1.0", default-features = false } lv2-sys = "2.0.0" -lv2-core-derive = "2.1.0" +lv2-core-derive = { version = "2.1.0", optional = true } + +[features] +default = ["lv2-core-derive"] diff --git a/core/src/feature/mod.rs b/core/src/feature/mod.rs index cbdcbc64..3871d1eb 100644 --- a/core/src/feature/mod.rs +++ b/core/src/feature/mod.rs @@ -152,10 +152,10 @@ mod tests { struct FeatureTestSetting<'a> { pub data_a: Pin>, - pub feature_a_sys: Pin>, + pub _feature_a_sys: Pin>, pub data_b: Pin>, - pub feature_b_sys: Pin>, - pub feature_c_sys: Pin>, + pub _feature_b_sys: Pin>, + pub _feature_c_sys: Pin>, pub features_cache: FeatureCache<'a>, } @@ -190,10 +190,10 @@ mod tests { Self { data_a, - feature_a_sys, + _feature_a_sys: feature_a_sys, data_b, - feature_b_sys, - feature_c_sys, + _feature_b_sys: feature_b_sys, + _feature_c_sys: feature_c_sys, features_cache, } } diff --git a/core/src/plugin/mod.rs b/core/src/plugin/mod.rs index a1509cec..05e7caf9 100644 --- a/core/src/plugin/mod.rs +++ b/core/src/plugin/mod.rs @@ -2,6 +2,8 @@ pub(crate) mod info; pub use info::PluginInfo; + +#[cfg(feature = "lv2-core-derive")] pub use lv2_core_derive::*; use crate::feature::*; diff --git a/core/src/port.rs b/core/src/port.rs index 76c8a3f1..0c7c16e6 100644 --- a/core/src/port.rs +++ b/core/src/port.rs @@ -13,6 +13,7 @@ use std::ffi::c_void; use std::ops::{Deref, DerefMut}; use std::ptr::NonNull; +#[cfg(feature = "lv2-core-derive")] pub use lv2_core_derive::*; /// Generalization of port types. diff --git a/core/src/prelude.rs b/core/src/prelude.rs index 65402383..eb9322c7 100644 --- a/core/src/prelude.rs +++ b/core/src/prelude.rs @@ -2,8 +2,10 @@ pub use crate::extension::ExtensionDescriptor; pub use crate::feature::{FeatureCache, FeatureCollection, MissingFeatureError, ThreadingClass}; pub use crate::match_extensions; -pub use crate::plugin::{ - lv2_descriptors, Plugin, PluginInfo, PluginInstance, PluginInstanceDescriptor, PortCollection, -}; +pub use crate::plugin::{Plugin, PluginInfo, PluginInstance, PluginInstanceDescriptor}; + +#[cfg(feature = "lv2-core-derive")] +pub use crate::plugin::{lv2_descriptors, PortCollection}; + pub use crate::port::*; pub use crate::sys::LV2_Descriptor; diff --git a/docs/fifths/src/lib.rs b/docs/fifths/src/lib.rs index 7e963c5d..9e165d3e 100644 --- a/docs/fifths/src/lib.rs +++ b/docs/fifths/src/lib.rs @@ -42,24 +42,25 @@ impl Plugin for Fifths { // Get the reading handle of the input sequence. let input_sequence = ports .input - .read(self.urids.atom.sequence, self.urids.unit.beat) + .read(self.urids.atom.sequence) + .unwrap() + .with_unit(self.urids.unit.frame) .unwrap(); // Initialise the output sequence and get the writing handle. let mut output_sequence = ports .output - .init( - self.urids.atom.sequence, - TimeStampURID::Frames(self.urids.unit.frame), - ) + .write(self.urids.atom.sequence) + .unwrap() + .with_unit(self.urids.unit.frame) .unwrap(); for (timestamp, atom) in input_sequence { - // Every message is forwarded, regardless of it's content. - output_sequence.forward(timestamp, atom); + // Every message is forwarded, regardless of its contents. + output_sequence.forward(timestamp, atom).unwrap(); // Retrieve the message. - let message = if let Some(message) = atom.read(self.urids.midi.wmidi, ()) { + let message = if let Ok(message) = atom.read(self.urids.midi.wmidi) { message } else { continue; @@ -71,11 +72,9 @@ impl Plugin for Fifths { if let Ok(note) = note.step(7) { // Write the fifth. Writing is done after initialization. output_sequence - .init( - timestamp, - self.urids.midi.wmidi, - MidiMessage::NoteOn(channel, note, velocity), - ) + .new_event(timestamp, self.urids.midi.wmidi) + .unwrap() + .set(MidiMessage::NoteOn(channel, note, velocity)) .unwrap(); } } @@ -83,11 +82,9 @@ impl Plugin for Fifths { // Do the same thing for `NoteOff`. if let Ok(note) = note.step(7) { output_sequence - .init( - timestamp, - self.urids.midi.wmidi, - MidiMessage::NoteOff(channel, note, velocity), - ) + .new_event(timestamp, self.urids.midi.wmidi) + .unwrap() + .set(MidiMessage::NoteOff(channel, note, velocity)) .unwrap(); } } diff --git a/docs/metro/src/lib.rs b/docs/metro/src/lib.rs index 582bed0e..d781ec51 100644 --- a/docs/metro/src/lib.rs +++ b/docs/metro/src/lib.rs @@ -75,15 +75,15 @@ impl Plugin for Metro { } fn run(&mut self, ports: &mut Ports, _: &mut (), _: u32) { - if let Some(control) = ports + if let Ok(control) = ports .control - .read(self.urids.atom.sequence, self.urids.unit.beat) + .read(self.urids.atom.sequence) + .and_then(|s| s.with_unit(self.urids.unit.beat)) { // Here, the final assembly of the pipeline is done. First, the event iterator is pre-processed to only emit an index and an `UnidentifiedAtom`. Then, the event iterator is wrapped into an `EventAtomizer`, which is then connected to an `EventReader` and the envelope. The resulting pipe consumes a `()` and emits the next frame of the envelope; It's already a compact pipeline. // // Then, the final pipeline is constructed using some lazy pipes: The first one splits a `()` to a tuple of `()`, which is then connected to a tuple of the envelope and the pre-constructed sampler. A tuple of two pipes is also a pipe; The two pipes are processed in parallel. Then, the emitted envelope and sample frame are multiplied to one frame. - let control = - control.map(|(timestamp, event)| (timestamp.as_frames().unwrap() as usize, event)); + let control = control.map(|(timestamp, event)| (timestamp as usize, event)); let complete_envelope = EventAtomizer::new(control).compose() >> EventReader::new(&self.urids.atom, &self.urids.time) diff --git a/docs/metro/src/pipes.rs b/docs/metro/src/pipes.rs index f9597b40..f51fd252 100644 --- a/docs/metro/src/pipes.rs +++ b/docs/metro/src/pipes.rs @@ -43,7 +43,7 @@ where fn test_sampler() { let sample: Vec = vec![1, 2, 3, 4]; let mut sampler = Sampler::new(sample); - for i in (0..32).chain(32..0) { + for i in (0..32).chain((0..32).rev()) { assert_eq!((i % 4 + 1) as u8, sampler.next(i)); } } @@ -312,10 +312,10 @@ impl<'a> EventReader<'a> { } impl<'a> Pipe for EventReader<'a> { - type InputItem = Option>; + type InputItem = Option<&'a UnidentifiedAtom>; type OutputItem = PulseInput; - fn next(&mut self, atom: Option) -> PulseInput { + fn next(&mut self, atom: Option<&'a UnidentifiedAtom>) -> PulseInput { let mut updates = PulseInput { beat_update: None, bpm_update: None, @@ -323,22 +323,24 @@ impl<'a> Pipe for EventReader<'a> { }; if let Some(atom) = atom { - if let Some((object_header, object_reader)) = atom - .read(self.atom_urids.object, ()) - .or_else(|| atom.read(self.atom_urids.blank, ())) + if let Ok((object_header, object_reader)) = atom + .read(self.atom_urids.object) + .or_else(|_| atom.read(self.atom_urids.blank)) { if object_header.otype == self.time_urids.position_class { for (property_header, property) in object_reader { if property_header.key == self.time_urids.bar_beat { updates.beat_update = property - .read(self.atom_urids.float, ()) - .map(|float| float as f64); + .read(self.atom_urids.float) + .map(|float| *float as f64) + .ok(); } if property_header.key == self.time_urids.beats_per_minute { - updates.bpm_update = property.read(self.atom_urids.float, ()); + updates.bpm_update = property.read(self.atom_urids.float).ok().copied(); } if property_header.key == self.time_urids.speed { - updates.speed_update = property.read(self.atom_urids.float, ()); + updates.speed_update = + property.read(self.atom_urids.float).ok().copied(); } } } diff --git a/docs/midigate/src/lib.rs b/docs/midigate/src/lib.rs index feb33897..13d8141a 100644 --- a/docs/midigate/src/lib.rs +++ b/docs/midigate/src/lib.rs @@ -30,9 +30,15 @@ pub struct Midigate { impl Midigate { // A function to write a chunk of output, to be called from `run()`. If the gate is high, then the input will be passed through for this chunk, otherwise silence is written. - fn write_output(&mut self, ports: &mut Ports, offset: usize, mut len: usize) { - if ports.input.len() < offset + len { - len = ports.input.len() - offset; + fn write_output( + &mut self, + input: &InputPort, - parameter: A::WriteParameter, - ) -> Result { + ) -> Result<>::Handle, StateErr> { if !self.initialized { self.initialized = true; - (&mut self.head as &mut dyn MutSpace) - .init(urid, parameter) - .ok_or(StateErr::Unknown) + self.cursor.write_atom(urid).map_err(|_| StateErr::Unknown) } else { Err(StateErr::Unknown) } @@ -182,7 +173,10 @@ impl<'a> RetrieveHandle<'a> { return Err(StateErr::NoProperty); }; - Ok(StatePropertyReader::new(type_, Space::from_slice(space))) + Ok(StatePropertyReader::new( + type_, + AlignedSpace::from_bytes(space).map_err(|_| StateErr::BadData)?, + )) } } @@ -191,12 +185,12 @@ impl<'a> RetrieveHandle<'a> { /// This handle contains the type and the data of a property retrieved from the [`RetrieveHandle`](struct.RetrieveHandle.html). pub struct StatePropertyReader<'a> { type_: URID, - body: Space<'a>, + body: &'a AtomSpace, } impl<'a> StatePropertyReader<'a> { /// Create a new reading handle with the given type and data. - pub fn new(type_: URID, body: Space<'a>) -> Self { + pub fn new(type_: URID, body: &'a AtomSpace) -> Self { Self { type_: type_.into_general(), body, @@ -209,7 +203,7 @@ impl<'a> StatePropertyReader<'a> { } /// Return the data of the property. - pub fn body(&self) -> Space { + pub fn body(&self) -> &AtomSpace { self.body } @@ -218,13 +212,12 @@ impl<'a> StatePropertyReader<'a> { /// This works like any atom reader: You pass the URID of the atom type as well as the type-specific argument, and if the desired type is the actual type of the data, a read handle is returned. /// /// If the desired and actual data types don't match, `Err(StateErr::BadType)` is returned. - pub fn read>( + pub fn read( &self, urid: URID, - parameter: A::ReadParameter, - ) -> Result { + ) -> Result<>::Handle, StateErr> { if urid == self.type_ { - A::read(self.body, parameter).ok_or(StateErr::Unknown) + unsafe { A::read(self.body) }.map_err(|_| StateErr::Unknown) } else { Err(StateErr::BadType) } @@ -235,31 +228,41 @@ impl<'a> StatePropertyReader<'a> { mod tests { use crate::raw::*; use crate::storage::Storage; - use atom::space::Space; + use atom::space::AlignedSpace; fn store(storage: &mut Storage, urids: &AtomURIDCollection) { let mut store_handle = storage.store_handle(); store_handle .draft(URID::new(1).unwrap()) - .init(urids.int, 17) + .init(urids.int) + .unwrap() + .set(17) .unwrap(); store_handle .draft(URID::new(2).unwrap()) - .init(urids.float, 1.0) + .init(urids.float) + .unwrap() + .set(1.0) .unwrap(); store_handle.commit(URID::new(1).unwrap()).unwrap().unwrap(); let mut vector_writer = store_handle.draft(URID::new(3).unwrap()); - let mut vector_writer = vector_writer.init(urids.vector(), urids.int).unwrap(); + let mut vector_writer = vector_writer + .init(urids.vector) + .unwrap() + .of_type(urids.int) + .unwrap(); vector_writer.append(&[1, 2, 3, 4]).unwrap(); store_handle.commit_all().unwrap(); store_handle .draft(URID::new(4).unwrap()) - .init(urids.int, 0) + .init(urids.int) + .unwrap() + .set(0) .unwrap(); } @@ -268,26 +271,29 @@ mod tests { assert_eq!( 17, - retrieve_handle + *retrieve_handle .retrieve(URID::new(1).unwrap()) .unwrap() - .read(urids.int, ()) + .read(urids.int) .unwrap() ); assert_eq!( - 1.0, + 1.0f32.to_ne_bytes(), retrieve_handle .retrieve(URID::new(2).unwrap()) .unwrap() - .read(urids.float, ()) + .read(urids.float) .unwrap() + .to_ne_bytes() ); assert_eq!( [1, 2, 3, 4], retrieve_handle .retrieve(URID::new(3).unwrap()) .unwrap() - .read(urids.vector(), urids.int) + .read(urids.vector) + .unwrap() + .of_type(urids.int) .unwrap() ); assert!(retrieve_handle.retrieve(URID::new(4).unwrap()).is_err()); @@ -310,14 +316,18 @@ mod tests { } 2 => { assert_eq!(urids.float, *type_); - assert_eq!(1.0, unsafe { - *(value.as_slice() as *const _ as *const f32) - }); + assert_eq!( + 1.0f32.to_ne_bytes(), + unsafe { *(value.as_slice() as *const _ as *const f32) }.to_ne_bytes() + ); } 3 => { - assert_eq!(urids.vector::(), *type_); - let space = Space::from_slice(value.as_slice()); - let data = Vector::read(space, urids.int).unwrap(); + assert_eq!(urids.vector, *type_); + let space = AlignedSpace::from_bytes(value.as_slice()).unwrap(); + let data = unsafe { Vector::read(space) } + .unwrap() + .of_type(urids.int) + .unwrap(); assert_eq!([1, 2, 3, 4], data); } _ => panic!("Invalid key!"), diff --git a/state/tests/integration.rs b/state/tests/integration.rs index b34f11dc..45a5c1df 100644 --- a/state/tests/integration.rs +++ b/state/tests/integration.rs @@ -52,23 +52,30 @@ impl State for Stateful { fn save(&self, mut store: StoreHandle, _: ()) -> Result<(), StateErr> { store .draft(URID::new(1000).unwrap()) - .init(self.urids.float, self.internal)?; + .init(self.urids.float)? + .set(self.internal) + .map_err(|_| StateErr::Unknown)?; store .draft(URID::new(1001).unwrap()) - .init(self.urids.vector(), self.urids.float)? - .append(self.audio.as_ref()); + .init(self.urids.vector)? + .of_type(self.urids.float) + .map_err(|_| StateErr::Unknown)? + .append(self.audio.as_ref()) + .unwrap(); store.commit_all() } fn restore(&mut self, store: RetrieveHandle, _: ()) -> Result<(), StateErr> { - self.internal = store + self.internal = *store .retrieve(URID::new(1000).unwrap())? - .read(self.urids.float, ())?; + .read(self.urids.float)?; self.audio = Vec::from( store .retrieve(URID::new(1001).unwrap())? - .read(self.urids.vector(), self.urids.float)?, + .read(self.urids.vector)? + .of_type(self.urids.float) + .map_err(|_| StateErr::BadData)?, ); Ok(()) } @@ -89,12 +96,12 @@ fn create_plugin(mapper: Pin<&mut HostMap>) -> Stateful { // Constructing the plugin. Stateful::new( &PluginInfo::new(Stateful::uri(), Path::new("./"), 44100.0), - &mut Features { map: map }, + &mut Features { map }, ) .unwrap() }; - assert_eq!(42.0, plugin.internal); + assert_eq!(42.0f32.to_ne_bytes(), plugin.internal.to_ne_bytes()); assert_eq!(0, plugin.audio.len()); plugin @@ -113,14 +120,12 @@ fn test_save_n_restore() { .unwrap(); (extension.save.unwrap(), extension.restore.unwrap()) }; - assert!(store_fn == StateDescriptor::::extern_save); - assert!(restore_fn == StateDescriptor::::extern_restore); let mut first_plugin = create_plugin(mapper.as_mut()); first_plugin.run(&mut (), &mut (), 32); - assert_eq!(17.0, first_plugin.internal); + assert_eq!(17.0f32.to_ne_bytes(), first_plugin.internal.to_ne_bytes()); assert_eq!(32, first_plugin.audio.len()); unsafe { @@ -145,6 +150,6 @@ fn test_save_n_restore() { ) }; - assert_eq!(17.0, second_plugin.internal); + assert_eq!(17.0f32.to_ne_bytes(), second_plugin.internal.to_ne_bytes()); assert_eq!(32, second_plugin.audio.len()); } diff --git a/sys/src/lib.rs b/sys/src/lib.rs index d9851981..be7051b4 100644 --- a/sys/src/lib.rs +++ b/sys/src/lib.rs @@ -5,6 +5,7 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(clippy::all)] +#![allow(rustdoc::bare_urls)] #[cfg_attr(target_os = "linux", path = "linux/mod.rs")] #[cfg_attr(target_os = "windows", path = "windows.rs")] diff --git a/sys/src/linux/aarch64.rs b/sys/src/linux/aarch64.rs index 22581d71..7f395a95 100644 --- a/sys/src/linux/aarch64.rs +++ b/sys/src/linux/aarch64.rs @@ -685,7 +685,7 @@ pub type __int32_t = ::std::os::raw::c_int; pub type __uint32_t = ::std::os::raw::c_uint; pub type __int64_t = ::std::os::raw::c_long; #[doc = " The header of an atom:Atom."] -#[repr(C)] +#[repr(C, align(8))] #[derive(Debug, Copy, Clone)] pub struct LV2_Atom { #[doc = "< Size in bytes, not including type and size."] diff --git a/sys/src/linux/arm.rs b/sys/src/linux/arm.rs index 8d6d0966..b74ab391 100644 --- a/sys/src/linux/arm.rs +++ b/sys/src/linux/arm.rs @@ -685,7 +685,7 @@ pub type __int32_t = ::std::os::raw::c_int; pub type __uint32_t = ::std::os::raw::c_uint; pub type __int64_t = ::std::os::raw::c_longlong; #[doc = " The header of an atom:Atom."] -#[repr(C)] +#[repr(C, align(8))] #[derive(Debug, Copy, Clone)] pub struct LV2_Atom { #[doc = "< Size in bytes, not including type and size."] diff --git a/sys/src/linux/x86.rs b/sys/src/linux/x86.rs index ca89369b..328e7b06 100644 --- a/sys/src/linux/x86.rs +++ b/sys/src/linux/x86.rs @@ -685,7 +685,7 @@ pub type __int32_t = ::std::os::raw::c_int; pub type __uint32_t = ::std::os::raw::c_uint; pub type __int64_t = ::std::os::raw::c_longlong; #[doc = " The header of an atom:Atom."] -#[repr(C)] +#[repr(C, align(8))] #[derive(Debug, Copy, Clone)] pub struct LV2_Atom { #[doc = "< Size in bytes, not including type and size."] diff --git a/sys/src/linux/x86_64.rs b/sys/src/linux/x86_64.rs index 438ccff2..cc535aea 100644 --- a/sys/src/linux/x86_64.rs +++ b/sys/src/linux/x86_64.rs @@ -685,7 +685,7 @@ pub type __int32_t = ::std::os::raw::c_int; pub type __uint32_t = ::std::os::raw::c_uint; pub type __int64_t = ::std::os::raw::c_long; #[doc = " The header of an atom:Atom."] -#[repr(C)] +#[repr(C, align(8))] #[derive(Debug, Copy, Clone)] pub struct LV2_Atom { #[doc = "< Size in bytes, not including type and size."] diff --git a/sys/src/windows.rs b/sys/src/windows.rs index 0b76865a..27397a77 100644 --- a/sys/src/windows.rs +++ b/sys/src/windows.rs @@ -681,7 +681,7 @@ pub const LV2_WORKER__schedule: &'static [u8; 41usize] = b"http://lv2plug.in/ns/ext/worker#schedule\0"; pub type va_list = *mut ::std::os::raw::c_char; #[doc = " The header of an atom:Atom."] -#[repr(C)] +#[repr(C, align(8))] #[derive(Debug, Copy, Clone)] pub struct LV2_Atom { #[doc = "< Size in bytes, not including type and size."] diff --git a/time/Cargo.toml b/time/Cargo.toml index 38816f10..86f00fe6 100644 --- a/time/Cargo.toml +++ b/time/Cargo.toml @@ -14,5 +14,5 @@ travis-ci = { repository = "RustAudio/rust-lv2", branch = "master" } maintenance = { status = "actively-developed" } [dependencies] -urid = "0.1.0" +urid = { version = "0.1.0", default-features = false } lv2-sys = "2.0.0" diff --git a/units/Cargo.toml b/units/Cargo.toml index 2e88b52e..094e66b4 100644 --- a/units/Cargo.toml +++ b/units/Cargo.toml @@ -15,4 +15,4 @@ maintenance = { status = "actively-developed" } [dependencies] lv2-sys = "2.0.0" -urid = "0.1.0" +urid = { version = "0.1.0", default-features = false } diff --git a/units/src/lib.rs b/units/src/lib.rs index 793db978..f2cf854c 100644 --- a/units/src/lib.rs +++ b/units/src/lib.rs @@ -133,7 +133,6 @@ pub mod units { use units::*; /// A URID cache containing all units. -#[derive(URIDCollection)] pub struct UnitURIDCollection { pub bar: URID, pub beat: URID, @@ -161,6 +160,37 @@ pub struct UnitURIDCollection { pub semitone: URID, } +impl URIDCollection for UnitURIDCollection { + fn from_map(map: &M) -> Option { + Some(Self { + bar: map.map_type()?, + beat: map.map_type()?, + bpm: map.map_type()?, + cent: map.map_type()?, + cm: map.map_type()?, + coef: map.map_type()?, + db: map.map_type()?, + degree: map.map_type()?, + frame: map.map_type()?, + hz: map.map_type()?, + inch: map.map_type()?, + khz: map.map_type()?, + km: map.map_type()?, + m: map.map_type()?, + mhz: map.map_type()?, + note: map.map_type()?, + mile: map.map_type()?, + min: map.map_type()?, + mm: map.map_type()?, + ms: map.map_type()?, + octave: map.map_type()?, + percent: map.map_type()?, + s: map.map_type()?, + semitone: map.map_type()?, + }) + } +} + /// Prelude of `lv2_units` for wildcard usage. pub mod prelude { pub use crate::units::*; diff --git a/urid/Cargo.toml b/urid/Cargo.toml index 6773068c..b889b48a 100644 --- a/urid/Cargo.toml +++ b/urid/Cargo.toml @@ -10,4 +10,7 @@ readme = "README.md" repository = "https://github.com/RustAudio/rust-lv2" [dependencies] -urid-derive = "0.1.0" +urid-derive = { version = "0.1.0", optional = true } + +[features] +default = ["urid-derive"] diff --git a/urid/lv2-urid/Cargo.toml b/urid/lv2-urid/Cargo.toml index 60c5947a..86a97c75 100644 --- a/urid/lv2-urid/Cargo.toml +++ b/urid/lv2-urid/Cargo.toml @@ -14,6 +14,9 @@ travis-ci = { repository = "RustAudio/rust-lv2", branch = "master" } maintenance = { status = "passively-maintained" } [dependencies] -lv2-core = "3.0.0" +lv2-core = { version = "3.0.0", optional = true, default-features = false } lv2-sys = "2.0.0" -urid = "0.1.0" +urid = { version = "0.1.0", default-features = false } + +[features] +default = ["lv2-core"] diff --git a/urid/lv2-urid/src/lib.rs b/urid/lv2-urid/src/lib.rs index 0a8d2dd1..a9ad9eab 100644 --- a/urid/lv2-urid/src/lib.rs +++ b/urid/lv2-urid/src/lib.rs @@ -1,11 +1,15 @@ //! LV2 integration of the URID concept. //! //! The URID specification provides a host feature that can be used by plugins to map URIs to integers, so-called URIDs. These URIDs are used by many other specifications to identify other URI bounds and combine the flexibility of URIs with the comparison speed of integers. + +#[cfg(feature = "lv2-core")] extern crate lv2_core as core; extern crate lv2_sys as sys; +#[cfg(feature = "lv2-core")] mod feature; mod mapper; +#[cfg(feature = "lv2-core")] pub use feature::*; pub use mapper::*; diff --git a/urid/src/lib.rs b/urid/src/lib.rs index 77eb7f5c..c2a96816 100644 --- a/urid/src/lib.rs +++ b/urid/src/lib.rs @@ -56,6 +56,7 @@ use std::marker::PhantomData; use std::num::NonZeroU32; use std::sync::Mutex; +#[cfg(feature = "urid-derive")] pub use urid_derive::*; /// Representation of a borrowed Uri. @@ -182,7 +183,7 @@ impl URID { /// A URID may not be 0 since this value is reserved for the `None` value of `Option>`, which therefore has the same size as a `URID`. If `T` is also a URI bound, the URID may only be the one that is mapped to the bounded URI. /// /// Since these constraints aren't checked by this method, it is unsafe. Using this method is technically sound as long as `raw_urid` is not zero, but might still result in bad behaviour if its the wrong URID for the bound `T`. - pub unsafe fn new_unchecked(raw_urid: u32) -> Self { + pub const unsafe fn new_unchecked(raw_urid: u32) -> Self { Self(NonZeroU32::new_unchecked(raw_urid), PhantomData) } @@ -270,12 +271,9 @@ impl Hash for URID { impl std::convert::TryFrom for URID { type Error = (); + #[inline] fn try_from(value: u32) -> Result { - if value == 0 { - Err(()) - } else { - Ok(unsafe { URID::new_unchecked(value) }) - } + URID::new(value).ok_or(()) } } diff --git a/worker/Cargo.toml b/worker/Cargo.toml index cfe0a10f..082e2efb 100644 --- a/worker/Cargo.toml +++ b/worker/Cargo.toml @@ -12,4 +12,4 @@ repository = "https://github.com/RustAudio/rust-lv2" [dependencies] lv2-sys = "2.0.0" lv2-core = "3.0.0" -urid = "0.1.0" +urid = { version = "0.1.0", default-features = false }