use std::collections::HashMap;
pub(crate) fn put_varint(out: &mut Vec<u8>, mut v: u32) {
loop {
let byte = (v & 0x7f) as u8;
v >>= 7;
if v == 0 {
out.push(byte);
return;
}
out.push(byte | 0x80);
}
}
pub(crate) fn get_varints(blob: &[u8]) -> Vec<u32> {
let mut out = Vec::new();
let mut cur = 0u32;
let mut shift = 0u32;
for &b in blob {
cur |= u32::from(b & 0x7f) << shift;
if b & 0x80 == 0 {
out.push(cur);
cur = 0;
shift = 0;
} else {
shift += 7;
}
}
out
}
#[derive(Debug)]
pub(crate) enum DocBlobs {
One { id: u32, blob: Vec<u8> },
Many(HashMap<u32, Vec<u8>>),
}
impl DocBlobs {
pub(crate) fn set(&mut self, id: u32, blob: Vec<u8>) {
match self {
DocBlobs::One { id: id0, blob: b0 } => {
if *id0 == id {
*b0 = blob;
} else {
let mut m = HashMap::with_capacity(2);
m.insert(*id0, std::mem::take(b0));
m.insert(id, blob);
*self = DocBlobs::Many(m);
}
}
DocBlobs::Many(m) => {
m.insert(id, blob);
}
}
}
pub(crate) fn get(&self, id: u32) -> Option<&[u8]> {
match self {
DocBlobs::One { id: id0, blob } => (*id0 == id).then_some(blob.as_slice()),
DocBlobs::Many(m) => m.get(&id).map(Vec::as_slice),
}
}
pub(crate) fn ids(&self) -> Vec<u32> {
match self {
DocBlobs::One { id, .. } => vec![*id],
DocBlobs::Many(m) => m.keys().copied().collect(),
}
}
pub(crate) fn each(&self) -> Vec<(u32, &[u8])> {
match self {
DocBlobs::One { id, blob } => vec![(*id, blob.as_slice())],
DocBlobs::Many(m) => m.iter().map(|(id, b)| (*id, b.as_slice())).collect(),
}
}
pub(crate) fn remove(&mut self, id: u32) -> bool {
match self {
DocBlobs::One { id: id0, .. } => *id0 == id,
DocBlobs::Many(m) => {
m.remove(&id);
m.is_empty()
}
}
}
pub(crate) fn approx_bytes(&self) -> u64 {
match self {
DocBlobs::One { blob, .. } => blob_alloc(blob),
DocBlobs::Many(m) => {
let n = m.len() as u64;
let cap = (n * 8 / 7 + 1).next_power_of_two().max(4);
cap * 33 + m.values().map(|b| blob_alloc(b)).sum::<u64>()
}
}
}
}
pub(crate) fn blob_alloc(b: &[u8]) -> u64 {
(b.len().max(1) as u64).next_multiple_of(16) + 16
}
pub(crate) fn channel_bytes(map: &HashMap<Vec<u8>, DocBlobs>) -> u64 {
map.iter()
.map(|(t, db)| t.len() as u64 + 24 + 56 + db.approx_bytes())
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn varints_roundtrip() {
for case in [
vec![],
vec![0u32],
vec![1, 2, 3],
vec![5, 127, 128, 129, 16_383, 16_384, 2_000_000],
] {
let mut blob = Vec::new();
for &v in &case {
put_varint(&mut blob, v);
}
assert_eq!(get_varints(&blob), case, "roundtrip {case:?}");
}
}
#[test]
fn one_promotes_to_many_and_each_sees_both() {
let mut db = DocBlobs::One { id: 7, blob: vec![1] };
assert_eq!(db.get(7), Some(&[1u8][..]));
db.set(7, vec![2]);
assert_eq!(db.get(7), Some(&[2u8][..]), "same id overwrites in place");
db.set(9, vec![3]);
let mut got = db.each();
got.sort_by_key(|(id, _)| *id);
assert_eq!(got, vec![(7, &[2u8][..]), (9, &[3u8][..])]);
assert!(!db.remove(7), "one of two left");
assert!(db.remove(9), "last document gone");
}
}