use crate::{
ser::{ScratchSpace, Serializer},
Fallible,
};
use core::{
alloc::Layout,
fmt,
ops::DerefMut,
ptr::{copy_nonoverlapping, NonNull},
};
#[derive(Debug)]
pub enum BufferSerializerError {
Overflow {
pos: usize,
bytes_needed: usize,
archive_len: usize,
},
}
impl fmt::Display for BufferSerializerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Overflow {
pos,
bytes_needed,
archive_len,
} => write!(
f,
"writing has overflowed the serializer buffer: pos {}, needed {}, total length {}",
pos, bytes_needed, archive_len
),
}
}
}
#[cfg(feature = "std")]
const _: () = {
use std::error::Error;
impl Error for BufferSerializerError {}
};
#[derive(Debug)]
pub struct BufferSerializer<T> {
inner: T,
pos: usize,
}
impl<T> BufferSerializer<T> {
#[inline]
pub fn new(inner: T) -> Self {
Self::with_pos(inner, 0)
}
#[inline]
pub fn with_pos(inner: T, pos: usize) -> Self {
Self { inner, pos }
}
#[inline]
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T: Default> Default for BufferSerializer<T> {
#[inline]
fn default() -> Self {
Self::new(T::default())
}
}
impl<T> Fallible for BufferSerializer<T> {
type Error = BufferSerializerError;
}
impl<T: AsMut<[u8]>> Serializer for BufferSerializer<T> {
#[inline]
fn pos(&self) -> usize {
self.pos
}
fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
let end_pos = self.pos + bytes.len();
let archive_len = self.inner.as_mut().len();
if end_pos > archive_len {
Err(BufferSerializerError::Overflow {
pos: self.pos,
bytes_needed: bytes.len(),
archive_len,
})
} else {
unsafe {
copy_nonoverlapping(
bytes.as_ptr(),
self.inner.as_mut().as_mut_ptr().add(self.pos),
bytes.len(),
);
}
self.pos = end_pos;
Ok(())
}
}
}
#[derive(Debug)]
pub enum FixedSizeScratchError {
OutOfScratch(Layout),
NotPoppedInReverseOrder {
pos: usize,
next_pos: usize,
next_size: usize,
},
UnownedAllocation,
}
impl fmt::Display for FixedSizeScratchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OutOfScratch(layout) => write!(
f,
"out of scratch: requested scratch space with size {} and align {}",
layout.size(),
layout.align()
),
Self::NotPoppedInReverseOrder {
pos,
next_pos,
next_size,
} => write!(
f,
"scratch space was not popped in reverse order: pos {}, next pos {}, next size {}",
pos, next_pos, next_size
),
Self::UnownedAllocation => write!(f, "unowned allocation"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FixedSizeScratchError {}
#[derive(Debug)]
pub struct BufferScratch<T> {
buffer: T,
pos: usize,
}
impl<T> BufferScratch<T> {
pub fn new(buffer: T) -> Self {
Self { buffer, pos: 0 }
}
pub fn clear(&mut self) {
self.pos = 0;
}
pub fn into_inner(self) -> T {
self.buffer
}
}
impl<T: Default> Default for BufferScratch<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T> Fallible for BufferScratch<T> {
type Error = FixedSizeScratchError;
}
impl<T: DerefMut<Target = U>, U: AsMut<[u8]>> ScratchSpace for BufferScratch<T> {
#[inline]
unsafe fn push_scratch(&mut self, layout: Layout) -> Result<NonNull<[u8]>, Self::Error> {
let bytes = self.buffer.as_mut();
let start = bytes.as_ptr().add(self.pos);
let pad = match (start as usize) & (layout.align() - 1) {
0 => 0,
x => layout.align() - x,
};
if pad + layout.size() <= bytes.len() - self.pos {
self.pos += pad;
let result_slice = ptr_meta::from_raw_parts_mut(
bytes.as_mut_ptr().add(self.pos).cast(),
layout.size(),
);
let result = NonNull::new_unchecked(result_slice);
self.pos += layout.size();
Ok(result)
} else {
Err(FixedSizeScratchError::OutOfScratch(layout))
}
}
#[inline]
unsafe fn pop_scratch(&mut self, ptr: NonNull<u8>, layout: Layout) -> Result<(), Self::Error> {
let bytes = self.buffer.as_mut();
let ptr = ptr.as_ptr();
if ptr >= bytes.as_mut_ptr() && ptr < bytes.as_mut_ptr().add(bytes.len()) {
let next_pos = ptr.offset_from(bytes.as_ptr()) as usize;
if next_pos + layout.size() <= self.pos {
self.pos = next_pos;
Ok(())
} else {
Err(FixedSizeScratchError::NotPoppedInReverseOrder {
pos: self.pos,
next_pos,
next_size: layout.size(),
})
}
} else {
Err(FixedSizeScratchError::UnownedAllocation)
}
}
}
#[derive(Debug)]
pub struct FallbackScratch<M, F> {
main: M,
fallback: F,
}
impl<M, F> FallbackScratch<M, F> {
pub fn new(main: M, fallback: F) -> Self {
Self { main, fallback }
}
}
impl<M: Default, F: Default> Default for FallbackScratch<M, F> {
fn default() -> Self {
Self {
main: M::default(),
fallback: F::default(),
}
}
}
impl<M, F: Fallible> Fallible for FallbackScratch<M, F> {
type Error = F::Error;
}
impl<M: ScratchSpace, F: ScratchSpace> ScratchSpace for FallbackScratch<M, F> {
#[inline]
unsafe fn push_scratch(&mut self, layout: Layout) -> Result<NonNull<[u8]>, Self::Error> {
self.main
.push_scratch(layout)
.or_else(|_| self.fallback.push_scratch(layout))
}
#[inline]
unsafe fn pop_scratch(&mut self, ptr: NonNull<u8>, layout: Layout) -> Result<(), Self::Error> {
self.main
.pop_scratch(ptr, layout)
.or_else(|_| self.fallback.pop_scratch(ptr, layout))
}
}
#[derive(Debug)]
pub struct ScratchTracker<T> {
inner: T,
bytes_allocated: usize,
allocations: usize,
max_bytes_allocated: usize,
max_allocations: usize,
max_alignment: usize,
}
impl<T> ScratchTracker<T> {
pub fn new(inner: T) -> Self {
Self {
inner,
bytes_allocated: 0,
allocations: 0,
max_bytes_allocated: 0,
max_allocations: 0,
max_alignment: 1,
}
}
pub fn max_bytes_allocated(&self) -> usize {
self.max_bytes_allocated
}
pub fn max_allocations(&self) -> usize {
self.max_allocations
}
pub fn max_alignment(&self) -> usize {
self.max_alignment
}
pub fn min_buffer_size(&self) -> usize {
self.max_bytes_allocated + self.min_buffer_size_max_error()
}
pub fn min_buffer_size_max_error(&self) -> usize {
self.max_allocations * (self.max_alignment - 1)
}
}
impl<T: Fallible> Fallible for ScratchTracker<T> {
type Error = T::Error;
}
impl<T: ScratchSpace> ScratchSpace for ScratchTracker<T> {
#[inline]
unsafe fn push_scratch(&mut self, layout: Layout) -> Result<NonNull<[u8]>, Self::Error> {
let result = self.inner.push_scratch(layout)?;
self.bytes_allocated += layout.size();
self.allocations += 1;
self.max_bytes_allocated = usize::max(self.bytes_allocated, self.max_bytes_allocated);
self.max_allocations = usize::max(self.allocations, self.max_allocations);
self.max_alignment = usize::max(self.max_alignment, layout.align());
Ok(result)
}
#[inline]
unsafe fn pop_scratch(&mut self, ptr: NonNull<u8>, layout: Layout) -> Result<(), Self::Error> {
self.inner.pop_scratch(ptr, layout)?;
self.bytes_allocated -= layout.size();
self.allocations -= 1;
Ok(())
}
}
impl<T> From<T> for ScratchTracker<T> {
fn from(inner: T) -> Self {
Self::new(inner)
}
}