#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Span {
pub at: u32,
pub len: u32,
}
const FLOOR: usize = 4096;
const LONG: usize = 255;
const LONG_PREFIX: usize = 5;
#[inline]
fn sized_head(bytes: &[u8], at: usize) -> (usize, usize) {
let head = usize::from(bytes[at]);
if head < LONG {
return (1, head);
}
let head: [u8; 4] = bytes[at + 1..at + LONG_PREFIX]
.try_into()
.expect("four bytes of length behind the marker");
(LONG_PREFIX, u32::from_le_bytes(head) as usize)
}
#[derive(Debug, Default, Clone)]
pub struct Blob {
bytes: Vec<u8>,
dead: usize,
}
impl Blob {
#[must_use]
pub const fn new() -> Blob {
Blob {
bytes: Vec::new(),
dead: 0,
}
}
#[must_use]
pub fn with_capacity(n: usize) -> Blob {
Blob {
bytes: Vec::with_capacity(n),
dead: 0,
}
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
self.bytes.len()
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[inline]
#[must_use]
pub const fn dead(&self) -> usize {
self.dead
}
#[inline]
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.bytes.capacity()
}
#[inline]
pub fn push(&mut self, bytes: &[u8]) -> u32 {
let at = u32::try_from(self.bytes.len()).expect("the blob is under 4 GiB");
crate::grow::reserve(&mut self.bytes, bytes.len());
self.bytes.extend_from_slice(bytes);
at
}
#[inline]
pub fn push_span(&mut self, bytes: &[u8]) -> Span {
Span {
at: self.push(bytes),
len: u32::try_from(bytes.len()).expect("no one value is 4 GiB"),
}
}
pub fn push_sized(&mut self, bytes: &[u8]) -> u32 {
if bytes.len() < LONG {
let head = [u8::try_from(bytes.len()).expect("under LONG")];
let at = self.push(&head);
self.push(bytes);
return at;
}
let len = u32::try_from(bytes.len()).expect("no one value is 4 GiB");
let mut head = [0u8; LONG_PREFIX];
head[0] = u8::try_from(LONG).expect("LONG is one byte");
head[1..].copy_from_slice(&len.to_le_bytes());
let at = self.push(&head);
self.push(bytes);
at
}
#[inline]
#[must_use]
pub fn sized(&self, at: u32) -> &[u8] {
let at = at as usize;
let (skip, len) = sized_head(&self.bytes, at);
&self.bytes[at + skip..at + skip + len]
}
#[inline]
#[must_use]
pub fn sized_len(&self, at: u32) -> usize {
sized_head(&self.bytes, at as usize).1
}
#[inline]
pub fn release_sized(&mut self, at: u32) {
let (skip, len) = sized_head(&self.bytes, at as usize);
self.release(skip + len);
}
#[inline]
#[must_use]
pub fn read(&self, at: u32, len: usize) -> &[u8] {
let at = at as usize;
&self.bytes[at..at + len]
}
#[inline]
#[must_use]
pub fn span(&self, span: Span) -> &[u8] {
self.read(span.at, span.len as usize)
}
#[inline]
pub const fn release(&mut self, len: usize) {
self.dead += len;
}
#[inline]
pub const fn release_span(&mut self, span: Span) {
self.release(span.len as usize);
}
#[inline]
pub fn clear(&mut self) {
self.bytes.clear();
self.dead = 0;
}
#[inline]
#[must_use]
pub const fn worth_compacting(&self) -> bool {
self.dead >= FLOOR && self.dead * 2 >= self.bytes.len()
}
pub fn compact<F>(&mut self, keep: F)
where
F: FnOnce(&mut Keep<'_>),
{
let fresh = {
let mut k = Keep {
old: &self.bytes,
fresh: Vec::with_capacity(self.bytes.len() - self.dead),
};
keep(&mut k);
k.fresh
};
self.bytes = fresh;
self.dead = 0;
}
}
#[derive(Debug)]
pub struct Keep<'a> {
old: &'a [u8],
fresh: Vec<u8>,
}
impl Keep<'_> {
#[inline]
pub fn moved(&mut self, at: &mut u32, len: usize) {
let from = *at as usize;
let to = u32::try_from(self.fresh.len()).expect("the blob only shrinks here");
self.fresh.extend_from_slice(&self.old[from..from + len]);
*at = to;
}
#[inline]
pub fn moved_span(&mut self, span: &mut Span) {
let len = span.len as usize;
self.moved(&mut span.at, len);
}
#[inline]
pub fn moved_sized(&mut self, at: &mut u32) {
let (skip, len) = sized_head(self.old, *at as usize);
self.moved(at, skip + len);
}
#[inline]
#[must_use]
pub fn peek(&self, at: u32, len: usize) -> &[u8] {
let at = at as usize;
&self.old[at..at + len]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn what_goes_in_comes_back_out() {
let mut b = Blob::new();
let one = b.push_span(b"field");
let two = b.push_span(b"");
let three = b.push_span(b"a longer value than the first one");
assert_eq!(b.span(one), b"field");
assert_eq!(b.span(two), b"");
assert_eq!(b.span(three), b"a longer value than the first one");
assert_eq!(b.len(), 5 + 33);
assert_eq!(b.dead(), 0);
}
#[test]
fn a_length_written_in_front_reads_back_at_every_length() {
let mut b = Blob::new();
let lens = [0usize, 1, 2, 100, 253, 254, 255, 256, 257, 70_000];
let at: Vec<u32> = lens
.iter()
.enumerate()
.map(|(i, &n)| {
let byte = u8::try_from(i).expect("ten of them");
b.push_sized(&vec![byte; n])
})
.collect();
for (i, (&n, &at)) in lens.iter().zip(&at).enumerate() {
let byte = u8::try_from(i).expect("ten of them");
assert_eq!(b.sized_len(at), n, "the length came back wrong");
assert_eq!(b.sized(at), &vec![byte; n][..], "the bytes came back wrong");
}
let short: usize = lens.iter().filter(|&&n| n < 255).map(|&n| n + 1).sum();
let long: usize = lens.iter().filter(|&&n| n >= 255).map(|&n| n + 5).sum();
assert_eq!(b.len(), short + long);
}
#[test]
fn a_run_that_carries_its_own_length_moves_with_it() {
let mut b = Blob::new();
let mut live: Vec<u32> = Vec::new();
for i in 0..100u32 {
let n = if i % 3 == 0 { 300 } else { 40 };
let byte = u8::try_from(i % 251).expect("under 251");
let first = b.push_sized(&vec![byte; n]);
b.release_sized(first);
live.push(b.push_sized(&vec![byte; n]));
}
assert!(b.worth_compacting());
let before = b.len();
b.compact(|k| {
for at in &mut live {
k.moved_sized(at);
}
});
assert_eq!(b.dead(), 0);
assert_eq!(b.len() * 2, before, "the dead half went and no more");
for (i, &at) in live.iter().enumerate() {
let i = u32::try_from(i).expect("a hundred of them");
let n = if i % 3 == 0 { 300 } else { 40 };
let byte = u8::try_from(i % 251).expect("under 251");
assert_eq!(b.sized(at), &vec![byte; n][..], "a reference moved wrongly");
}
}
#[test]
fn a_rewrite_leaves_the_old_bytes_behind_and_says_so() {
let mut b = Blob::new();
let old = b.push_span(b"before");
b.release_span(old);
let new = b.push_span(b"after");
assert_eq!(b.span(new), b"after");
assert_eq!(b.dead(), 6, "the old bytes are still there and counted");
assert_eq!(b.len(), 11);
}
#[test]
fn the_dead_bytes_come_back_and_the_live_ones_move() {
let mut b = Blob::new();
let mut live: Vec<Span> = Vec::new();
for i in 0..100u32 {
let bytes = vec![b'a' + u8::try_from(i % 26).expect("under 26"); 100];
let first = b.push_span(&bytes);
b.release_span(first);
live.push(b.push_span(&bytes));
}
assert_eq!(b.dead(), 10_000);
assert!(b.worth_compacting());
let want: Vec<Vec<u8>> = live.iter().map(|&s| b.span(s).to_vec()).collect();
b.compact(|k| {
for span in &mut live {
k.moved_span(span);
}
});
assert_eq!(b.dead(), 0);
assert_eq!(b.len(), 10_000, "only the live half survived");
for (span, bytes) in live.iter().zip(&want) {
assert_eq!(b.span(*span), &bytes[..], "a reference moved wrongly");
}
}
#[test]
fn a_small_or_mostly_live_blob_is_left_alone() {
let mut b = Blob::new();
b.push(&vec![0u8; 100_000]);
b.release(3000);
assert!(!b.worth_compacting(), "under the floor, whatever the ratio");
let mut c = Blob::new();
c.push(&vec![0u8; 100_000]);
c.release(40_000);
assert!(!c.worth_compacting(), "over the floor and under the half");
c.release(10_000);
assert!(c.worth_compacting(), "and at the half it is worth doing");
}
#[test]
fn clearing_keeps_the_allocation_and_forgets_the_dead() {
let mut b = Blob::with_capacity(1024);
b.push(b"something");
b.release(4);
b.clear();
assert!(b.is_empty());
assert_eq!(b.dead(), 0);
assert!(b.memory_bytes() >= 1024, "the allocation stayed");
}
}