Skip to main content

pipeline/value/
vector.rs

1//! `Vec`-like container with per-slot **dirty** and **validity** tracking.
2//!
3//! [`Vector<V>`] backs the pipeline's incremental, cycle-based recompute: each
4//! slot carries a validity bit (multi-cycle: "does this slot hold a value?")
5//! and a dirty bit (per-cycle: "was it written this cycle?", cleared by
6//! [`Reset`]). Producers `commit`/`update`/`push` slots; consumers read via
7//! `get_valid` and iterate only what changed via `iter_updated_*`.
8
9use crate::Error;
10use crate::Reset;
11use std::vec::Vec;
12
13#[inline]
14fn word_bit(i: usize) -> (usize, usize) {
15    (i / 64, i % 64)
16}
17
18/// A `Vec`-like container with per-slot **dirty** *and* **validity**
19/// tracking.
20///
21/// # Two independent bits per slot
22///
23/// - **Dirty** (`dirty`): per-cycle bit, set when a slot is written
24///   via [`Vector::commit`] / [`Vector::invalidate`] /
25///   [`Vector::push`] / [`Vector::push_committed`] / [`Vector::update`],
26///   cleared by [`Vector::reset`]. Drives [`Vector::iter_updated_valid`].
27/// - **Validity** (`valid`): multi-cycle bit, set by
28///   [`Vector::commit`] / [`Vector::push_committed`] /
29///   [`Vector::update`], cleared by [`Vector::invalidate`]. **Not**
30///   touched by [`Vector::reset`] — a slot that was valid at end of
31///   cycle stays valid at start of the next cycle. Drives
32///   [`Vector::get_valid`] and [`Vector::iter_updated_valid`].
33///
34/// The validity bit gives pipeline stages an `Option`-like read API:
35/// a downstream stage that reads via [`Vector::get_valid`] cannot
36/// reach the inner value without first matching on `Some` / `None`,
37/// so the consistency / freshness check is enforced at the type
38/// system level. See [`SlotWriter`] for the symmetric write side.
39///
40/// # Breaking change in 0.2.1
41///
42/// The legacy bypass methods `get`, `get_updated`, `get_mut`,
43/// `iter_updated` were removed in 0.2.1. All access paths now engage
44/// with the validity bit. Callers that genuinely need indexed
45/// accumulation rather than single-value-per-slot should use
46/// [`crate::value::buckets::Buckets`] instead.
47pub struct Vector<V> {
48    data: Vec<V>,
49    /// Per-slot dirty bits, packed 64 per `u64`. Cleared by `reset`.
50    dirty: Vec<u64>,
51    /// Per-slot validity bits, packed 64 per `u64`. Persists across `reset`.
52    valid: Vec<u64>,
53    /// O(1) "anything dirty?" and "how many dirty?" — incremented on
54    /// every transition `0 → 1` and decremented on `1 → 0`.
55    dirty_count: usize,
56}
57
58impl<V> Default for Vector<V> {
59    fn default() -> Self {
60        Self::new()
61    }
62}
63
64impl<V> Vector<V> {
65    /// Creates a new, empty `Vector`.
66    pub fn new() -> Self {
67        Self {
68            data: Vec::new(),
69            dirty: Vec::new(),
70            valid: Vec::new(),
71            dirty_count: 0,
72        }
73    }
74
75    /// Creates a `Vector` with the specified capacity.
76    pub fn with_capacity(capacity: usize) -> Self {
77        let words = capacity.div_ceil(64);
78        Self {
79            data: Vec::with_capacity(capacity),
80            dirty: Vec::with_capacity(words),
81            valid: Vec::with_capacity(words),
82            dirty_count: 0,
83        }
84    }
85
86    /// Bulk constructor from an existing `Vec<V>`. Every slot starts
87    /// **valid** and **clean** (no dirty bits set), matching the
88    /// "loaded from saved state" pattern: downstream consumers can
89    /// read via [`Vector::get_valid`] but won't see a fresh round of
90    /// `iter_updated_*` events until something is committed or
91    /// invalidated.
92    pub fn from_vec(data: Vec<V>) -> Self {
93        let len = data.len();
94        let words = len.div_ceil(64);
95        let mut valid = vec![0u64; words];
96        // Set the valid bit for every existing slot.
97        let full_words = len / 64;
98        let rem = len % 64;
99        for w in &mut valid[..full_words] {
100            *w = u64::MAX;
101        }
102        if rem != 0 {
103            valid[full_words] = (1u64 << rem) - 1;
104        }
105        Self {
106            data,
107            dirty: vec![0u64; words],
108            valid,
109            dirty_count: 0,
110        }
111    }
112
113    /// Bulk constructor filled with `len` clones of `value`. Same
114    /// dirty / valid semantics as [`Vector::from_vec`]: every slot
115    /// starts valid + clean.
116    pub fn from_fill(value: V, len: usize) -> Self
117    where
118        V: Clone,
119    {
120        Self::from_vec(vec![value; len])
121    }
122
123    /// Bulk constructor with `len` slots that all start **invalid** and
124    /// **clean**. The backing storage is allocated — so `commit(i, ..)`
125    /// for any `i < len` will not panic — but every slot reads as `None`
126    /// via [`Vector::get_valid`] until the caller commits a value into
127    /// it. Slots hold `V::default()` placeholders that are never exposed
128    /// while invalid.
129    ///
130    /// Useful for an externally-fed buffer of known size whose slots are
131    /// populated incrementally: allocate once with `with_invalid_slots(n)`,
132    /// then `commit` real values as they arrive. Contrast with
133    /// [`Vector::from_fill`], whose slots start **valid**.
134    pub fn with_invalid_slots(len: usize) -> Self
135    where
136        V: Default,
137    {
138        let words = len.div_ceil(64);
139        let mut data = Vec::with_capacity(len);
140        data.resize_with(len, V::default);
141        Self {
142            data,
143            dirty: vec![0u64; words],
144            valid: vec![0u64; words],
145            dirty_count: 0,
146        }
147    }
148
149    /// Returns the full underlying slice of values, regardless of
150    /// per-slot validity or dirty state. Useful for bulk read paths
151    /// where the caller has already established validity through
152    /// other means (e.g. tests, snapshot serialisation).
153    #[inline]
154    pub fn as_slice(&self) -> &[V] {
155        &self.data
156    }
157
158    /// Returns `true` if any element in the `Vector` is updated.
159    #[inline]
160    pub fn is_updated(&self) -> bool {
161        self.dirty_count != 0
162    }
163
164    /// Returns `true` if every element in the `Vector` is updated.
165    /// Returns `false` for an empty `Vector`.
166    #[inline]
167    pub fn all_updated(&self) -> bool {
168        !self.data.is_empty() && self.dirty_count == self.data.len()
169    }
170
171    /// Clears the `Vector`, removing all values.
172    pub fn clear(&mut self) {
173        self.data.clear();
174        self.dirty.clear();
175        self.valid.clear();
176        self.dirty_count = 0;
177    }
178
179    /// Returns the number of elements in the `Vector`.
180    pub fn len(&self) -> usize {
181        self.data.len()
182    }
183
184    /// Returns `true` if the `Vector` contains no elements.
185    pub fn is_empty(&self) -> bool {
186        self.data.is_empty()
187    }
188
189    /// Returns an iterator over the elements of the `Vector`.
190    pub fn iter(&self) -> std::slice::Iter<'_, V> {
191        self.data.iter()
192    }
193
194    /// Returns a mutable iterator over the elements of the `Vector`.
195    /// Marks all elements as updated.
196    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, V> {
197        let len = self.data.len();
198        if self.dirty_count < len {
199            let full_words = len / 64;
200            let rem = len % 64;
201            for w in 0..full_words {
202                self.dirty[w] = u64::MAX;
203            }
204            if rem != 0 {
205                self.dirty[full_words] = (1u64 << rem) - 1;
206            }
207            self.dirty_count = len;
208        }
209        self.data.iter_mut()
210    }
211
212    /// Internal: mark slot `index` as dirty. Caller is responsible
213    /// for ensuring `index < self.data.len()` and the dirty word
214    /// for `index` already exists in `self.dirty`.
215    #[inline]
216    fn mark_dirty_internal(&mut self, index: usize) {
217        let (w, b) = word_bit(index);
218        let mask = 1u64 << b;
219        if (self.dirty[w] & mask) == 0 {
220            self.dirty[w] |= mask;
221            self.dirty_count += 1;
222        }
223    }
224
225    /// Internal: set or clear the validity bit for slot `index`.
226    #[inline]
227    fn set_valid_internal(&mut self, index: usize, value: bool) {
228        let (w, b) = word_bit(index);
229        let mask = 1u64 << b;
230        if value {
231            self.valid[w] |= mask;
232        } else {
233            self.valid[w] &= !mask;
234        }
235    }
236
237    /// Internal: extend `dirty` and `valid` so they cover slot
238    /// `self.data.len() - 1` (called after a push has already
239    /// extended `self.data`).
240    #[inline]
241    fn extend_bitsets_for_last_push(&mut self) {
242        let new_idx = self.data.len() - 1;
243        if new_idx.is_multiple_of(64) {
244            self.dirty.push(0);
245            self.valid.push(0);
246        }
247    }
248
249    /// Returns a mutable reference to the element at the given index without
250    /// updating dirty tracking.
251    ///
252    /// This is useful when callers want to reuse or clear previously touched
253    /// storage between compute cycles without reporting a fresh update to
254    /// downstream stages.
255    #[inline]
256    pub fn get_mut_untracked(&mut self, index: usize) -> Option<&mut V> {
257        self.data.get_mut(index)
258    }
259
260    /// Appends an element to the back of the `Vector`, marking it as updated.
261    ///
262    /// The new slot is marked **invalid**: the pushed `value` is
263    /// treated as a placeholder rather than a meaningful committed
264    /// value. Use [`Vector::push_committed`] when the initial value
265    /// is meaningful, or call [`Vector::commit`] on the new index
266    /// afterwards.
267    pub fn push(&mut self, value: V) {
268        self.data.push(value);
269        self.extend_bitsets_for_last_push();
270        let idx = self.data.len() - 1;
271        // valid stays 0 (invalid placeholder).
272        self.mark_dirty_internal(idx);
273    }
274
275    /// Like [`Vector::push`], but the new slot is marked **valid**.
276    ///
277    /// Use this when you genuinely have a meaningful starting value
278    /// for the slot; otherwise prefer `push` and explicitly
279    /// [`Vector::commit`] later, which makes the moment the slot
280    /// becomes valid explicit.
281    pub fn push_committed(&mut self, value: V) {
282        self.data.push(value);
283        self.extend_bitsets_for_last_push();
284        let idx = self.data.len() - 1;
285        self.set_valid_internal(idx, true);
286        self.mark_dirty_internal(idx);
287    }
288
289    /// Removes the last element from the `Vector` and returns it, or `None` if empty.
290    pub fn pop(&mut self) -> Option<V> {
291        if self.data.is_empty() {
292            return None;
293        }
294        let idx = self.data.len() - 1;
295        let (w, b) = word_bit(idx);
296        let mask = 1u64 << b;
297        // If the popped slot was dirty, decrement the count.
298        if (self.dirty[w] & mask) != 0 {
299            self.dirty_count -= 1;
300        }
301        // Clear both bits for the popped slot.
302        self.dirty[w] &= !mask;
303        self.valid[w] &= !mask;
304        // If the popped slot owned the last word entirely, drop it.
305        if idx.is_multiple_of(64) {
306            self.dirty.pop();
307            self.valid.pop();
308        }
309        self.data.pop()
310    }
311
312    // -------------------------------------------------------------
313    // Validity-aware API
314    // -------------------------------------------------------------
315
316    /// Returns `true` if slot `index` is valid (has a committed value
317    /// that has not been invalidated). Returns `false` if the index
318    /// is out of bounds.
319    #[inline]
320    pub fn is_valid(&self, index: usize) -> bool {
321        if index >= self.data.len() {
322            return false;
323        }
324        let (w, b) = word_bit(index);
325        (self.valid[w] & (1u64 << b)) != 0
326    }
327
328    /// Returns `true` if slot `index` was written this cycle (commit
329    /// / update / invalidate / slot_writer.commit). Returns `false`
330    /// if the index is out of bounds, or if the slot's dirty bit was
331    /// cleared by [`Reset::reset`] at end-of-cycle.
332    ///
333    /// Equivalent to looking up `index` in the set produced by
334    /// [`Vector::iter_updated_indices`], but as an O(1) bit test
335    /// rather than an iteration. Use this when you have a fixed slot
336    /// index and want to decide whether to do dirty-driven work on
337    /// it — avoids the per-cycle materialisation that
338    /// `iter_updated_indices().collect::<HashSet<_>>()` would force.
339    #[inline]
340    pub fn is_updated_at(&self, index: usize) -> bool {
341        if index >= self.data.len() {
342            return false;
343        }
344        let (w, b) = word_bit(index);
345        (self.dirty[w] & (1u64 << b)) != 0
346    }
347
348    /// Returns a reference to slot `index` if (and only if) it is
349    /// **valid**. The Option-like read accessor: mirrors
350    /// `Option::as_ref` and forces callers to match on `Some` /
351    /// `None` before reaching the inner value.
352    #[inline]
353    pub fn get_valid(&self, index: usize) -> Option<&V> {
354        if self.is_valid(index) {
355            self.data.get(index)
356        } else {
357            None
358        }
359    }
360
361    /// Write `value` into slot `index`, mark the slot **valid**, and
362    /// mark it **dirty**. Panics if `index` is out of bounds.
363    pub fn commit(&mut self, index: usize, value: V) {
364        assert!(
365            index < self.data.len(),
366            "Vector::commit: index {} out of bounds (len = {})",
367            index,
368            self.data.len()
369        );
370        self.data[index] = value;
371        self.set_valid_internal(index, true);
372        self.mark_dirty_internal(index);
373    }
374
375    /// Apply `f` to slot `index` **in place**, then mark the slot
376    /// **valid** and **dirty**.
377    ///
378    /// The point of this method is to mutate a large `V` without
379    /// moving in a fresh value the way [`Vector::commit`] does. For
380    /// a wide value type, `commit(i, fresh)` copies the whole struct
381    /// in; `update(i, |v| v.field = new_field)` writes only the bytes
382    /// that changed.
383    ///
384    /// The closure sees whatever the slot currently stores — the
385    /// prior committed value, the placeholder from a non-
386    /// [`Vector::push_committed`] [`Vector::push`], or stale bytes
387    /// from a slot that was [`Vector::invalidate`]d. Callers that
388    /// care about the prior validity should check
389    /// [`Vector::is_valid`] / [`Vector::get_valid`] first.
390    ///
391    /// Panics if `index` is out of bounds.
392    pub fn update<F>(&mut self, index: usize, f: F)
393    where
394        F: FnOnce(&mut V),
395    {
396        assert!(
397            index < self.data.len(),
398            "Vector::update: index {} out of bounds (len = {})",
399            index,
400            self.data.len()
401        );
402        f(&mut self.data[index]);
403        self.set_valid_internal(index, true);
404        self.mark_dirty_internal(index);
405    }
406
407    /// Mark slot `index` **invalid** and **dirty**. The slot retains
408    /// its prior data; it just becomes invisible to readers using
409    /// [`Vector::get_valid`]. Panics if `index` is out of bounds.
410    pub fn invalidate(&mut self, index: usize) {
411        assert!(
412            index < self.data.len(),
413            "Vector::invalidate: index {} out of bounds (len = {})",
414            index,
415            self.data.len()
416        );
417        self.set_valid_internal(index, false);
418        self.mark_dirty_internal(index);
419    }
420
421    /// Acquire a one-shot writer for slot `index`. The returned
422    /// [`SlotWriter`] is `#[must_use]` and **must** be consumed via
423    /// [`SlotWriter::commit`] or [`SlotWriter::invalidate`]; dropping
424    /// it without consumption triggers a `debug_assert!` in debug
425    /// builds.
426    ///
427    /// Panics if `index` is out of bounds.
428    #[must_use = "the returned SlotWriter must be consumed via commit() or invalidate()"]
429    pub fn slot_writer(&mut self, index: usize) -> SlotWriter<'_, V> {
430        assert!(
431            index < self.data.len(),
432            "Vector::slot_writer: index {} out of bounds (len = {})",
433            index,
434            self.data.len()
435        );
436        SlotWriter {
437            vector: self,
438            index,
439            consumed: false,
440        }
441    }
442}
443
444impl<V> Reset for Vector<V> {
445    /// Clears the dirty bits and indices. **Does not** touch
446    /// validity bits: a slot that was valid before `reset` stays
447    /// valid after `reset`. Dirty is per-cycle; validity is
448    /// multi-cycle.
449    type Error = Error;
450    fn reset(&mut self) -> Result<(), Error> {
451        for w in &mut self.dirty {
452            *w = 0;
453        }
454        self.dirty_count = 0;
455        Ok(())
456    }
457}
458
459impl<V> crate::Updated for Vector<V> {
460    fn is_updated(&self) -> bool {
461        self.is_updated() // inherent method (preferred over the trait method)
462    }
463}
464
465/// A one-shot, must-use writer for a single slot in a [`Vector`].
466///
467/// Acquired by [`Vector::slot_writer`]. Consume by exactly one of
468/// [`SlotWriter::commit`] (write a value and mark the slot valid +
469/// dirty) or [`SlotWriter::invalidate`] (mark the slot invalid +
470/// dirty without changing its stored data).
471///
472/// Dropping a `SlotWriter` without consuming it triggers
473/// `debug_assert!` in debug builds and is a silent no-op in release
474/// builds. The `#[must_use]` annotation makes "forgot to acquire and
475/// then forgot to use" a compile-time warning in `-D warnings`
476/// projects.
477#[must_use = "SlotWriter must be consumed via commit() or invalidate()"]
478pub struct SlotWriter<'a, V> {
479    vector: &'a mut Vector<V>,
480    index: usize,
481    consumed: bool,
482}
483
484impl<'a, V> SlotWriter<'a, V> {
485    /// Write `value` into the target slot and mark it valid + dirty.
486    pub fn commit(mut self, value: V) {
487        self.vector.commit(self.index, value);
488        self.consumed = true;
489    }
490
491    /// Mark the target slot invalid + dirty. The slot retains its
492    /// prior stored data; it just becomes invisible via
493    /// [`Vector::get_valid`].
494    pub fn invalidate(mut self) {
495        self.vector.invalidate(self.index);
496        self.consumed = true;
497    }
498}
499
500impl<'a, V> Drop for SlotWriter<'a, V> {
501    fn drop(&mut self) {
502        if !self.consumed {
503            debug_assert!(
504                false,
505                "SlotWriter at index {} dropped without commit() or invalidate()",
506                self.index
507            );
508        }
509    }
510}
511
512impl<V> Vector<V> {
513    /// Iterate dirty slots in **ascending index order**, yielding
514    /// `(index, Some(&V))` for slots that are also valid and
515    /// `(index, None)` for dirty slots that are invalid.
516    pub fn iter_updated_valid(&self) -> IterUpdatedValidItems<'_, V> {
517        IterUpdatedValidItems::new(self)
518    }
519
520    /// Iterate **indices** of dirty slots in ascending order. Skips
521    /// the validity check and data lookup that
522    /// [`Vector::iter_updated_valid`] performs — useful when you only
523    /// need the slot indices, e.g. to drive a parallel walk over
524    /// another `Vector` or external array keyed by the same slot.
525    pub fn iter_updated_indices(&self) -> IterUpdatedIndices<'_> {
526        IterUpdatedIndices::new(&self.dirty, self.data.len())
527    }
528}
529
530/// Iterator returned by [`Vector::iter_updated_valid`]. Yields
531/// `(usize, Option<&V>)` for every **dirty** slot, where the `Option`
532/// reflects the slot's **validity**. Always in ascending index order.
533pub struct IterUpdatedValidItems<'a, V> {
534    vector: &'a Vector<V>,
535    word_idx: usize,
536    word: u64,
537}
538
539impl<'a, V> IterUpdatedValidItems<'a, V> {
540    fn new(v: &'a Vector<V>) -> Self {
541        let mut it = Self {
542            vector: v,
543            word_idx: 0,
544            word: 0,
545        };
546        it.advance_to_nonzero();
547        it
548    }
549
550    fn advance_to_nonzero(&mut self) {
551        while self.word == 0 && self.word_idx < self.vector.dirty.len() {
552            let mut w = self.vector.dirty[self.word_idx];
553            // Mask padding bits in the last word so we don't yield
554            // indices >= data.len().
555            if self.word_idx + 1 == self.vector.dirty.len() {
556                let rem = self.vector.data.len() % 64;
557                if rem != 0 {
558                    w &= (1u64 << rem) - 1;
559                }
560            }
561            self.word = w;
562            if self.word != 0 {
563                break;
564            }
565            self.word_idx += 1;
566        }
567    }
568}
569
570impl<'a, V> Iterator for IterUpdatedValidItems<'a, V> {
571    type Item = (usize, Option<&'a V>);
572
573    fn next(&mut self) -> Option<Self::Item> {
574        if self.word == 0 {
575            self.advance_to_nonzero();
576            if self.word == 0 {
577                return None;
578            }
579        }
580        let tz = self.word.trailing_zeros() as usize;
581        let idx = self.word_idx * 64 + tz;
582        // Pop the bit from the snapshot word.
583        self.word &= self.word - 1;
584        let opt = if self.vector.is_valid(idx) {
585            Some(&self.vector.data[idx])
586        } else {
587            None
588        };
589        if self.word == 0 {
590            self.word_idx += 1;
591        }
592        Some((idx, opt))
593    }
594}
595
596/// Iterator returned by [`Vector::iter_updated_indices`]. Yields the
597/// `usize` index of every **dirty** slot in ascending order, with no
598/// validity check or data lookup.
599pub struct IterUpdatedIndices<'a> {
600    dirty: &'a [u64],
601    total_len: usize,
602    word_idx: usize,
603    word: u64,
604}
605
606impl<'a> IterUpdatedIndices<'a> {
607    fn new(dirty: &'a [u64], total_len: usize) -> Self {
608        let mut it = Self {
609            dirty,
610            total_len,
611            word_idx: 0,
612            word: 0,
613        };
614        it.advance_to_nonzero();
615        it
616    }
617
618    fn advance_to_nonzero(&mut self) {
619        while self.word == 0 && self.word_idx < self.dirty.len() {
620            let mut w = self.dirty[self.word_idx];
621            if self.word_idx + 1 == self.dirty.len() {
622                let rem = self.total_len % 64;
623                if rem != 0 {
624                    w &= (1u64 << rem) - 1;
625                }
626            }
627            self.word = w;
628            if self.word != 0 {
629                break;
630            }
631            self.word_idx += 1;
632        }
633    }
634}
635
636impl<'a> Iterator for IterUpdatedIndices<'a> {
637    type Item = usize;
638
639    fn next(&mut self) -> Option<Self::Item> {
640        if self.word == 0 {
641            self.advance_to_nonzero();
642            if self.word == 0 {
643                return None;
644            }
645        }
646        let tz = self.word.trailing_zeros() as usize;
647        let idx = self.word_idx * 64 + tz;
648        self.word &= self.word - 1;
649        if self.word == 0 {
650            self.word_idx += 1;
651        }
652        Some(idx)
653    }
654}
655
656#[cfg(test)]
657mod tests {
658    use super::*;
659
660    #[test]
661    fn test_new_vector() {
662        let vec: Vector<i32> = Vector::new();
663        assert_eq!(vec.len(), 0);
664        assert!(vec.is_empty());
665        assert!(!vec.is_updated());
666        assert!(!vec.all_updated());
667    }
668
669    #[test]
670    fn test_with_capacity() {
671        let mut vec: Vector<i32> = Vector::with_capacity(10);
672        assert_eq!(vec.len(), 0);
673        assert_eq!(vec.data.capacity(), 10);
674        // 10 slots fit in a single u64 word, so the bitsets reserve
675        // exactly one word.
676        assert!(vec.dirty.capacity() >= 1);
677        assert!(vec.valid.capacity() >= 1);
678        assert_eq!(vec.dirty.len(), 0);
679        assert_eq!(vec.valid.len(), 0);
680        assert!(!vec.is_valid(0));
681
682        vec.push_committed(42);
683        assert_eq!(vec.get_valid(0), Some(&42));
684
685        assert_eq!(vec.pop(), Some(42));
686        assert_eq!(vec.len(), 0);
687        assert!(!vec.is_valid(0));
688    }
689
690    #[test]
691    fn get_mut_untracked_does_not_mark_updated() -> Result<(), Error> {
692        let mut vec = Vector::new();
693        vec.push_committed(String::from("a"));
694        vec.push_committed(String::from("b"));
695        vec.reset()?;
696
697        if let Some(value) = vec.get_mut_untracked(1) {
698            value.clear();
699        }
700
701        assert_eq!(vec.get_valid(1).map(String::as_str), Some(""));
702        assert!(!vec.is_updated());
703        assert!(!vec.all_updated());
704        assert!(vec.iter_updated_valid().next().is_none());
705        Ok(())
706    }
707
708    #[test]
709    fn reset_clears_dirty_preserves_data() -> Result<(), Error> {
710        let mut vec = Vector::new();
711        vec.push_committed(1);
712        vec.push_committed(2);
713        vec.push_committed(3);
714
715        assert!(vec.is_updated());
716
717        vec.reset()?;
718        assert!(!vec.is_updated());
719        assert!(!vec.all_updated());
720        assert_eq!(vec.iter_updated_valid().count(), 0);
721        // Reset preserves data + validity, just clears dirty.
722        assert_eq!(vec.get_valid(0), Some(&1));
723        assert_eq!(vec.get_valid(1), Some(&2));
724        assert_eq!(vec.get_valid(2), Some(&3));
725
726        vec.commit(1, 20);
727        assert!(vec.is_updated());
728        assert_eq!(vec.get_valid(1), Some(&20));
729        Ok(())
730    }
731
732    #[test]
733    fn clear_drops_all_state() {
734        let mut vec = Vector::new();
735        vec.push_committed(1);
736        vec.push_committed(2);
737        vec.push_committed(3);
738
739        vec.clear();
740        assert_eq!(vec.len(), 0);
741        assert!(vec.is_empty());
742        assert!(!vec.is_updated());
743        assert!(!vec.all_updated());
744    }
745
746    #[test]
747    fn pop_drops_last_slot_and_its_bits() {
748        let mut vec = Vector::new();
749        vec.push_committed(10);
750        vec.push_committed(20);
751        vec.push_committed(30);
752
753        let val = vec.pop();
754        assert_eq!(val, Some(30));
755        assert_eq!(vec.len(), 2);
756        assert!(vec.is_updated());
757
758        vec.pop();
759        vec.pop();
760        assert!(vec.is_empty());
761        assert!(!vec.is_updated());
762    }
763
764    #[test]
765    fn out_of_bounds_returns_none() {
766        let mut vec = Vector::new();
767        vec.push_committed(1);
768
769        assert_eq!(vec.get_valid(1), None);
770        assert!(!vec.is_valid(1));
771    }
772
773    #[test]
774    fn iter_and_iter_mut_walk_raw_data() {
775        let mut vec = Vector::new();
776        vec.push_committed(1);
777        vec.push_committed(2);
778        vec.push_committed(3);
779
780        let collected: Vec<&i32> = vec.iter().collect();
781        assert_eq!(collected, vec![&1, &2, &3]);
782
783        for val in vec.iter_mut() {
784            *val *= 2;
785        }
786
787        let collected: Vec<&i32> = vec.iter().collect();
788        assert_eq!(collected, vec![&2, &4, &6]);
789    }
790
791    #[test]
792    fn push_after_all_updated_extends_correctly() {
793        let mut vec = Vector::new();
794        vec.push_committed(1);
795        vec.push_committed(2);
796
797        // Now push a third element — should mark dirty + invalid.
798        vec.push(3);
799        assert!(vec.is_updated());
800
801        let updated: Vec<(usize, Option<i32>)> = vec
802            .iter_updated_valid()
803            .map(|(i, v)| (i, v.copied()))
804            .collect();
805        assert_eq!(updated, vec![(0, Some(1)), (1, Some(2)), (2, None)]);
806    }
807
808    #[test]
809    fn commit_marks_all_slots_dirty() {
810        let mut vec = Vector::new();
811        vec.push_committed(1);
812        vec.push_committed(2);
813        vec.push_committed(3);
814
815        vec.commit(0, 10);
816        vec.commit(1, 20);
817        vec.commit(2, 30);
818
819        let updated: Vec<(usize, i32)> = vec
820            .iter_updated_valid()
821            .map(|(i, v)| (i, *v.unwrap()))
822            .collect();
823        assert_eq!(updated, vec![(0, 10), (1, 20), (2, 30)]);
824    }
825
826    #[test]
827    fn reset_after_committed_pushes_keeps_validity() -> Result<(), Error> {
828        let mut vec = Vector::new();
829        vec.push_committed(1);
830        vec.push_committed(2);
831
832        vec.reset()?;
833        assert!(!vec.is_updated());
834        assert_eq!(vec.iter_updated_valid().count(), 0);
835        assert_eq!(vec.get_valid(0), Some(&1));
836        assert_eq!(vec.get_valid(1), Some(&2));
837
838        vec.commit(0, 10);
839        let updated: Vec<(usize, i32)> = vec
840            .iter_updated_valid()
841            .map(|(i, v)| (i, *v.unwrap()))
842            .collect();
843        assert_eq!(updated, vec![(0, 10)]);
844        Ok(())
845    }
846
847    #[test]
848    fn push_committed_reads_back_via_get_valid() {
849        let mut vec = Vector::new();
850        vec.push_committed(1);
851        vec.push_committed(2);
852        vec.push_committed(3);
853
854        assert_eq!(vec.len(), 3);
855        assert_eq!(vec.get_valid(0), Some(&1));
856        assert_eq!(vec.get_valid(1), Some(&2));
857        assert_eq!(vec.get_valid(2), Some(&3));
858        assert_eq!(vec.get_valid(3), None);
859
860        assert!(vec.is_updated());
861    }
862
863    // -----------------------------------------------------------------
864    // Validity-tracking tests
865    // -----------------------------------------------------------------
866
867    #[test]
868    fn validity_push_is_invalid_by_default() {
869        let mut vec = Vector::new();
870        vec.push(42);
871        assert!(!vec.is_valid(0));
872        assert_eq!(vec.get_valid(0), None);
873        // Out-of-bounds index is also "not valid".
874        assert!(!vec.is_valid(1));
875        assert_eq!(vec.get_valid(1), None);
876    }
877
878    #[test]
879    fn validity_push_committed_is_valid() {
880        let mut vec = Vector::new();
881        vec.push_committed(42);
882        assert!(vec.is_valid(0));
883        assert_eq!(vec.get_valid(0), Some(&42));
884        assert!(vec.is_updated());
885    }
886
887    #[test]
888    fn validity_commit_roundtrip() -> Result<(), Error> {
889        let mut vec = Vector::new();
890        vec.push(0); // invalid placeholder
891        assert!(!vec.is_valid(0));
892
893        vec.commit(0, 42);
894        assert!(vec.is_valid(0));
895        assert_eq!(vec.get_valid(0), Some(&42));
896        assert!(vec.is_updated());
897
898        vec.reset()?;
899        // Reset clears dirty, keeps valid.
900        assert!(!vec.is_updated());
901        assert!(vec.is_valid(0));
902        assert_eq!(vec.get_valid(0), Some(&42));
903        Ok(())
904    }
905
906    #[test]
907    fn validity_invalidate_marks_dirty_and_clears_valid() -> Result<(), Error> {
908        let mut vec = Vector::new();
909        vec.push_committed(42);
910        vec.reset()?;
911        assert!(!vec.is_updated());
912
913        vec.invalidate(0);
914        assert!(!vec.is_valid(0));
915        assert_eq!(vec.get_valid(0), None);
916        // Slot retains its prior raw data internally (we don't drop V
917        // on invalidate), but it's invisible through any public API.
918        assert!(vec.is_updated());
919        Ok(())
920    }
921
922    #[test]
923    fn validity_reset_preserves_validity_clears_dirty() -> Result<(), Error> {
924        let mut vec = Vector::new();
925        vec.push(0);
926        vec.commit(0, 100);
927        vec.reset()?;
928        assert!(!vec.is_updated());
929        assert!(vec.is_valid(0)); // <-- key property
930        assert_eq!(vec.get_valid(0), Some(&100));
931        Ok(())
932    }
933
934    #[test]
935    fn validity_slot_writer_commit_consumes_without_panic() {
936        let mut vec = Vector::new();
937        vec.push(0);
938        let writer = vec.slot_writer(0);
939        writer.commit(99);
940        assert_eq!(vec.get_valid(0), Some(&99));
941    }
942
943    #[test]
944    fn validity_slot_writer_invalidate_consumes_without_panic() {
945        let mut vec = Vector::new();
946        vec.push_committed(123);
947        let writer = vec.slot_writer(0);
948        writer.invalidate();
949        assert_eq!(vec.get_valid(0), None);
950        assert!(vec.is_updated());
951    }
952
953    #[test]
954    #[cfg(debug_assertions)]
955    #[should_panic(expected = "SlotWriter at index 0 dropped without commit() or invalidate()")]
956    fn validity_slot_writer_drop_without_consume_panics_in_debug() {
957        let mut vec: Vector<i32> = Vector::new();
958        vec.push(0);
959        let _writer = vec.slot_writer(0);
960        // drop without commit/invalidate
961    }
962
963    #[test]
964    fn validity_iter_updated_valid_yields_option_per_slot() -> Result<(), Error> {
965        let mut vec = Vector::new();
966        vec.push(0); // index 0, invalid placeholder, dirty
967        vec.push(0); // index 1, invalid placeholder, dirty
968        vec.push(0); // index 2, invalid placeholder, dirty
969        vec.commit(0, 10);
970        vec.commit(1, 20);
971        // index 2 stays invalid
972
973        let collected: Vec<(usize, Option<i32>)> = vec
974            .iter_updated_valid()
975            .map(|(i, v)| (i, v.copied()))
976            .collect();
977        assert_eq!(
978            collected,
979            vec![(0, Some(10)), (1, Some(20)), (2, None)],
980            "all three slots are dirty; only the two committed ones expose Some(v)"
981        );
982
983        vec.reset()?;
984        let collected: Vec<(usize, Option<i32>)> = vec
985            .iter_updated_valid()
986            .map(|(i, v)| (i, v.copied()))
987            .collect();
988        assert!(collected.is_empty(), "after reset no slots are dirty");
989
990        // Invalidate slot 0 — becomes dirty again, yields None.
991        vec.invalidate(0);
992        let collected: Vec<(usize, Option<i32>)> = vec
993            .iter_updated_valid()
994            .map(|(i, v)| (i, v.copied()))
995            .collect();
996        assert_eq!(collected, vec![(0, None)]);
997        Ok(())
998    }
999
1000    #[test]
1001    fn update_mutates_in_place_and_marks_valid_dirty() -> Result<(), Error> {
1002        // Start with a valid value, reset to drop the dirty flag, then
1003        // `update` it. The closure should see the prior value and the
1004        // slot should come back dirty + valid.
1005        let mut vec = Vector::new();
1006        vec.push_committed(vec![1, 2, 3]);
1007        vec.reset()?;
1008        assert!(!vec.is_updated());
1009
1010        let mut prior = None;
1011        vec.update(0, |v| {
1012            prior = Some(v.clone());
1013            v.push(4);
1014        });
1015
1016        assert_eq!(prior, Some(vec![1, 2, 3]));
1017        assert!(vec.is_valid(0));
1018        assert!(vec.is_updated());
1019        assert_eq!(vec.get_valid(0), Some(&vec![1, 2, 3, 4]));
1020        Ok(())
1021    }
1022
1023    #[test]
1024    fn update_on_invalid_slot_makes_it_valid() {
1025        // A slot that was `push`ed (placeholder, not valid) becomes
1026        // valid after `update` — the closure sees the placeholder
1027        // bytes, then we mark it as a real committed value.
1028        let mut vec = Vector::new();
1029        vec.push(99); // invalid placeholder
1030        assert!(!vec.is_valid(0));
1031
1032        let mut seen_placeholder = None;
1033        vec.update(0, |v| {
1034            seen_placeholder = Some(*v);
1035            *v = 42;
1036        });
1037
1038        assert_eq!(seen_placeholder, Some(99));
1039        assert!(vec.is_valid(0));
1040        assert_eq!(vec.get_valid(0), Some(&42));
1041    }
1042
1043    #[test]
1044    fn update_redirties_after_reset() -> Result<(), Error> {
1045        let mut vec = Vector::new();
1046        vec.push_committed(10);
1047        vec.reset()?;
1048        assert!(!vec.is_updated());
1049
1050        vec.update(0, |v| *v += 5);
1051        assert!(vec.is_updated());
1052        let collected: Vec<(usize, Option<i32>)> = vec
1053            .iter_updated_valid()
1054            .map(|(i, v)| (i, v.copied()))
1055            .collect();
1056        assert_eq!(collected, vec![(0, Some(15))]);
1057        Ok(())
1058    }
1059
1060    #[test]
1061    #[should_panic(expected = "Vector::update: index 1 out of bounds")]
1062    fn update_out_of_bounds_panics() {
1063        let mut vec: Vector<i32> = Vector::new();
1064        vec.push_committed(7);
1065        vec.update(1, |v| *v += 1);
1066    }
1067
1068    // -----------------------------------------------------------------
1069    // Bulk-constructor / as_slice tests
1070    // -----------------------------------------------------------------
1071
1072    #[test]
1073    fn from_vec_loads_clean_and_valid() {
1074        let vec = Vector::from_vec(vec![10, 20, 30]);
1075        assert_eq!(vec.len(), 3);
1076        assert!(!vec.is_updated());
1077        assert_eq!(vec.get_valid(0), Some(&10));
1078        assert_eq!(vec.get_valid(1), Some(&20));
1079        assert_eq!(vec.get_valid(2), Some(&30));
1080        // No slots dirty → no iteration events.
1081        assert_eq!(vec.iter_updated_valid().count(), 0);
1082        assert_eq!(vec.iter_updated_indices().count(), 0);
1083    }
1084
1085    #[test]
1086    fn from_vec_empty_is_well_formed() {
1087        let vec: Vector<i32> = Vector::from_vec(Vec::new());
1088        assert_eq!(vec.len(), 0);
1089        assert!(vec.is_empty());
1090        assert!(!vec.is_updated());
1091        assert!(!vec.all_updated());
1092    }
1093
1094    #[test]
1095    fn from_vec_straddles_word_boundary() {
1096        // 100 slots spans two u64 words; check both ranges are valid + clean.
1097        let data: Vec<u32> = (0..100).collect();
1098        let mut vec = Vector::from_vec(data);
1099        for i in 0..100 {
1100            assert_eq!(vec.get_valid(i), Some(&(i as u32)));
1101        }
1102        assert!(!vec.is_updated());
1103
1104        // Writing into a loaded Vector still works.
1105        vec.commit(75, 9999);
1106        let updated: Vec<usize> = vec.iter_updated_indices().collect();
1107        assert_eq!(updated, vec![75]);
1108    }
1109
1110    #[test]
1111    fn from_fill_clones_value() {
1112        let vec = Vector::from_fill(String::from("hi"), 4);
1113        assert_eq!(vec.len(), 4);
1114        assert!(!vec.is_updated());
1115        for i in 0..4 {
1116            assert_eq!(vec.get_valid(i).map(String::as_str), Some("hi"));
1117        }
1118    }
1119
1120    #[test]
1121    fn with_invalid_slots_allocates_invalid_and_clean() {
1122        let mut vec: Vector<u32> = Vector::with_invalid_slots(3);
1123        // Slots are allocated (len reflects them) but all invalid + clean.
1124        assert_eq!(vec.len(), 3);
1125        assert!(!vec.is_empty());
1126        assert!(!vec.is_updated());
1127        for i in 0..3 {
1128            assert!(!vec.is_valid(i));
1129            assert_eq!(vec.get_valid(i), None);
1130        }
1131        // commit() into a pre-allocated slot does not panic and makes it
1132        // valid + dirty; untouched slots stay invalid.
1133        vec.commit(1, 42);
1134        assert_eq!(vec.get_valid(1), Some(&42));
1135        assert!(vec.is_updated_at(1));
1136        assert_eq!(vec.get_valid(0), None);
1137        assert_eq!(vec.get_valid(2), None);
1138    }
1139
1140    #[test]
1141    fn with_invalid_slots_zero_len_is_well_formed() {
1142        let vec: Vector<u32> = Vector::with_invalid_slots(0);
1143        assert_eq!(vec.len(), 0);
1144        assert!(vec.is_empty());
1145        assert!(!vec.is_updated());
1146    }
1147
1148    #[test]
1149    fn as_slice_returns_full_data() {
1150        let mut vec = Vector::new();
1151        vec.push_committed(1);
1152        vec.push_committed(2);
1153        vec.push(3); // invalid placeholder
1154        assert_eq!(vec.as_slice(), &[1, 2, 3]);
1155    }
1156
1157    // -----------------------------------------------------------------
1158    // iter_updated_indices tests
1159    // -----------------------------------------------------------------
1160
1161    #[test]
1162    fn iter_updated_indices_yields_dirty_in_ascending_order() -> Result<(), Error> {
1163        let mut vec = Vector::new();
1164        for _ in 0..5 {
1165            vec.push_committed(0);
1166        }
1167        vec.reset()?;
1168        assert_eq!(vec.iter_updated_indices().count(), 0);
1169
1170        // Commit out of order; iter must still yield in ascending order.
1171        vec.commit(3, 30);
1172        vec.commit(0, 10);
1173        vec.commit(4, 40);
1174
1175        let got: Vec<usize> = vec.iter_updated_indices().collect();
1176        assert_eq!(got, vec![0, 3, 4]);
1177        Ok(())
1178    }
1179
1180    #[test]
1181    fn iter_updated_indices_handles_word_boundaries() {
1182        // Touch slots straddling word boundaries (63 / 64 / 127 / 128).
1183        let mut vec: Vector<u32> = Vector::new();
1184        for _ in 0..200 {
1185            vec.push(0);
1186        }
1187        // Reset so only the commits below count.
1188        vec.reset().unwrap();
1189        for &i in &[0_usize, 63, 64, 127, 128, 199] {
1190            vec.commit(i, i as u32);
1191        }
1192        let got: Vec<usize> = vec.iter_updated_indices().collect();
1193        assert_eq!(got, vec![0, 63, 64, 127, 128, 199]);
1194    }
1195
1196    #[test]
1197    fn iter_updated_indices_empty_when_nothing_dirty() -> Result<(), Error> {
1198        let mut vec: Vector<i32> = Vector::new();
1199        assert_eq!(vec.iter_updated_indices().count(), 0);
1200
1201        vec.push_committed(1);
1202        vec.reset()?;
1203        assert_eq!(vec.iter_updated_indices().count(), 0);
1204        Ok(())
1205    }
1206
1207    #[test]
1208    fn iter_updated_indices_ignores_validity() {
1209        // Dirty + invalid slot must still show up — this iterator
1210        // is index-only and doesn't gate on validity.
1211        let mut vec: Vector<i32> = Vector::new();
1212        vec.push(0); // dirty, invalid
1213        vec.push_committed(99); // dirty, valid
1214        let got: Vec<usize> = vec.iter_updated_indices().collect();
1215        assert_eq!(got, vec![0, 1]);
1216    }
1217
1218    #[test]
1219    fn validity_pop_keeps_remaining_bits_aligned() -> Result<(), Error> {
1220        let mut vec = Vector::new();
1221        vec.push_committed(1);
1222        vec.push(0);
1223        vec.push_committed(3);
1224        vec.reset()?;
1225        assert!(vec.is_valid(0));
1226        assert!(!vec.is_valid(1));
1227        assert!(vec.is_valid(2));
1228
1229        let popped = vec.pop();
1230        assert_eq!(popped, Some(3));
1231        assert_eq!(vec.len(), 2);
1232        assert!(vec.is_valid(0));
1233        assert!(!vec.is_valid(1));
1234        Ok(())
1235    }
1236
1237    #[test]
1238    fn validity_clear_resets_everything() {
1239        let mut vec = Vector::new();
1240        vec.push_committed(1);
1241        vec.push_committed(2);
1242        vec.clear();
1243        assert_eq!(vec.len(), 0);
1244        assert!(vec.is_empty());
1245        assert!(!vec.is_updated());
1246    }
1247
1248    // -----------------------------------------------------------------
1249    // is_updated_at tests
1250    // -----------------------------------------------------------------
1251
1252    #[test]
1253    fn is_updated_at_tracks_per_slot_dirty() -> Result<(), Error> {
1254        let mut vec = Vector::new();
1255        for _ in 0..5 {
1256            vec.push_committed(0_u32);
1257        }
1258        // push_committed marks every slot dirty.
1259        for i in 0..5 {
1260            assert!(vec.is_updated_at(i), "slot {} should be dirty", i);
1261        }
1262        // After reset, no slots are dirty.
1263        vec.reset()?;
1264        for i in 0..5 {
1265            assert!(!vec.is_updated_at(i), "slot {} should be clean", i);
1266        }
1267        // Commit specific slots; only those are dirty.
1268        vec.commit(1, 10);
1269        vec.commit(4, 40);
1270        assert!(!vec.is_updated_at(0));
1271        assert!(vec.is_updated_at(1));
1272        assert!(!vec.is_updated_at(2));
1273        assert!(!vec.is_updated_at(3));
1274        assert!(vec.is_updated_at(4));
1275        Ok(())
1276    }
1277
1278    #[test]
1279    fn is_updated_at_out_of_bounds_returns_false() {
1280        let mut vec: Vector<u32> = Vector::new();
1281        vec.push_committed(0);
1282        assert!(vec.is_updated_at(0));
1283        assert!(!vec.is_updated_at(1));
1284        assert!(!vec.is_updated_at(usize::MAX));
1285    }
1286
1287    #[test]
1288    fn is_updated_at_agrees_with_iter_updated_indices() {
1289        let mut vec: Vector<u32> = Vector::new();
1290        for _ in 0..200 {
1291            vec.push(0);
1292        }
1293        vec.reset().unwrap();
1294        for &i in &[0_usize, 63, 64, 127, 128, 199] {
1295            vec.commit(i, i as u32);
1296        }
1297        let dirty: std::collections::HashSet<usize> = vec.iter_updated_indices().collect();
1298        for i in 0..200 {
1299            assert_eq!(
1300                vec.is_updated_at(i),
1301                dirty.contains(&i),
1302                "mismatch at slot {}",
1303                i
1304            );
1305        }
1306    }
1307
1308    #[test]
1309    fn is_updated_at_set_by_invalidate() {
1310        let mut vec = Vector::new();
1311        vec.push_committed(7);
1312        vec.reset().unwrap();
1313        assert!(!vec.is_updated_at(0));
1314        vec.invalidate(0);
1315        assert!(
1316            vec.is_updated_at(0),
1317            "invalidate is a write — should set the dirty bit"
1318        );
1319    }
1320}