use crate::Error;
use crate::Reset;
use std::vec::Vec;
#[inline]
fn word_bit(i: usize) -> (usize, usize) {
(i / 64, i % 64)
}
pub struct Vector<V> {
data: Vec<V>,
dirty: Vec<u64>,
valid: Vec<u64>,
dirty_count: usize,
}
impl<V> Default for Vector<V> {
fn default() -> Self {
Self::new()
}
}
impl<V> Vector<V> {
pub fn new() -> Self {
Self {
data: Vec::new(),
dirty: Vec::new(),
valid: Vec::new(),
dirty_count: 0,
}
}
pub fn with_capacity(capacity: usize) -> Self {
let words = capacity.div_ceil(64);
Self {
data: Vec::with_capacity(capacity),
dirty: Vec::with_capacity(words),
valid: Vec::with_capacity(words),
dirty_count: 0,
}
}
pub fn from_vec(data: Vec<V>) -> Self {
let len = data.len();
let words = len.div_ceil(64);
let mut valid = vec![0u64; words];
let full_words = len / 64;
let rem = len % 64;
for w in &mut valid[..full_words] {
*w = u64::MAX;
}
if rem != 0 {
valid[full_words] = (1u64 << rem) - 1;
}
Self {
data,
dirty: vec![0u64; words],
valid,
dirty_count: 0,
}
}
pub fn from_fill(value: V, len: usize) -> Self
where
V: Clone,
{
Self::from_vec(vec![value; len])
}
pub fn with_invalid_slots(len: usize) -> Self
where
V: Default,
{
let words = len.div_ceil(64);
let mut data = Vec::with_capacity(len);
data.resize_with(len, V::default);
Self {
data,
dirty: vec![0u64; words],
valid: vec![0u64; words],
dirty_count: 0,
}
}
#[inline]
pub fn as_slice(&self) -> &[V] {
&self.data
}
#[inline]
pub fn is_updated(&self) -> bool {
self.dirty_count != 0
}
#[inline]
pub fn all_updated(&self) -> bool {
!self.data.is_empty() && self.dirty_count == self.data.len()
}
pub fn clear(&mut self) {
self.data.clear();
self.dirty.clear();
self.valid.clear();
self.dirty_count = 0;
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn iter(&self) -> std::slice::Iter<'_, V> {
self.data.iter()
}
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, V> {
let len = self.data.len();
if self.dirty_count < len {
let full_words = len / 64;
let rem = len % 64;
for w in 0..full_words {
self.dirty[w] = u64::MAX;
}
if rem != 0 {
self.dirty[full_words] = (1u64 << rem) - 1;
}
self.dirty_count = len;
}
self.data.iter_mut()
}
#[inline]
fn mark_dirty_internal(&mut self, index: usize) {
let (w, b) = word_bit(index);
let mask = 1u64 << b;
if (self.dirty[w] & mask) == 0 {
self.dirty[w] |= mask;
self.dirty_count += 1;
}
}
#[inline]
fn set_valid_internal(&mut self, index: usize, value: bool) {
let (w, b) = word_bit(index);
let mask = 1u64 << b;
if value {
self.valid[w] |= mask;
} else {
self.valid[w] &= !mask;
}
}
#[inline]
fn extend_bitsets_for_last_push(&mut self) {
let new_idx = self.data.len() - 1;
if new_idx.is_multiple_of(64) {
self.dirty.push(0);
self.valid.push(0);
}
}
#[inline]
pub fn get_mut_untracked(&mut self, index: usize) -> Option<&mut V> {
self.data.get_mut(index)
}
pub fn push(&mut self, value: V) {
self.data.push(value);
self.extend_bitsets_for_last_push();
let idx = self.data.len() - 1;
self.mark_dirty_internal(idx);
}
pub fn push_committed(&mut self, value: V) {
self.data.push(value);
self.extend_bitsets_for_last_push();
let idx = self.data.len() - 1;
self.set_valid_internal(idx, true);
self.mark_dirty_internal(idx);
}
pub fn pop(&mut self) -> Option<V> {
if self.data.is_empty() {
return None;
}
let idx = self.data.len() - 1;
let (w, b) = word_bit(idx);
let mask = 1u64 << b;
if (self.dirty[w] & mask) != 0 {
self.dirty_count -= 1;
}
self.dirty[w] &= !mask;
self.valid[w] &= !mask;
if idx.is_multiple_of(64) {
self.dirty.pop();
self.valid.pop();
}
self.data.pop()
}
#[inline]
pub fn is_valid(&self, index: usize) -> bool {
if index >= self.data.len() {
return false;
}
let (w, b) = word_bit(index);
(self.valid[w] & (1u64 << b)) != 0
}
#[inline]
pub fn is_updated_at(&self, index: usize) -> bool {
if index >= self.data.len() {
return false;
}
let (w, b) = word_bit(index);
(self.dirty[w] & (1u64 << b)) != 0
}
#[inline]
pub fn get_valid(&self, index: usize) -> Option<&V> {
if self.is_valid(index) {
self.data.get(index)
} else {
None
}
}
pub fn commit(&mut self, index: usize, value: V) {
assert!(
index < self.data.len(),
"Vector::commit: index {} out of bounds (len = {})",
index,
self.data.len()
);
self.data[index] = value;
self.set_valid_internal(index, true);
self.mark_dirty_internal(index);
}
pub fn update<F>(&mut self, index: usize, f: F)
where
F: FnOnce(&mut V),
{
assert!(
index < self.data.len(),
"Vector::update: index {} out of bounds (len = {})",
index,
self.data.len()
);
f(&mut self.data[index]);
self.set_valid_internal(index, true);
self.mark_dirty_internal(index);
}
pub fn invalidate(&mut self, index: usize) {
assert!(
index < self.data.len(),
"Vector::invalidate: index {} out of bounds (len = {})",
index,
self.data.len()
);
self.set_valid_internal(index, false);
self.mark_dirty_internal(index);
}
#[must_use = "the returned SlotWriter must be consumed via commit() or invalidate()"]
pub fn slot_writer(&mut self, index: usize) -> SlotWriter<'_, V> {
assert!(
index < self.data.len(),
"Vector::slot_writer: index {} out of bounds (len = {})",
index,
self.data.len()
);
SlotWriter {
vector: self,
index,
consumed: false,
}
}
}
impl<V> Reset for Vector<V> {
type Error = Error;
fn reset(&mut self) -> Result<(), Error> {
for w in &mut self.dirty {
*w = 0;
}
self.dirty_count = 0;
Ok(())
}
}
impl<V> crate::Updated for Vector<V> {
fn is_updated(&self) -> bool {
self.is_updated() }
}
#[must_use = "SlotWriter must be consumed via commit() or invalidate()"]
pub struct SlotWriter<'a, V> {
vector: &'a mut Vector<V>,
index: usize,
consumed: bool,
}
impl<'a, V> SlotWriter<'a, V> {
pub fn commit(mut self, value: V) {
self.vector.commit(self.index, value);
self.consumed = true;
}
pub fn invalidate(mut self) {
self.vector.invalidate(self.index);
self.consumed = true;
}
}
impl<'a, V> Drop for SlotWriter<'a, V> {
fn drop(&mut self) {
if !self.consumed {
debug_assert!(
false,
"SlotWriter at index {} dropped without commit() or invalidate()",
self.index
);
}
}
}
impl<V> Vector<V> {
pub fn iter_updated_valid(&self) -> IterUpdatedValidItems<'_, V> {
IterUpdatedValidItems::new(self)
}
pub fn iter_updated_indices(&self) -> IterUpdatedIndices<'_> {
IterUpdatedIndices::new(&self.dirty, self.data.len())
}
}
pub struct IterUpdatedValidItems<'a, V> {
vector: &'a Vector<V>,
word_idx: usize,
word: u64,
}
impl<'a, V> IterUpdatedValidItems<'a, V> {
fn new(v: &'a Vector<V>) -> Self {
let mut it = Self {
vector: v,
word_idx: 0,
word: 0,
};
it.advance_to_nonzero();
it
}
fn advance_to_nonzero(&mut self) {
while self.word == 0 && self.word_idx < self.vector.dirty.len() {
let mut w = self.vector.dirty[self.word_idx];
if self.word_idx + 1 == self.vector.dirty.len() {
let rem = self.vector.data.len() % 64;
if rem != 0 {
w &= (1u64 << rem) - 1;
}
}
self.word = w;
if self.word != 0 {
break;
}
self.word_idx += 1;
}
}
}
impl<'a, V> Iterator for IterUpdatedValidItems<'a, V> {
type Item = (usize, Option<&'a V>);
fn next(&mut self) -> Option<Self::Item> {
if self.word == 0 {
self.advance_to_nonzero();
if self.word == 0 {
return None;
}
}
let tz = self.word.trailing_zeros() as usize;
let idx = self.word_idx * 64 + tz;
self.word &= self.word - 1;
let opt = if self.vector.is_valid(idx) {
Some(&self.vector.data[idx])
} else {
None
};
if self.word == 0 {
self.word_idx += 1;
}
Some((idx, opt))
}
}
pub struct IterUpdatedIndices<'a> {
dirty: &'a [u64],
total_len: usize,
word_idx: usize,
word: u64,
}
impl<'a> IterUpdatedIndices<'a> {
fn new(dirty: &'a [u64], total_len: usize) -> Self {
let mut it = Self {
dirty,
total_len,
word_idx: 0,
word: 0,
};
it.advance_to_nonzero();
it
}
fn advance_to_nonzero(&mut self) {
while self.word == 0 && self.word_idx < self.dirty.len() {
let mut w = self.dirty[self.word_idx];
if self.word_idx + 1 == self.dirty.len() {
let rem = self.total_len % 64;
if rem != 0 {
w &= (1u64 << rem) - 1;
}
}
self.word = w;
if self.word != 0 {
break;
}
self.word_idx += 1;
}
}
}
impl<'a> Iterator for IterUpdatedIndices<'a> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.word == 0 {
self.advance_to_nonzero();
if self.word == 0 {
return None;
}
}
let tz = self.word.trailing_zeros() as usize;
let idx = self.word_idx * 64 + tz;
self.word &= self.word - 1;
if self.word == 0 {
self.word_idx += 1;
}
Some(idx)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_vector() {
let vec: Vector<i32> = Vector::new();
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
assert!(!vec.is_updated());
assert!(!vec.all_updated());
}
#[test]
fn test_with_capacity() {
let mut vec: Vector<i32> = Vector::with_capacity(10);
assert_eq!(vec.len(), 0);
assert_eq!(vec.data.capacity(), 10);
assert!(vec.dirty.capacity() >= 1);
assert!(vec.valid.capacity() >= 1);
assert_eq!(vec.dirty.len(), 0);
assert_eq!(vec.valid.len(), 0);
assert!(!vec.is_valid(0));
vec.push_committed(42);
assert_eq!(vec.get_valid(0), Some(&42));
assert_eq!(vec.pop(), Some(42));
assert_eq!(vec.len(), 0);
assert!(!vec.is_valid(0));
}
#[test]
fn get_mut_untracked_does_not_mark_updated() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push_committed(String::from("a"));
vec.push_committed(String::from("b"));
vec.reset()?;
if let Some(value) = vec.get_mut_untracked(1) {
value.clear();
}
assert_eq!(vec.get_valid(1).map(String::as_str), Some(""));
assert!(!vec.is_updated());
assert!(!vec.all_updated());
assert!(vec.iter_updated_valid().next().is_none());
Ok(())
}
#[test]
fn reset_clears_dirty_preserves_data() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push_committed(2);
vec.push_committed(3);
assert!(vec.is_updated());
vec.reset()?;
assert!(!vec.is_updated());
assert!(!vec.all_updated());
assert_eq!(vec.iter_updated_valid().count(), 0);
assert_eq!(vec.get_valid(0), Some(&1));
assert_eq!(vec.get_valid(1), Some(&2));
assert_eq!(vec.get_valid(2), Some(&3));
vec.commit(1, 20);
assert!(vec.is_updated());
assert_eq!(vec.get_valid(1), Some(&20));
Ok(())
}
#[test]
fn clear_drops_all_state() {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push_committed(2);
vec.push_committed(3);
vec.clear();
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
assert!(!vec.is_updated());
assert!(!vec.all_updated());
}
#[test]
fn pop_drops_last_slot_and_its_bits() {
let mut vec = Vector::new();
vec.push_committed(10);
vec.push_committed(20);
vec.push_committed(30);
let val = vec.pop();
assert_eq!(val, Some(30));
assert_eq!(vec.len(), 2);
assert!(vec.is_updated());
vec.pop();
vec.pop();
assert!(vec.is_empty());
assert!(!vec.is_updated());
}
#[test]
fn out_of_bounds_returns_none() {
let mut vec = Vector::new();
vec.push_committed(1);
assert_eq!(vec.get_valid(1), None);
assert!(!vec.is_valid(1));
}
#[test]
fn iter_and_iter_mut_walk_raw_data() {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push_committed(2);
vec.push_committed(3);
let collected: Vec<&i32> = vec.iter().collect();
assert_eq!(collected, vec![&1, &2, &3]);
for val in vec.iter_mut() {
*val *= 2;
}
let collected: Vec<&i32> = vec.iter().collect();
assert_eq!(collected, vec![&2, &4, &6]);
}
#[test]
fn push_after_all_updated_extends_correctly() {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push_committed(2);
vec.push(3);
assert!(vec.is_updated());
let updated: Vec<(usize, Option<i32>)> = vec
.iter_updated_valid()
.map(|(i, v)| (i, v.copied()))
.collect();
assert_eq!(updated, vec![(0, Some(1)), (1, Some(2)), (2, None)]);
}
#[test]
fn commit_marks_all_slots_dirty() {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push_committed(2);
vec.push_committed(3);
vec.commit(0, 10);
vec.commit(1, 20);
vec.commit(2, 30);
let updated: Vec<(usize, i32)> = vec
.iter_updated_valid()
.map(|(i, v)| (i, *v.unwrap()))
.collect();
assert_eq!(updated, vec![(0, 10), (1, 20), (2, 30)]);
}
#[test]
fn reset_after_committed_pushes_keeps_validity() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push_committed(2);
vec.reset()?;
assert!(!vec.is_updated());
assert_eq!(vec.iter_updated_valid().count(), 0);
assert_eq!(vec.get_valid(0), Some(&1));
assert_eq!(vec.get_valid(1), Some(&2));
vec.commit(0, 10);
let updated: Vec<(usize, i32)> = vec
.iter_updated_valid()
.map(|(i, v)| (i, *v.unwrap()))
.collect();
assert_eq!(updated, vec![(0, 10)]);
Ok(())
}
#[test]
fn push_committed_reads_back_via_get_valid() {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push_committed(2);
vec.push_committed(3);
assert_eq!(vec.len(), 3);
assert_eq!(vec.get_valid(0), Some(&1));
assert_eq!(vec.get_valid(1), Some(&2));
assert_eq!(vec.get_valid(2), Some(&3));
assert_eq!(vec.get_valid(3), None);
assert!(vec.is_updated());
}
#[test]
fn validity_push_is_invalid_by_default() {
let mut vec = Vector::new();
vec.push(42);
assert!(!vec.is_valid(0));
assert_eq!(vec.get_valid(0), None);
assert!(!vec.is_valid(1));
assert_eq!(vec.get_valid(1), None);
}
#[test]
fn validity_push_committed_is_valid() {
let mut vec = Vector::new();
vec.push_committed(42);
assert!(vec.is_valid(0));
assert_eq!(vec.get_valid(0), Some(&42));
assert!(vec.is_updated());
}
#[test]
fn validity_commit_roundtrip() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push(0); assert!(!vec.is_valid(0));
vec.commit(0, 42);
assert!(vec.is_valid(0));
assert_eq!(vec.get_valid(0), Some(&42));
assert!(vec.is_updated());
vec.reset()?;
assert!(!vec.is_updated());
assert!(vec.is_valid(0));
assert_eq!(vec.get_valid(0), Some(&42));
Ok(())
}
#[test]
fn validity_invalidate_marks_dirty_and_clears_valid() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push_committed(42);
vec.reset()?;
assert!(!vec.is_updated());
vec.invalidate(0);
assert!(!vec.is_valid(0));
assert_eq!(vec.get_valid(0), None);
assert!(vec.is_updated());
Ok(())
}
#[test]
fn validity_reset_preserves_validity_clears_dirty() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push(0);
vec.commit(0, 100);
vec.reset()?;
assert!(!vec.is_updated());
assert!(vec.is_valid(0)); assert_eq!(vec.get_valid(0), Some(&100));
Ok(())
}
#[test]
fn validity_slot_writer_commit_consumes_without_panic() {
let mut vec = Vector::new();
vec.push(0);
let writer = vec.slot_writer(0);
writer.commit(99);
assert_eq!(vec.get_valid(0), Some(&99));
}
#[test]
fn validity_slot_writer_invalidate_consumes_without_panic() {
let mut vec = Vector::new();
vec.push_committed(123);
let writer = vec.slot_writer(0);
writer.invalidate();
assert_eq!(vec.get_valid(0), None);
assert!(vec.is_updated());
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "SlotWriter at index 0 dropped without commit() or invalidate()")]
fn validity_slot_writer_drop_without_consume_panics_in_debug() {
let mut vec: Vector<i32> = Vector::new();
vec.push(0);
let _writer = vec.slot_writer(0);
}
#[test]
fn validity_iter_updated_valid_yields_option_per_slot() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push(0); vec.push(0); vec.push(0); vec.commit(0, 10);
vec.commit(1, 20);
let collected: Vec<(usize, Option<i32>)> = vec
.iter_updated_valid()
.map(|(i, v)| (i, v.copied()))
.collect();
assert_eq!(
collected,
vec![(0, Some(10)), (1, Some(20)), (2, None)],
"all three slots are dirty; only the two committed ones expose Some(v)"
);
vec.reset()?;
let collected: Vec<(usize, Option<i32>)> = vec
.iter_updated_valid()
.map(|(i, v)| (i, v.copied()))
.collect();
assert!(collected.is_empty(), "after reset no slots are dirty");
vec.invalidate(0);
let collected: Vec<(usize, Option<i32>)> = vec
.iter_updated_valid()
.map(|(i, v)| (i, v.copied()))
.collect();
assert_eq!(collected, vec![(0, None)]);
Ok(())
}
#[test]
fn update_mutates_in_place_and_marks_valid_dirty() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push_committed(vec![1, 2, 3]);
vec.reset()?;
assert!(!vec.is_updated());
let mut prior = None;
vec.update(0, |v| {
prior = Some(v.clone());
v.push(4);
});
assert_eq!(prior, Some(vec![1, 2, 3]));
assert!(vec.is_valid(0));
assert!(vec.is_updated());
assert_eq!(vec.get_valid(0), Some(&vec![1, 2, 3, 4]));
Ok(())
}
#[test]
fn update_on_invalid_slot_makes_it_valid() {
let mut vec = Vector::new();
vec.push(99); assert!(!vec.is_valid(0));
let mut seen_placeholder = None;
vec.update(0, |v| {
seen_placeholder = Some(*v);
*v = 42;
});
assert_eq!(seen_placeholder, Some(99));
assert!(vec.is_valid(0));
assert_eq!(vec.get_valid(0), Some(&42));
}
#[test]
fn update_redirties_after_reset() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push_committed(10);
vec.reset()?;
assert!(!vec.is_updated());
vec.update(0, |v| *v += 5);
assert!(vec.is_updated());
let collected: Vec<(usize, Option<i32>)> = vec
.iter_updated_valid()
.map(|(i, v)| (i, v.copied()))
.collect();
assert_eq!(collected, vec![(0, Some(15))]);
Ok(())
}
#[test]
#[should_panic(expected = "Vector::update: index 1 out of bounds")]
fn update_out_of_bounds_panics() {
let mut vec: Vector<i32> = Vector::new();
vec.push_committed(7);
vec.update(1, |v| *v += 1);
}
#[test]
fn from_vec_loads_clean_and_valid() {
let vec = Vector::from_vec(vec![10, 20, 30]);
assert_eq!(vec.len(), 3);
assert!(!vec.is_updated());
assert_eq!(vec.get_valid(0), Some(&10));
assert_eq!(vec.get_valid(1), Some(&20));
assert_eq!(vec.get_valid(2), Some(&30));
assert_eq!(vec.iter_updated_valid().count(), 0);
assert_eq!(vec.iter_updated_indices().count(), 0);
}
#[test]
fn from_vec_empty_is_well_formed() {
let vec: Vector<i32> = Vector::from_vec(Vec::new());
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
assert!(!vec.is_updated());
assert!(!vec.all_updated());
}
#[test]
fn from_vec_straddles_word_boundary() {
let data: Vec<u32> = (0..100).collect();
let mut vec = Vector::from_vec(data);
for i in 0..100 {
assert_eq!(vec.get_valid(i), Some(&(i as u32)));
}
assert!(!vec.is_updated());
vec.commit(75, 9999);
let updated: Vec<usize> = vec.iter_updated_indices().collect();
assert_eq!(updated, vec![75]);
}
#[test]
fn from_fill_clones_value() {
let vec = Vector::from_fill(String::from("hi"), 4);
assert_eq!(vec.len(), 4);
assert!(!vec.is_updated());
for i in 0..4 {
assert_eq!(vec.get_valid(i).map(String::as_str), Some("hi"));
}
}
#[test]
fn with_invalid_slots_allocates_invalid_and_clean() {
let mut vec: Vector<u32> = Vector::with_invalid_slots(3);
assert_eq!(vec.len(), 3);
assert!(!vec.is_empty());
assert!(!vec.is_updated());
for i in 0..3 {
assert!(!vec.is_valid(i));
assert_eq!(vec.get_valid(i), None);
}
vec.commit(1, 42);
assert_eq!(vec.get_valid(1), Some(&42));
assert!(vec.is_updated_at(1));
assert_eq!(vec.get_valid(0), None);
assert_eq!(vec.get_valid(2), None);
}
#[test]
fn with_invalid_slots_zero_len_is_well_formed() {
let vec: Vector<u32> = Vector::with_invalid_slots(0);
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
assert!(!vec.is_updated());
}
#[test]
fn as_slice_returns_full_data() {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push_committed(2);
vec.push(3); assert_eq!(vec.as_slice(), &[1, 2, 3]);
}
#[test]
fn iter_updated_indices_yields_dirty_in_ascending_order() -> Result<(), Error> {
let mut vec = Vector::new();
for _ in 0..5 {
vec.push_committed(0);
}
vec.reset()?;
assert_eq!(vec.iter_updated_indices().count(), 0);
vec.commit(3, 30);
vec.commit(0, 10);
vec.commit(4, 40);
let got: Vec<usize> = vec.iter_updated_indices().collect();
assert_eq!(got, vec![0, 3, 4]);
Ok(())
}
#[test]
fn iter_updated_indices_handles_word_boundaries() {
let mut vec: Vector<u32> = Vector::new();
for _ in 0..200 {
vec.push(0);
}
vec.reset().unwrap();
for &i in &[0_usize, 63, 64, 127, 128, 199] {
vec.commit(i, i as u32);
}
let got: Vec<usize> = vec.iter_updated_indices().collect();
assert_eq!(got, vec![0, 63, 64, 127, 128, 199]);
}
#[test]
fn iter_updated_indices_empty_when_nothing_dirty() -> Result<(), Error> {
let mut vec: Vector<i32> = Vector::new();
assert_eq!(vec.iter_updated_indices().count(), 0);
vec.push_committed(1);
vec.reset()?;
assert_eq!(vec.iter_updated_indices().count(), 0);
Ok(())
}
#[test]
fn iter_updated_indices_ignores_validity() {
let mut vec: Vector<i32> = Vector::new();
vec.push(0); vec.push_committed(99); let got: Vec<usize> = vec.iter_updated_indices().collect();
assert_eq!(got, vec![0, 1]);
}
#[test]
fn validity_pop_keeps_remaining_bits_aligned() -> Result<(), Error> {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push(0);
vec.push_committed(3);
vec.reset()?;
assert!(vec.is_valid(0));
assert!(!vec.is_valid(1));
assert!(vec.is_valid(2));
let popped = vec.pop();
assert_eq!(popped, Some(3));
assert_eq!(vec.len(), 2);
assert!(vec.is_valid(0));
assert!(!vec.is_valid(1));
Ok(())
}
#[test]
fn validity_clear_resets_everything() {
let mut vec = Vector::new();
vec.push_committed(1);
vec.push_committed(2);
vec.clear();
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
assert!(!vec.is_updated());
}
#[test]
fn is_updated_at_tracks_per_slot_dirty() -> Result<(), Error> {
let mut vec = Vector::new();
for _ in 0..5 {
vec.push_committed(0_u32);
}
for i in 0..5 {
assert!(vec.is_updated_at(i), "slot {} should be dirty", i);
}
vec.reset()?;
for i in 0..5 {
assert!(!vec.is_updated_at(i), "slot {} should be clean", i);
}
vec.commit(1, 10);
vec.commit(4, 40);
assert!(!vec.is_updated_at(0));
assert!(vec.is_updated_at(1));
assert!(!vec.is_updated_at(2));
assert!(!vec.is_updated_at(3));
assert!(vec.is_updated_at(4));
Ok(())
}
#[test]
fn is_updated_at_out_of_bounds_returns_false() {
let mut vec: Vector<u32> = Vector::new();
vec.push_committed(0);
assert!(vec.is_updated_at(0));
assert!(!vec.is_updated_at(1));
assert!(!vec.is_updated_at(usize::MAX));
}
#[test]
fn is_updated_at_agrees_with_iter_updated_indices() {
let mut vec: Vector<u32> = Vector::new();
for _ in 0..200 {
vec.push(0);
}
vec.reset().unwrap();
for &i in &[0_usize, 63, 64, 127, 128, 199] {
vec.commit(i, i as u32);
}
let dirty: std::collections::HashSet<usize> = vec.iter_updated_indices().collect();
for i in 0..200 {
assert_eq!(
vec.is_updated_at(i),
dirty.contains(&i),
"mismatch at slot {}",
i
);
}
}
#[test]
fn is_updated_at_set_by_invalidate() {
let mut vec = Vector::new();
vec.push_committed(7);
vec.reset().unwrap();
assert!(!vec.is_updated_at(0));
vec.invalidate(0);
assert!(
vec.is_updated_at(0),
"invalidate is a write — should set the dirty bit"
);
}
}