use super::Error;
use crate::bitmap::{historical::BitMap, Prunable};
#[cfg(not(feature = "std"))]
use alloc::{collections::BTreeMap, vec::Vec};
#[cfg(feature = "std")]
use std::collections::BTreeMap;
pub(super) struct Batch<const N: usize> {
pub(super) base_len: u64,
pub(super) base_pruned_chunks: usize,
pub(super) projected_len: u64,
pub(super) projected_pruned_chunks: usize,
pub(super) modified_bits: BTreeMap<u64, bool>,
pub(super) appended_bits: Vec<bool>,
pub(super) chunks_to_prune: BTreeMap<usize, [u8; N]>,
}
#[must_use = "batches must be committed or explicitly dropped"]
pub struct BatchGuard<'a, const N: usize> {
pub(super) bitmap: &'a mut BitMap<N>,
pub(super) committed: bool,
}
impl<'a, const N: usize> BatchGuard<'a, N> {
#[inline]
pub const fn len(&self) -> u64 {
self.bitmap
.active_batch
.as_ref()
.expect("active batch must exist since we have this guard")
.projected_len
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub const fn pruned_chunks(&self) -> usize {
self.bitmap
.active_batch
.as_ref()
.expect("active batch must exist since we have this guard")
.projected_pruned_chunks
}
pub fn get_bit(&self, bit: u64) -> bool {
let batch = self.bitmap.active_batch.as_ref().unwrap();
assert!(
bit < batch.projected_len,
"bit offset {bit} out of bounds (len: {})",
batch.projected_len
);
let chunk_idx = Prunable::<N>::unpruned_chunk(bit);
assert!(
chunk_idx >= batch.projected_pruned_chunks,
"cannot get bit {bit}: chunk {chunk_idx} is pruned (pruned up to chunk {})",
batch.projected_pruned_chunks
);
let appended_start = batch.projected_len - batch.appended_bits.len() as u64;
if bit >= appended_start {
let append_offset = (bit - appended_start) as usize;
return batch.appended_bits[append_offset];
}
if let Some(&value) = batch.modified_bits.get(&bit) {
return value;
}
self.bitmap.current.get_bit(bit)
}
pub fn get_chunk(&self, bit: u64) -> [u8; N] {
let batch = self.bitmap.active_batch.as_ref().unwrap();
assert!(
bit < batch.projected_len,
"bit offset {bit} out of bounds (len: {})",
batch.projected_len
);
let chunk_idx = Prunable::<N>::unpruned_chunk(bit);
assert!(
chunk_idx >= batch.projected_pruned_chunks,
"cannot get chunk at bit offset {bit}: chunk {chunk_idx} is pruned (pruned up to chunk {})",
batch.projected_pruned_chunks
);
let chunk_start_bit = chunk_idx as u64 * Prunable::<N>::CHUNK_SIZE_BITS;
let chunk_end_bit = chunk_start_bit + Prunable::<N>::CHUNK_SIZE_BITS;
let appended_start = batch.projected_len - batch.appended_bits.len() as u64;
let chunk_entirely_past_end = chunk_start_bit >= batch.projected_len;
let chunk_entirely_before_changes =
chunk_end_bit <= appended_start && chunk_end_bit <= batch.projected_len;
let chunk_needs_reconstruction =
!(chunk_entirely_past_end || chunk_entirely_before_changes)
|| (chunk_start_bit..chunk_end_bit.min(batch.base_len))
.any(|bit| batch.modified_bits.contains_key(&bit));
if chunk_needs_reconstruction {
self.reconstruct_modified_chunk(chunk_start_bit)
} else {
*self.bitmap.current.get_chunk_containing(bit)
}
}
fn reconstruct_modified_chunk(&self, chunk_start: u64) -> [u8; N] {
let batch = self.bitmap.active_batch.as_ref().unwrap();
let mut chunk = if chunk_start < self.bitmap.current.len() {
*self.bitmap.current.get_chunk_containing(chunk_start)
} else {
[0u8; N]
};
let appended_start = batch.projected_len - batch.appended_bits.len() as u64;
for bit_in_chunk in 0..Prunable::<N>::CHUNK_SIZE_BITS {
let bit = chunk_start + bit_in_chunk;
let byte_idx = (bit_in_chunk / 8) as usize;
let bit_idx = bit_in_chunk % 8;
let mask = 1u8 << bit_idx;
if bit >= batch.projected_len {
chunk[byte_idx] &= !mask;
} else if let Some(&value) = batch.modified_bits.get(&bit) {
if value {
chunk[byte_idx] |= mask;
} else {
chunk[byte_idx] &= !mask;
}
} else if bit >= appended_start {
let append_offset = (bit - appended_start) as usize;
if append_offset < batch.appended_bits.len() {
let value = batch.appended_bits[append_offset];
if value {
chunk[byte_idx] |= mask;
} else {
chunk[byte_idx] &= !mask;
}
}
}
}
chunk
}
pub fn set_bit(&mut self, bit: u64, value: bool) -> &mut Self {
let batch = self.bitmap.active_batch.as_mut().unwrap();
assert!(
bit < batch.projected_len,
"cannot set bit {bit}: out of bounds (len: {})",
batch.projected_len
);
let chunk_idx = Prunable::<N>::unpruned_chunk(bit);
assert!(
chunk_idx >= batch.projected_pruned_chunks,
"cannot set bit {bit}: chunk {chunk_idx} is pruned (pruned up to chunk {})",
batch.projected_pruned_chunks
);
let appended_start = batch.projected_len - batch.appended_bits.len() as u64;
if bit >= appended_start {
let append_offset = (bit - appended_start) as usize;
batch.appended_bits[append_offset] = value;
} else {
batch.modified_bits.insert(bit, value);
}
self
}
pub fn push(&mut self, bit: bool) -> &mut Self {
let batch = self.bitmap.active_batch.as_mut().unwrap();
batch.appended_bits.push(bit);
batch.projected_len += 1;
self
}
pub fn push_byte(&mut self, byte: u8) -> &mut Self {
for i in 0..8 {
let bit = (byte >> i) & 1 == 1;
self.push(bit);
}
self
}
pub fn push_chunk(&mut self, chunk: &[u8; N]) -> &mut Self {
for byte in chunk {
self.push_byte(*byte);
}
self
}
pub fn pop(&mut self) -> bool {
let batch = self.bitmap.active_batch.as_mut().unwrap();
assert!(batch.projected_len > 0, "cannot pop from empty bitmap");
let old_projected_len = batch.projected_len;
batch.projected_len -= 1;
let bit = batch.projected_len;
let appended_start = old_projected_len - batch.appended_bits.len() as u64;
if bit >= appended_start {
batch.appended_bits.pop().unwrap()
} else {
if let Some(&modified_value) = batch.modified_bits.get(&bit) {
batch.modified_bits.remove(&bit);
modified_value
} else {
self.bitmap.current.get_bit(bit)
}
}
}
pub fn prune_to_bit(&mut self, bit: u64) -> &mut Self {
let batch = self.bitmap.active_batch.as_mut().unwrap();
assert!(
bit <= batch.projected_len,
"cannot prune to bit {bit}: beyond projected length ({})",
batch.projected_len
);
let chunk_num = Prunable::<N>::unpruned_chunk(bit);
if chunk_num <= batch.projected_pruned_chunks {
return self; }
let current_pruned = self.bitmap.current.pruned_chunks();
for chunk_idx in batch.projected_pruned_chunks..chunk_num {
if batch.chunks_to_prune.contains_key(&chunk_idx) {
continue; }
assert!(
chunk_idx >= current_pruned,
"attempting to prune chunk {chunk_idx} which is already pruned (current pruned_chunks={current_pruned})",
);
let bitmap_idx = chunk_idx - current_pruned;
let chunk_data = if bitmap_idx < self.bitmap.current.chunks_len() {
*self.bitmap.current.get_chunk(bitmap_idx)
} else {
let chunk_start_bit = chunk_idx as u64 * Prunable::<N>::CHUNK_SIZE_BITS;
let appended_start = batch.projected_len - batch.appended_bits.len() as u64;
let mut chunk = [0u8; N];
for bit_in_chunk in 0..Prunable::<N>::CHUNK_SIZE_BITS {
let bit = chunk_start_bit + bit_in_chunk;
if bit >= batch.projected_len {
break;
}
if bit >= appended_start {
let append_idx = (bit - appended_start) as usize;
if append_idx < batch.appended_bits.len() && batch.appended_bits[append_idx]
{
let byte_idx = (bit_in_chunk / 8) as usize;
let bit_idx = bit_in_chunk % 8;
chunk[byte_idx] |= 1u8 << bit_idx;
}
}
}
chunk
};
batch.chunks_to_prune.insert(chunk_idx, chunk_data);
}
batch.projected_pruned_chunks = chunk_num;
self
}
pub fn commit(mut self, commit_number: u64) -> Result<(), Error> {
if commit_number == u64::MAX {
return Err(Error::ReservedCommitNumber);
}
if let Some(&max_commit) = self.bitmap.commits.keys().next_back() {
if commit_number <= max_commit {
return Err(Error::NonMonotonicCommit {
previous: max_commit,
attempted: commit_number,
});
}
}
let batch = self.bitmap.active_batch.take().unwrap();
let reverse_diff = self.bitmap.build_reverse_diff(&batch);
self.bitmap.apply_batch_to_current(&batch);
self.bitmap.commits.insert(commit_number, reverse_diff);
self.committed = true;
Ok(())
}
}
impl<'a, const N: usize> Drop for BatchGuard<'a, N> {
fn drop(&mut self) {
if !self.committed {
self.bitmap.active_batch = None;
}
}
}