Skip to content

Commit

Permalink
Add copy_to_slice() and copy_from_slice()
Browse files Browse the repository at this point in the history
  • Loading branch information
mgeier committed Sep 24, 2021
1 parent 4f15f30 commit 4d60af4
Showing 1 changed file with 95 additions and 112 deletions.
207 changes: 95 additions & 112 deletions src/chunks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,91 +81,20 @@
//! }
//! ```
//!
//! ## Common Access Patterns
//! If the trait bound `T: Copy` is satisfied, the convenience functions
//! [`Producer::copy_from_slice()`] and [`Consumer::copy_to_slice()`] can be used.
//!
//! The following examples show the [`Producer`] side;
//! similar patterns can of course be used with [`Consumer::read_chunk()`] as well.
//! Furthermore, the examples use [`Producer::write_chunk_uninit()`],
//! along with a bit of `unsafe` code.
//! To avoid this, you can use [`Producer::write_chunk()`] instead,
//! which requires the trait bound `T: Default` and will lead to a small runtime overhead.
//!
//! Copy a whole slice of items into the ring buffer, but only if space permits
//! (if not, the entire input slice is returned as an error):
//!
//! ```
//! use rtrb::{Producer, CopyToUninit};
//!
//! fn push_entire_slice<'a, T>(queue: &mut Producer<T>, slice: &'a [T]) -> Result<(), &'a [T]>
//! where
//! T: Copy,
//! {
//! if let Ok(mut chunk) = queue.write_chunk_uninit(slice.len()) {
//! let (first, second) = chunk.as_mut_slices();
//! let mid = first.len();
//! slice[..mid].copy_to_uninit(first);
//! slice[mid..].copy_to_uninit(second);
//! // SAFETY: All slots have been initialized
//! unsafe {
//! chunk.commit_all();
//! }
//! Ok(())
//! } else {
//! Err(slice)
//! }
//! }
//! ```
//!
//! Copy as many items as possible from a given slice, returning the number of copied items:
//!
//! ```
//! use rtrb::{Producer, CopyToUninit, chunks::ChunkError::TooFewSlots};
//!
//! fn push_partial_slice<T>(queue: &mut Producer<T>, slice: &[T]) -> usize
//! where
//! T: Copy,
//! {
//! let mut chunk = match queue.write_chunk_uninit(slice.len()) {
//! Ok(chunk) => chunk,
//! // Remaining slots are returned, this will always succeed:
//! Err(TooFewSlots(n)) => queue.write_chunk_uninit(n).unwrap(),
//! };
//! let end = chunk.len();
//! let (first, second) = chunk.as_mut_slices();
//! let mid = first.len();
//! slice[..mid].copy_to_uninit(first);
//! slice[mid..end].copy_to_uninit(second);
//! // SAFETY: All slots have been initialized
//! unsafe {
//! chunk.commit_all();
//! }
//! end
//! }
//! ```
//! use rtrb::RingBuffer;
//!
//! Write as many slots as possible, given an iterator
//! (and return the number of written slots):
//!
//! ```
//! use rtrb::{Producer, chunks::ChunkError::TooFewSlots};
//!
//! fn push_from_iter<T, I>(queue: &mut Producer<T>, iter: I) -> usize
//! where
//! T: Default,
//! I: IntoIterator<Item = T>,
//! {
//! let iter = iter.into_iter();
//! let n = match iter.size_hint() {
//! (_, None) => queue.slots(),
//! (_, Some(n)) => n,
//! };
//! let chunk = match queue.write_chunk_uninit(n) {
//! Ok(chunk) => chunk,
//! // Remaining slots are returned, this will always succeed:
//! Err(TooFewSlots(n)) => queue.write_chunk_uninit(n).unwrap(),
//! };
//! chunk.fill_from_iter(iter)
//! }
//! let (mut p, mut c) = RingBuffer::new(5);
//! let source = vec![1, 2, 3, 4, 5, 6];
//! assert_eq!(p.copy_from_slice(&source), 5);
//! let mut destination = vec![0; 3];
//! assert_eq!(c.copy_to_slice(&mut destination), 3);
//! assert_eq!(destination, [1, 2, 3]);
//! assert_eq!(c.copy_to_slice(&mut destination), 2);
//! assert_eq!(destination, [4, 5, 3]);
//! ```

use core::fmt;
Expand Down Expand Up @@ -261,6 +190,33 @@ impl<T> Producer<T> {
producer: self,
})
}

/// Copies as many items as possible from a given slice into the ring buffer.
///
/// Returns the number of written items.
///
/// The written slots are automatically made available to be read by the [`Consumer`].
pub fn copy_from_slice(&mut self, slice: &[T]) -> usize
where
T: Copy,
{
let mut chunk = match self.write_chunk_uninit(slice.len()) {
Ok(chunk) => chunk,
// Remaining slots are returned, this will always succeed:
Err(ChunkError::TooFewSlots(n)) => self.write_chunk_uninit(n).unwrap(),
};
let end = chunk.len();
let (first, second) = chunk.as_mut_slices();
let mid = first.len();
// NB: If slice.is_empty(), chunk will be empty as well and the following are no-ops:
slice[..mid].copy_to_uninit(first);
slice[mid..end].copy_to_uninit(second);
// Safety: All slots have been initialized
unsafe {
chunk.commit_all();
}
end
}
}

impl<T> Consumer<T> {
Expand Down Expand Up @@ -311,6 +267,50 @@ impl<T> Consumer<T> {
consumer: self,
})
}

/// Copies as many items as possible from the ring buffer to a given slice.
///
/// Returns the number of copied items.
///
/// The copied slots are automatically made available to be written again by the [`Producer`].
pub fn copy_to_slice(&mut self, slice: &mut [T]) -> usize
where
T: Copy,
{
// Safety: Transmuting &mut [T] to &mut [MaybeUninit<T>] is generally unsafe!
// However, since we can guarantee that only valid T values will ever be written,
// and the reference never leaves our control, it should be fine.
unsafe { self.copy_to_slice_uninit(&mut *(slice as *mut [T] as *mut _)) }
}

/// Copies as many items as possible from the ring buffer to a given uninitialized slice.
///
/// Returns the number of copied items.
///
/// The copied slots are automatically made available to be written again by the [`Producer`].
///
/// # Safety
///
/// This function is safe, but if the return value is smaller than the size of `slice`,
/// the remaining part of it might still contain uninitialized memory.
pub fn copy_to_slice_uninit(&mut self, slice: &mut [MaybeUninit<T>]) -> usize
where
T: Copy,
{
let chunk = match self.read_chunk(slice.len()) {
Ok(chunk) => chunk,
// Remaining slots are returned, this will always succeed:
Err(ChunkError::TooFewSlots(n)) => self.read_chunk(n).unwrap(),
};
let end = chunk.len();
let (first, second) = chunk.as_slices();
let mid = first.len();
// NB: If slice.is_empty(), chunk will be empty as well and the following are no-ops:
first.copy_to_uninit(&mut slice[..mid]);
second.copy_to_uninit(&mut slice[mid..end]);
chunk.commit_all();
end
}
}

/// Structure for writing into multiple ([`Default`]-initialized) slots in one go.
Expand Down Expand Up @@ -761,23 +761,13 @@ impl<'a, T> core::iter::FusedIterator for ReadChunkIntoIter<'a, T> {}
impl std::io::Write for Producer<u8> {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
use ChunkError::TooFewSlots;
let mut chunk = match self.write_chunk_uninit(buf.len()) {
Ok(chunk) => chunk,
Err(TooFewSlots(0)) => return Err(std::io::ErrorKind::WouldBlock.into()),
Err(TooFewSlots(n)) => self.write_chunk_uninit(n).unwrap(),
};
let end = chunk.len();
let (first, second) = chunk.as_mut_slices();
let mid = first.len();
// NB: If buf.is_empty(), chunk will be empty as well and the following are no-ops:
buf[..mid].copy_to_uninit(first);
buf[mid..end].copy_to_uninit(second);
// Safety: All slots have been initialized
unsafe {
chunk.commit_all();
if buf.is_empty() {
return Ok(0);
}
match self.copy_from_slice(buf) {
0 => Err(std::io::ErrorKind::WouldBlock.into()),
n => Ok(n),
}
Ok(end)
}

fn flush(&mut self) -> std::io::Result<()> {
Expand All @@ -790,20 +780,13 @@ impl std::io::Write for Producer<u8> {
impl std::io::Read for Consumer<u8> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
use ChunkError::TooFewSlots;
let chunk = match self.read_chunk(buf.len()) {
Ok(chunk) => chunk,
Err(TooFewSlots(0)) => return Err(std::io::ErrorKind::WouldBlock.into()),
Err(TooFewSlots(n)) => self.read_chunk(n).unwrap(),
};
let (first, second) = chunk.as_slices();
let mid = first.len();
let end = chunk.len();
// NB: If buf.is_empty(), chunk will be empty as well and the following are no-ops:
buf[..mid].copy_from_slice(first);
buf[mid..end].copy_from_slice(second);
chunk.commit_all();
Ok(end)
if buf.is_empty() {
return Ok(0);
}
match self.copy_to_slice(buf) {
0 => Err(std::io::ErrorKind::WouldBlock.into()),
n => Ok(n),
}
}
}

Expand Down

0 comments on commit 4d60af4

Please sign in to comment.