diff --git a/CHANGELOG.md b/CHANGELOG.md index b63bd39b51..43477e102f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `String::drain`. - Implemented `DoubleEndedIterator` for `OldestOrdered`. - Added std `Entry` methods to indexmap `Entry`. +- Added `MpMcQueueView`, the `!Sized` version of `MpMcQueue`. ### Changed diff --git a/src/mpmc.rs b/src/mpmc.rs index bb9b1fdea4..caef2caf9d 100644 --- a/src/mpmc.rs +++ b/src/mpmc.rs @@ -95,6 +95,8 @@ use portable_atomic as atomic; use atomic::Ordering; +use crate::storage::{OwnedStorage, Storage, ViewStorage}; + #[cfg(feature = "mpmc_large")] type AtomicTargetSize = atomic::AtomicUsize; #[cfg(not(feature = "mpmc_large"))] @@ -128,17 +130,27 @@ pub type Q32 = MpMcQueue; /// MPMC queue with a capability for 64 elements. pub type Q64 = MpMcQueue; -/// MPMC queue with a capacity for N elements -/// N must be a power of 2 -/// The max value of N is u8::MAX - 1 if `mpmc_large` feature is not enabled. -pub struct MpMcQueue { - buffer: UnsafeCell<[Cell; N]>, +/// Base struct for [`MpMcQueue`] and [`MpMcQueueView`], generic over the [`Storage`]. +/// +/// In most cases you should use [`MpMcQueue`] or [`MpMcQueueView`] directly. Only use this +/// struct if you want to write code that's generic over both. +pub struct MpMcQueueInner { dequeue_pos: AtomicTargetSize, enqueue_pos: AtomicTargetSize, + buffer: UnsafeCell>>, } +/// MPMC queue with a capacity for N elements +/// N must be a power of 2 +/// The max value of N is u8::MAX - 1 if `mpmc_large` feature is not enabled. +pub type MpMcQueue = MpMcQueueInner>; + +/// MPMC queue with a capacity for N elements +/// N must be a power of 2 +/// The max value of N is u8::MAX - 1 if `mpmc_large` feature is not enabled. +pub type MpMcQueueView = MpMcQueueInner; + impl MpMcQueue { - const MASK: UintSize = (N - 1) as UintSize; const EMPTY_CELL: Cell = Cell::new(0); const ASSERT: [(); 1] = [()]; @@ -167,10 +179,56 @@ impl MpMcQueue { enqueue_pos: AtomicTargetSize::new(0), } } + /// Get a reference to the `MpMcQueue`, erasing the `N` const-generic. + /// + /// + /// ```rust + /// # use heapless::mpmc::{MpMcQueue, MpMcQueueView}; + /// let queue: MpMcQueue = MpMcQueue::new(); + /// let view: &MpMcQueueView = queue.as_view(); + /// ``` + /// + /// It is often preferable to do the same through type coerction, since `MpMcQueue` implements `Unsize>`: + /// + /// ```rust + /// # use heapless::mpmc::{MpMcQueue, MpMcQueueView}; + /// let queue: MpMcQueue = MpMcQueue::new(); + /// let view: &MpMcQueueView = &queue; + /// ``` + #[inline] + pub const fn as_view(&self) -> &MpMcQueueView { + self + } + + /// Get a mutable reference to the `MpMcQueue`, erasing the `N` const-generic. + /// + /// ```rust + /// # use heapless::mpmc::{MpMcQueue, MpMcQueueView}; + /// let mut queue: MpMcQueue = MpMcQueue::new(); + /// let view: &mut MpMcQueueView = queue.as_mut_view(); + /// ``` + /// + /// It is often preferable to do the same through type coerction, since `MpMcQueue` implements `Unsize>`: + /// + /// ```rust + /// # use heapless::mpmc::{MpMcQueue, MpMcQueueView}; + /// let mut queue: MpMcQueue = MpMcQueue::new(); + /// let view: &mut MpMcQueueView = &mut queue; + /// ``` + #[inline] + pub fn as_mut_view(&mut self) -> &mut MpMcQueueView { + self + } +} + +impl MpMcQueueInner { + fn mask(&self) -> UintSize { + (S::len(self.buffer.get()) - 1) as _ + } /// Returns the item in the front of the queue, or `None` if the queue is empty pub fn dequeue(&self) -> Option { - unsafe { dequeue(self.buffer.get() as *mut _, &self.dequeue_pos, Self::MASK) } + unsafe { dequeue(S::as_ptr(self.buffer.get()), &self.dequeue_pos, self.mask()) } } /// Adds an `item` to the end of the queue @@ -179,9 +237,9 @@ impl MpMcQueue { pub fn enqueue(&self, item: T) -> Result<(), T> { unsafe { enqueue( - self.buffer.get() as *mut _, + S::as_ptr(self.buffer.get()), &self.enqueue_pos, - Self::MASK, + self.mask(), item, ) } @@ -194,14 +252,14 @@ impl Default for MpMcQueue { } } -impl Drop for MpMcQueue { +impl Drop for MpMcQueueInner { fn drop(&mut self) { // drop all contents currently in the queue while self.dequeue().is_some() {} } } -unsafe impl Sync for MpMcQueue where T: Send {} +unsafe impl Sync for MpMcQueueInner where T: Send {} struct Cell { data: MaybeUninit, diff --git a/src/storage.rs b/src/storage.rs index b4e9269cef..6daa4e5719 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -4,6 +4,10 @@ use core::borrow::{Borrow, BorrowMut}; pub(crate) trait SealedStorage { type Buffer: ?Sized + Borrow<[T]> + BorrowMut<[T]>; + /// Obtain the length of the buffer + fn len(this: *const Self::Buffer) -> usize; + /// Obtain access to the first element of the buffer + fn as_ptr(this: *mut Self::Buffer) -> *mut T; } /// Trait defining how data for a container is stored. @@ -33,6 +37,12 @@ pub enum OwnedStorage {} impl Storage for OwnedStorage {} impl SealedStorage for OwnedStorage { type Buffer = [T; N]; + fn len(_: *const Self::Buffer) -> usize { + N + } + fn as_ptr(this: *mut Self::Buffer) -> *mut T { + this as _ + } } /// Implementation of [`Storage`] that stores the data in an unsized `[T]`. @@ -40,4 +50,17 @@ pub enum ViewStorage {} impl Storage for ViewStorage {} impl SealedStorage for ViewStorage { type Buffer = [T]; + fn len(this: *const Self::Buffer) -> usize { + // We get the len of the buffer. There is no reverse method of `core::ptr::from_raw_parts`, so we have to work around it. + let ptr: *const [()] = this as _; + // SAFETY: There is no aliasing as () is zero-sized + let slice: &[()] = unsafe { &*ptr }; + let len = slice.len(); + + len - 1 + } + + fn as_ptr(this: *mut Self::Buffer) -> *mut T { + this as _ + } }