use crate::listpack::{Entry, backlen_len, decode, entry_len, read_backlen, write_entry};
pub const CHUNK_BYTES: usize = 8192;
pub const CHUNK_ENTRIES: usize = 512;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Chunk {
bytes: Vec<u8>,
head: usize,
tail: usize,
count: usize,
}
impl Chunk {
#[must_use]
pub fn for_back() -> Chunk {
Chunk {
bytes: vec![0; CHUNK_BYTES],
head: 0,
tail: 0,
count: 0,
}
}
#[must_use]
pub fn for_front() -> Chunk {
Chunk {
bytes: vec![0; CHUNK_BYTES],
head: CHUNK_BYTES,
tail: CHUNK_BYTES,
count: 0,
}
}
#[must_use]
pub fn plain(value: &[u8]) -> Chunk {
let mut c = Chunk {
bytes: vec![0; entry_len(value)],
head: 0,
tail: 0,
count: 0,
};
let put = c.push_back(value);
debug_assert!(put, "a chunk sized for one element refused it");
c
}
#[must_use]
pub fn adopt(entries: &[u8], count: usize) -> Chunk {
let mut bytes = vec![0; CHUNK_BYTES.max(entries.len())];
bytes[..entries.len()].copy_from_slice(entries);
Chunk {
bytes,
head: 0,
tail: entries.len(),
count,
}
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
self.count
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.count == 0
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.bytes.capacity() + size_of::<Chunk>()
}
#[must_use]
#[inline]
pub const fn live_bytes(&self) -> usize {
self.tail - self.head
}
pub fn push_back(&mut self, value: &[u8]) -> bool {
if self.count >= CHUNK_ENTRIES {
return false;
}
let need = entry_len(value);
if self.tail + need > self.bytes.len() && !self.shift(need, false) {
return false;
}
write_entry(&mut self.bytes[self.tail..], value);
self.tail += need;
self.count += 1;
true
}
pub fn push_front(&mut self, value: &[u8]) -> bool {
if self.count >= CHUNK_ENTRIES {
return false;
}
let need = entry_len(value);
if need > self.head && !self.shift(need, true) {
return false;
}
self.head -= need;
write_entry(&mut self.bytes[self.head..], value);
self.count += 1;
true
}
fn shift(&mut self, need: usize, front: bool) -> bool {
let live = self.tail - self.head;
let free = self.bytes.len() - live;
if free < need {
return false;
}
let spare = (free - need) / 2;
let head = if front { need + spare } else { spare };
self.bytes.copy_within(self.head..self.tail, head);
self.head = head;
self.tail = head + live;
true
}
#[must_use]
pub fn front(&self) -> Option<Entry<'_>> {
if self.count == 0 {
return None;
}
decode(&self.bytes[self.head..self.tail]).map(|(e, _)| e)
}
#[must_use]
pub fn back(&self) -> Option<Entry<'_>> {
let at = self.back_at()?;
decode(&self.bytes[at..self.tail]).map(|(e, _)| e)
}
#[must_use]
pub fn get(&self, index: usize) -> Option<Entry<'_>> {
let at = self.offset_of(index)?;
decode(&self.bytes[at..self.tail]).map(|(e, _)| e)
}
pub fn drop_front(&mut self) -> bool {
if self.count == 0 {
return false;
}
let Some(step) = self.step(self.head) else {
return false;
};
self.head += step;
self.count -= 1;
true
}
pub fn drop_back(&mut self) -> bool {
let Some(at) = self.back_at() else {
return false;
};
self.tail = at;
self.count -= 1;
true
}
pub fn drop_front_n(&mut self, n: usize) -> usize {
let mut at = self.head;
let took = n.min(self.count);
for _ in 0..took {
let Some(step) = self.step(at) else {
break;
};
at += step;
}
self.count -= took;
self.head = at;
took
}
pub fn drop_back_n(&mut self, n: usize) -> usize {
let took = n.min(self.count);
for _ in 0..took {
let Some(at) = self.back_at() else {
break;
};
self.tail = at;
self.count -= 1;
}
took
}
pub fn insert_at(&mut self, index: usize, value: &[u8]) -> bool {
if index > self.count {
return false;
}
if index == self.count {
return self.push_back(value);
}
if index == 0 {
return self.push_front(value);
}
if self.count >= CHUNK_ENTRIES {
return false;
}
let need = entry_len(value);
let Some(at) = self.offset_of(index) else {
return false;
};
let front = need <= self.head;
let back = self.tail + need <= self.bytes.len();
let at = if (front && !back) || (front && back && index * 2 <= self.count) {
self.bytes.copy_within(self.head..at, self.head - need);
self.head -= need;
at - need
} else if back {
self.bytes.copy_within(at..self.tail, at + need);
self.tail += need;
at
} else {
if !self.shift(need, false) {
return false;
}
let at = self.offset_of(index).expect("the entries did not move");
self.bytes.copy_within(at..self.tail, at + need);
self.tail += need;
at
};
write_entry(&mut self.bytes[at..], value);
self.count += 1;
true
}
pub fn remove_at(&mut self, index: usize) -> bool {
if index >= self.count {
return false;
}
if index == 0 {
return self.drop_front();
}
if index + 1 == self.count {
return self.drop_back();
}
let Some(at) = self.offset_of(index) else {
return false;
};
let Some(span) = self.step(at) else {
return false;
};
if index * 2 <= self.count {
self.bytes.copy_within(self.head..at, self.head + span);
self.head += span;
} else {
self.bytes.copy_within(at + span..self.tail, at);
self.tail -= span;
}
self.count -= 1;
true
}
pub fn replace_at(&mut self, index: usize, value: &[u8]) -> bool {
let Some(at) = self.offset_of(index) else {
return false;
};
let need = entry_len(value);
if self.step(at) == Some(need) {
write_entry(&mut self.bytes[at..], value);
return true;
}
if !self.insert_at(index, value) {
return false;
}
self.remove_at(index + 1)
}
#[must_use]
pub fn split_off(&mut self, index: usize) -> Chunk {
let at = self.offset_of(index).unwrap_or(self.tail);
let rest = Chunk::adopt(&self.bytes[at..self.tail], self.count - index);
self.tail = at;
self.count = index;
rest
}
pub fn seal(&mut self) {
if self.head > 0 {
self.bytes.copy_within(self.head..self.tail, 0);
self.tail -= self.head;
self.head = 0;
}
self.bytes.truncate(self.tail);
self.bytes.shrink_to_fit();
}
#[must_use]
pub fn find(&self, value: &[u8], as_int: Option<i64>) -> Option<usize> {
crate::listpack::scan_for(&self.bytes[self.head..self.tail], value, as_int, 1)
}
pub fn find_each(
&self,
value: &[u8],
as_int: Option<i64>,
limit: usize,
hit: &mut dyn FnMut(usize) -> bool,
) -> usize {
crate::listpack::scan_each(&self.bytes[self.head..self.tail], value, as_int, limit, hit)
}
pub fn find_each_back(
&self,
value: &[u8],
as_int: Option<i64>,
limit: usize,
hit: &mut dyn FnMut(usize) -> bool,
) -> usize {
crate::listpack::scan_each_back(
&self.bytes[self.head..self.tail],
value,
as_int,
limit,
hit,
)
}
#[must_use]
pub fn iter(&self) -> Iter<'_> {
Iter {
bytes: &self.bytes[self.head..self.tail],
at: 0,
}
}
#[must_use]
pub fn iter_back(&self) -> RevIter<'_> {
RevIter {
bytes: &self.bytes[self.head..self.tail],
at: self.tail - self.head,
}
}
#[must_use]
pub fn iter_from(&self, index: usize) -> Iter<'_> {
let at = self.offset_of(index).unwrap_or(self.tail);
Iter {
bytes: &self.bytes[self.head..self.tail],
at: at - self.head,
}
}
fn offset_of(&self, index: usize) -> Option<usize> {
if index >= self.count {
return None;
}
if index * 2 <= self.count {
let mut at = self.head;
for _ in 0..index {
at += self.step(at)?;
}
return Some(at);
}
let mut end = self.tail;
for _ in index..self.count {
let len = read_backlen(&self.bytes[self.head..end])?;
end = end.checked_sub(len + backlen_len(len))?;
}
Some(end)
}
fn back_at(&self) -> Option<usize> {
if self.count == 0 {
return None;
}
let len = read_backlen(&self.bytes[self.head..self.tail])?;
self.tail.checked_sub(len + backlen_len(len))
}
fn step(&self, at: usize) -> Option<usize> {
let (_, len) = decode(&self.bytes[at..self.tail])?;
Some(len + backlen_len(len))
}
}
#[derive(Debug)]
pub struct Iter<'a> {
bytes: &'a [u8],
at: usize,
}
impl<'a> Iterator for Iter<'a> {
type Item = Entry<'a>;
#[inline]
fn next(&mut self) -> Option<Entry<'a>> {
if self.at >= self.bytes.len() {
return None;
}
let (entry, len) = decode(&self.bytes[self.at..])?;
self.at += len + backlen_len(len);
Some(entry)
}
}
#[derive(Debug)]
pub struct RevIter<'a> {
bytes: &'a [u8],
at: usize,
}
impl<'a> Iterator for RevIter<'a> {
type Item = Entry<'a>;
#[inline]
fn next(&mut self) -> Option<Entry<'a>> {
if self.at == 0 {
return None;
}
let len = read_backlen(&self.bytes[..self.at])?;
let start = self.at.checked_sub(len + backlen_len(len))?;
let (entry, _) = decode(&self.bytes[start..self.at])?;
self.at = start;
Some(entry)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn all(c: &Chunk) -> Vec<Vec<u8>> {
c.iter().map(|e| e.to_vec()).collect()
}
#[test]
fn a_back_chunk_fills_from_the_front_of_its_buffer() {
let mut c = Chunk::for_back();
assert!(c.push_back(b"a"));
assert!(c.push_back(b"b"));
assert_eq!(all(&c), vec![b"a".to_vec(), b"b".to_vec()]);
assert_eq!(c.len(), 2);
assert_eq!(c.front().unwrap().to_vec(), b"a");
assert_eq!(c.back().unwrap().to_vec(), b"b");
}
#[test]
fn a_front_chunk_fills_backward_and_reads_forward() {
let mut c = Chunk::for_front();
assert!(c.push_front(b"b"));
assert!(c.push_front(b"a"));
assert_eq!(all(&c), vec![b"a".to_vec(), b"b".to_vec()]);
assert_eq!(c.front().unwrap().to_vec(), b"a");
assert_eq!(c.back().unwrap().to_vec(), b"b");
}
#[test]
fn pushing_and_popping_at_both_ends_stays_in_order() {
let mut c = Chunk::for_back();
for m in [b"c", b"d"] {
assert!(c.push_back(m));
}
for m in [b"b", b"a"] {
assert!(c.push_front(m));
}
assert_eq!(
all(&c),
vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec(), b"d".to_vec()]
);
assert!(c.drop_front());
assert!(c.drop_back());
assert_eq!(all(&c), vec![b"b".to_vec(), b"c".to_vec()]);
assert_eq!(c.len(), 2);
}
#[test]
fn a_back_chunk_makes_room_at_the_front_once() {
let mut c = Chunk::for_back();
assert!(c.push_back(b"a"));
assert!(c.push_front(b"z"));
assert_eq!(all(&c), vec![b"z".to_vec(), b"a".to_vec()]);
let head = c.head;
for i in 0..100 {
assert!(c.push_front(i.to_string().as_bytes()), "at {i}");
}
assert!(c.head < head, "the front pushes went somewhere else");
assert_eq!(c.len(), 102);
assert_eq!(c.back().unwrap().to_vec(), b"a");
}
#[test]
fn a_full_chunk_refuses_both_ends() {
let mut c = Chunk::for_back();
let big = vec![b'x'; 500];
while c.push_back(&big) {}
assert!(!c.push_back(&big));
assert!(!c.push_front(&big));
assert!(c.push_front(b"1"), "there is still room for a short one");
}
#[test]
fn an_empty_chunk_answers_nothing_rather_than_panicking() {
let mut c = Chunk::for_back();
assert!(c.front().is_none());
assert!(c.back().is_none());
assert!(c.get(0).is_none());
assert!(!c.drop_front());
assert!(!c.drop_back());
}
#[test]
fn the_element_cap_is_what_stops_a_chunk_of_small_members() {
let mut c = Chunk::for_back();
for i in 0..CHUNK_ENTRIES {
assert!(c.push_back(i.to_string().as_bytes()), "at {i}");
}
assert!(!c.push_back(b"1"));
assert_eq!(c.len(), CHUNK_ENTRIES);
}
#[test]
fn the_byte_cap_is_what_stops_a_chunk_of_large_members() {
let mut c = Chunk::for_back();
let big = vec![b'x'; 300];
let mut n = 0;
while c.push_back(&big) {
n += 1;
}
assert!(n < CHUNK_ENTRIES, "{n} entries fitted, which is too many");
assert_eq!(c.len(), n);
assert!(c.live_bytes() <= CHUNK_BYTES);
}
#[test]
fn indexing_walks_from_the_head_cursor_and_not_from_the_buffer() {
let mut c = Chunk::for_front();
for m in [b"d", b"c", b"b", b"a"] {
assert!(c.push_front(m));
}
for (i, want) in [b"a", b"b", b"c", b"d"].iter().enumerate() {
assert_eq!(c.get(i).unwrap().to_vec(), want.to_vec(), "at {i}");
}
assert!(c.get(4).is_none());
}
#[test]
fn sealing_keeps_the_elements_and_gives_back_the_room() {
let mut c = Chunk::for_front();
for m in [b"c", b"b", b"a"] {
assert!(c.push_front(m));
}
let before = c.memory_bytes();
let live = c.live_bytes();
c.seal();
assert_eq!(
all(&c),
vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()],
"sealing changed what is in it"
);
assert_eq!(c.live_bytes(), live);
assert!(c.memory_bytes() < before);
assert_eq!(c.front().unwrap().to_vec(), b"a");
assert_eq!(c.back().unwrap().to_vec(), b"c");
assert_eq!(c.get(1).unwrap().to_vec(), b"b");
assert!(!c.push_back(b"d"));
assert!(!c.push_front(b"d"));
assert!(c.drop_front());
assert_eq!(c.front().unwrap().to_vec(), b"b");
}
#[test]
fn integers_come_back_as_integers() {
let mut c = Chunk::for_back();
assert!(c.push_back(b"42"));
assert!(c.push_back(b"007"));
assert_eq!(c.get(0), Some(Entry::Int(42)));
assert_eq!(c.get(1), Some(Entry::Str(b"007")));
}
#[test]
fn the_back_walk_survives_a_long_entry() {
for len in [1usize, 63, 64, 120, 126, 127, 128, 200] {
let mut c = Chunk::for_back();
let v = vec![b'x'; len];
assert!(c.push_back(b"first"));
assert!(c.push_back(&v), "{len} did not fit");
assert_eq!(c.back().unwrap().to_vec(), v, "back of a {len} byte entry");
assert!(c.drop_back());
assert_eq!(c.back().unwrap().to_vec(), b"first");
}
}
fn abcde() -> Chunk {
let mut c = Chunk::for_back();
for m in [b"c", b"d", b"e"] {
assert!(c.push_back(m));
}
for m in [b"b", b"a"] {
assert!(c.push_front(m));
}
c
}
#[test]
fn an_insert_lands_where_it_was_asked_to_from_either_side() {
for at in 0..=5 {
let mut c = abcde();
assert!(c.insert_at(at, b"new"), "inserting at {at}");
let mut want: Vec<Vec<u8>> = [b"a", b"b", b"c", b"d", b"e"]
.iter()
.map(|m| m.to_vec())
.collect();
want.insert(at, b"new".to_vec());
assert_eq!(all(&c), want, "inserting at {at}");
assert_eq!(c.len(), 6);
}
let mut c = abcde();
assert!(!c.insert_at(6, b"new"), "past the end is not an insert");
}
#[test]
fn a_remove_closes_the_gap_from_either_side() {
for at in 0..5 {
let mut c = abcde();
assert!(c.remove_at(at), "removing at {at}");
let mut want: Vec<Vec<u8>> = [b"a", b"b", b"c", b"d", b"e"]
.iter()
.map(|m| m.to_vec())
.collect();
want.remove(at);
assert_eq!(all(&c), want, "removing at {at}");
assert_eq!(c.len(), 4);
assert_eq!(c.back().unwrap().to_vec(), want[3]);
}
let mut c = abcde();
assert!(!c.remove_at(5));
}
#[test]
fn a_replace_takes_a_value_of_any_length() {
for value in [&b"z"[..], &b"much longer than what was there"[..], b"7"] {
for at in 0..5 {
let mut c = abcde();
assert!(c.replace_at(at, value), "replacing at {at}");
let mut want: Vec<Vec<u8>> = [b"a", b"b", b"c", b"d", b"e"]
.iter()
.map(|m| m.to_vec())
.collect();
want[at] = value.to_vec();
assert_eq!(all(&c), want, "replacing at {at}");
assert_eq!(c.len(), 5);
}
}
let mut c = abcde();
assert!(!c.replace_at(5, b"z"));
}
#[test]
fn dropping_many_from_an_end_is_the_walk_and_nothing_else() {
let mut c = abcde();
assert_eq!(c.drop_front_n(2), 2);
assert_eq!(all(&c), vec![b"c".to_vec(), b"d".to_vec(), b"e".to_vec()]);
assert_eq!(c.drop_back_n(2), 2);
assert_eq!(all(&c), vec![b"c".to_vec()]);
assert_eq!(c.drop_front_n(9), 1, "it stops when it runs out");
assert!(c.is_empty());
assert_eq!(c.drop_back_n(3), 0);
}
#[test]
fn a_split_leaves_both_halves_readable_and_with_room() {
for at in 0..=5 {
let mut c = abcde();
let mut rest = c.split_off(at);
let want: Vec<Vec<u8>> = [b"a", b"b", b"c", b"d", b"e"]
.iter()
.map(|m| m.to_vec())
.collect();
assert_eq!(all(&c), want[..at].to_vec(), "the front half of {at}");
assert_eq!(all(&rest), want[at..].to_vec(), "the back half of {at}");
assert_eq!(c.len() + rest.len(), 5);
assert!(rest.push_back(b"more"), "the back half has no room");
assert_eq!(rest.back().unwrap().to_vec(), b"more");
}
}
#[test]
fn a_full_chunk_refuses_an_insert_and_a_split_fixes_it() {
let mut c = Chunk::for_back();
let big = vec![b'x'; 500];
while c.push_back(&big) {}
let held = c.len();
assert!(!c.insert_at(held / 2, &big));
let mut rest = c.split_off(held / 2);
assert!(c.push_back(&big) || rest.push_front(&big));
assert_eq!(c.len() + rest.len(), held + 1);
}
}