use alloc::{
alloc::{Layout, alloc, dealloc, handle_alloc_error},
boxed::Box,
vec::Vec
};
use core::{
ptr::{self, NonNull},
slice
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GrowStrategy {
Linear { size: usize },
Exponential { size: usize }
}
impl GrowStrategy {
#[inline]
const fn current(&self) -> usize {
match self {
Self::Linear { size } => *size,
Self::Exponential { size: current } => *current
}
}
#[inline]
fn next(&mut self) -> usize {
match self {
Self::Linear { size } => *size,
Self::Exponential { size: current } => {
*current = current.saturating_mul(2);
*current
}
}
}
}
struct Segment {
ptr: NonNull<u8>,
cap: usize,
len: usize
}
unsafe impl Send for Segment {}
unsafe impl Sync for Segment {}
impl Segment {
fn new(cap: usize) -> Self {
if cap == 0 {
return Segment {
ptr: NonNull::dangling(),
cap: 0,
len: 0
};
}
let layout = Layout::array::<u8>(cap).expect("infallible");
let Some(ptr) = NonNull::new(unsafe { alloc(layout) }) else {
handle_alloc_error(layout);
};
Segment { ptr, cap, len: 0 }
}
#[inline]
fn remaining(&self) -> usize {
self.cap - self.len
}
#[inline]
fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
fn push(&mut self, byte: u8) -> bool {
if self.len == self.cap {
return false;
}
unsafe { *self.ptr.as_ptr().add(self.len) = byte };
self.len += 1;
true
}
fn extend_from_slice(&mut self, data: &[u8]) -> usize {
let n = data.len().min(self.remaining());
if n > 0 {
unsafe { ptr::copy_nonoverlapping(data.as_ptr(), self.ptr.as_ptr().add(self.len), n) };
self.len += n;
}
n
}
}
impl Drop for Segment {
fn drop(&mut self) {
if self.cap != 0 {
let layout = Layout::array::<u8>(self.cap).expect("infallible");
unsafe { dealloc(self.ptr.as_ptr(), layout) };
}
}
}
pub struct SegmentedBuf {
segments: Vec<Segment>,
total_len: usize,
grow_strategy: GrowStrategy
}
impl SegmentedBuf {
pub const fn new(grow_strategy: GrowStrategy) -> Self {
SegmentedBuf {
segments: Vec::new(),
total_len: 0,
grow_strategy
}
}
#[inline]
fn has_space(&self) -> bool {
self.segments.last().is_some_and(|s| s.len < s.cap)
}
fn alloc_segment(&mut self) {
let cap = if self.segments.is_empty() { self.grow_strategy.current() } else { self.grow_strategy.next() };
self.segments.push(Segment::new(cap));
}
pub fn push(&mut self, byte: u8) {
if !self.has_space() {
self.alloc_segment();
}
let seg = self.segments.last_mut().expect("infallible");
let res = seg.push(byte);
debug_assert!(res);
self.total_len += 1;
}
pub fn extend_from_slice(&mut self, mut data: &[u8]) {
if data.is_empty() {
return;
}
if let Some(seg) = self.segments.last_mut() {
if seg.remaining() > 0 {
let n = seg.extend_from_slice(data);
self.total_len += n;
data = &data[n..];
}
}
if data.is_empty() {
return;
}
if data.len() >= self.grow_strategy.current() {
let mut seg = Segment::new(data.len());
let n = seg.extend_from_slice(data);
debug_assert_eq!(n, data.len());
self.total_len += n;
self.segments.push(seg);
return;
}
while !data.is_empty() {
self.alloc_segment();
let seg = self.segments.last_mut().expect("infallible");
let n = seg.extend_from_slice(data);
self.total_len += n;
data = &data[n..];
}
}
pub fn take_from(&mut self, other: SegmentedBuf) {
self.segments.extend(other.segments);
self.total_len += other.total_len;
}
#[cfg(feature = "std")]
pub fn segments(&self) -> impl Iterator<Item = &[u8]> + '_ {
self.segments.iter().map(|s| s.as_slice())
}
pub fn into_contiguous(self) -> Box<[u8]> {
let mut buf = Vec::with_capacity(self.total_len);
for seg in self.segments {
buf.extend_from_slice(seg.as_slice());
}
buf.into_boxed_slice()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_push_linear() {
let mut v = SegmentedBuf::new(GrowStrategy::Linear { size: 4 });
for b in 0u8..10 {
v.push(b);
}
assert_eq!(v.segments.len(), 3);
assert_eq!(&*v.into_contiguous(), &(0u8..10).collect::<Vec<_>>()[..]);
}
#[test]
fn test_push_exponential() {
let mut v = SegmentedBuf::new(GrowStrategy::Exponential { size: 2 });
for b in 0u8..20 {
v.push(b);
}
assert_eq!(v.segments.len(), 4);
assert_eq!(&*v.into_contiguous(), &(0u8..20).collect::<Vec<_>>()[..]);
}
#[test]
fn test_extend_small() {
let mut v = SegmentedBuf::new(GrowStrategy::Linear { size: 4 });
v.push(1);
v.push(2);
v.extend_from_slice(&[3, 4, 5, 6]);
assert_eq!(v.segments.len(), 2);
assert_eq!(&*v.into_contiguous(), &[1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_extend_large() {
let mut v = SegmentedBuf::new(GrowStrategy::Linear { size: 8 });
v.push(0xAA);
let x: Vec<u8> = (0u8..100).collect();
v.extend_from_slice(&x);
assert_eq!(v.segments.len(), 2);
let y: Vec<u8> = core::iter::once(0xAA).chain(x).collect();
assert_eq!(&*v.into_contiguous(), &y[..]);
}
}