#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Span {
pub at: u32,
pub len: u32,
}
const FLOOR: usize = 4096;
#[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"),
}
}
#[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);
}
}
#[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_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");
}
}