Skip to main content

cranpose_core/
snapshot_id_set.rs

1/// An optimized bit-set implementation for tracking snapshot IDs.
2///
3/// This is based on Jetpack Compose's SnapshotIdSet, optimized for:
4/// - O(1) access for the most recent 128 snapshot IDs
5/// - O(log N) access for older snapshots
6/// - Immutable copy-on-write semantics
7///
8/// The set maintains:
9/// - `lower_set`: 64 bits for IDs in range [lower_bound, lower_bound+63]
10/// - `upper_set`: 64 bits for IDs in range [lower_bound+64, lower_bound+127]
11/// - `below_bound`: sorted array for IDs below lower_bound
12///
13/// This structure is highly biased toward recent snapshots being set,
14/// with older snapshots mostly or completely clear.
15use std::fmt;
16
17pub type SnapshotId = usize;
18
19const BITS_PER_SET: usize = 64;
20const SNAPSHOT_ID_SIZE: usize = 64;
21
22#[derive(Clone, PartialEq, Eq)]
23pub struct SnapshotIdSet {
24    /// Bit set from (lower_bound + 64) to (lower_bound + 127)
25    upper_set: u64,
26    /// Bit set from lower_bound to (lower_bound + 63)
27    lower_set: u64,
28    /// Lower bound of the bit set. All values above lower_bound+127 are clear.
29    lower_bound: SnapshotId,
30    /// Sorted array of snapshot IDs below lower_bound
31    below_bound: Option<Box<[SnapshotId]>>,
32}
33
34impl SnapshotIdSet {
35    /// Empty snapshot ID set.
36    pub const EMPTY: SnapshotIdSet = SnapshotIdSet {
37        upper_set: 0,
38        lower_set: 0,
39        lower_bound: 0,
40        below_bound: None,
41    };
42
43    /// Create a new empty snapshot ID set.
44    pub fn new() -> Self {
45        Self::EMPTY
46    }
47
48    /// Check if a snapshot ID is in the set.
49    pub fn get(&self, id: SnapshotId) -> bool {
50        let offset = id.wrapping_sub(self.lower_bound);
51
52        if offset < BITS_PER_SET {
53            // In lower_set range
54            let mask = 1u64 << offset;
55            (self.lower_set & mask) != 0
56        } else if offset < BITS_PER_SET * 2 {
57            // In upper_set range
58            let mask = 1u64 << (offset - BITS_PER_SET);
59            (self.upper_set & mask) != 0
60        } else if id > self.lower_bound {
61            // Above our tracked range
62            false
63        } else {
64            // Below lower_bound, check the array
65            self.below_bound
66                .as_ref()
67                .map(|arr| arr.binary_search(&id).is_ok())
68                .unwrap_or(false)
69        }
70    }
71
72    /// Add a snapshot ID to the set (returns a new set if modified).
73    pub fn set(&self, id: SnapshotId) -> Self {
74        if id < self.lower_bound {
75            if let Some(ref arr) = self.below_bound {
76                match arr.binary_search(&id) {
77                    Ok(_) => {
78                        // Already present
79                        return self.clone();
80                    }
81                    Err(insert_pos) => {
82                        // Insert at position
83                        let mut new_arr = Vec::with_capacity(arr.len() + 1);
84                        new_arr.extend_from_slice(&arr[..insert_pos]);
85                        new_arr.push(id);
86                        new_arr.extend_from_slice(&arr[insert_pos..]);
87                        return Self {
88                            upper_set: self.upper_set,
89                            lower_set: self.lower_set,
90                            lower_bound: self.lower_bound,
91                            below_bound: Some(new_arr.into_boxed_slice()),
92                        };
93                    }
94                }
95            } else {
96                // First element below bound
97                return Self {
98                    upper_set: self.upper_set,
99                    lower_set: self.lower_set,
100                    lower_bound: self.lower_bound,
101                    below_bound: Some(vec![id].into_boxed_slice()),
102                };
103            }
104        }
105
106        let offset = id - self.lower_bound;
107
108        if offset < BITS_PER_SET {
109            // In lower_set range
110            let mask = 1u64 << offset;
111            if (self.lower_set & mask) == 0 {
112                return Self {
113                    upper_set: self.upper_set,
114                    lower_set: self.lower_set | mask,
115                    lower_bound: self.lower_bound,
116                    below_bound: self.below_bound.clone(),
117                };
118            }
119        } else if offset < BITS_PER_SET * 2 {
120            // In upper_set range
121            let mask = 1u64 << (offset - BITS_PER_SET);
122            if (self.upper_set & mask) == 0 {
123                return Self {
124                    upper_set: self.upper_set | mask,
125                    lower_set: self.lower_set,
126                    lower_bound: self.lower_bound,
127                    below_bound: self.below_bound.clone(),
128                };
129            }
130        } else if offset >= BITS_PER_SET * 2 {
131            // Need to shift the bit arrays
132            if !self.get(id) {
133                return self.shift_and_set(id);
134            }
135        }
136
137        // No change needed
138        self.clone()
139    }
140
141    /// Remove a snapshot ID from the set (returns a new set if modified).
142    pub fn clear(&self, id: SnapshotId) -> Self {
143        let offset = id.wrapping_sub(self.lower_bound);
144
145        if offset < BITS_PER_SET {
146            // In lower_set range
147            let mask = 1u64 << offset;
148            if (self.lower_set & mask) != 0 {
149                return Self {
150                    upper_set: self.upper_set,
151                    lower_set: self.lower_set & !mask,
152                    lower_bound: self.lower_bound,
153                    below_bound: self.below_bound.clone(),
154                };
155            }
156        } else if offset < BITS_PER_SET * 2 {
157            // In upper_set range
158            let mask = 1u64 << (offset - BITS_PER_SET);
159            if (self.upper_set & mask) != 0 {
160                return Self {
161                    upper_set: self.upper_set & !mask,
162                    lower_set: self.lower_set,
163                    lower_bound: self.lower_bound,
164                    below_bound: self.below_bound.clone(),
165                };
166            }
167        } else if id < self.lower_bound {
168            // Below lower_bound
169            if let Some(ref arr) = self.below_bound
170                && let Ok(pos) = arr.binary_search(&id)
171            {
172                let mut new_arr = Vec::with_capacity(arr.len() - 1);
173                new_arr.extend_from_slice(&arr[..pos]);
174                new_arr.extend_from_slice(&arr[pos + 1..]);
175                return Self {
176                    upper_set: self.upper_set,
177                    lower_set: self.lower_set,
178                    lower_bound: self.lower_bound,
179                    below_bound: if new_arr.is_empty() {
180                        None
181                    } else {
182                        Some(new_arr.into_boxed_slice())
183                    },
184                };
185            }
186        }
187
188        // No change needed
189        self.clone()
190    }
191
192    /// Remove all IDs in `other` from this set (a & ~b).
193    pub fn and_not(&self, other: &Self) -> Self {
194        if other.is_empty() {
195            return self.clone();
196        }
197        if self.is_empty() {
198            return Self::EMPTY;
199        }
200
201        // Fast path: if both have same lower_bound and below_bound, can do bitwise ops
202        if self.lower_bound == other.lower_bound && self.below_bound_equals(&other.below_bound) {
203            return Self {
204                upper_set: self.upper_set & !other.upper_set,
205                lower_set: self.lower_set & !other.lower_set,
206                lower_bound: self.lower_bound,
207                below_bound: self.below_bound.clone(),
208            };
209        }
210
211        // Slow path: iterate and clear each ID
212        let mut result = self.clone();
213        for id in other.iter() {
214            result = result.clear(id);
215        }
216        result
217    }
218
219    /// Union this set with another (a | b).
220    pub fn or(&self, other: &Self) -> Self {
221        if other.is_empty() {
222            return self.clone();
223        }
224        if self.is_empty() {
225            return other.clone();
226        }
227
228        // Fast path: if both have same lower_bound and below_bound
229        if self.lower_bound == other.lower_bound && self.below_bound_equals(&other.below_bound) {
230            return Self {
231                upper_set: self.upper_set | other.upper_set,
232                lower_set: self.lower_set | other.lower_set,
233                lower_bound: self.lower_bound,
234                below_bound: self.below_bound.clone(),
235            };
236        }
237
238        // Slow path: iterate and set each ID
239        let mut result = self.clone();
240        for id in other.iter() {
241            result = result.set(id);
242        }
243        result
244    }
245
246    /// Find the lowest snapshot ID in the set that is <= upper.
247    pub fn lowest(&self, upper: SnapshotId) -> SnapshotId {
248        // Check below_bound array first
249        if let Some(ref arr) = self.below_bound
250            && let Some(&lowest) = arr.first()
251            && lowest <= upper
252        {
253            return lowest;
254        }
255
256        // Check lower_set
257        if self.lower_set != 0 {
258            let lowest_in_lower = self.lower_bound + self.lower_set.trailing_zeros() as usize;
259            if lowest_in_lower <= upper {
260                return lowest_in_lower;
261            }
262        }
263
264        // Check upper_set
265        if self.upper_set != 0 {
266            let lowest_in_upper =
267                self.lower_bound + BITS_PER_SET + self.upper_set.trailing_zeros() as usize;
268            if lowest_in_upper <= upper {
269                return lowest_in_upper;
270            }
271        }
272
273        // Nothing found, return upper
274        upper
275    }
276
277    /// Check if the set is empty.
278    pub fn is_empty(&self) -> bool {
279        self.lower_set == 0 && self.upper_set == 0 && self.below_bound.is_none()
280    }
281
282    /// Iterate over all snapshot IDs in the set.
283    pub fn iter(&self) -> SnapshotIdSetIter<'_> {
284        SnapshotIdSetIter::new(self)
285    }
286
287    /// Convert to a Vec of snapshot IDs (for testing/debugging).
288    pub fn to_list(&self) -> Vec<SnapshotId> {
289        self.iter().collect()
290    }
291
292    /// Add a contiguous range of IDs [from, until) to the set.
293    /// Mirrors AndroidX SnapshotIdSet.addRange semantics used by Snapshot.kt.
294    pub fn add_range(&self, from: SnapshotId, until: SnapshotId) -> Self {
295        if from >= until {
296            return self.clone();
297        }
298        let mut result = self.clone();
299        let mut id = from;
300        while id < until {
301            result = result.set(id);
302            id += 1;
303        }
304        result
305    }
306
307    // Helper: check if two below_bound arrays are equal
308    fn below_bound_equals(&self, other: &Option<Box<[SnapshotId]>>) -> bool {
309        match (&self.below_bound, other) {
310            (None, None) => true,
311            (Some(a), Some(b)) => a == b,
312            _ => false,
313        }
314    }
315
316    // Helper: shift the bit arrays and set a new ID
317    fn shift_and_set(&self, id: SnapshotId) -> Self {
318        let target_lower_bound = (id / SNAPSHOT_ID_SIZE) * SNAPSHOT_ID_SIZE;
319
320        let mut new_upper_set = self.upper_set;
321        let mut new_lower_set = self.lower_set;
322        let mut new_lower_bound = self.lower_bound;
323        let mut new_below_bound: Vec<SnapshotId> = if let Some(ref arr) = self.below_bound {
324            arr.to_vec()
325        } else {
326            Vec::new()
327        };
328
329        while new_lower_bound < target_lower_bound {
330            // Shift lower_set into below_bound array
331            if new_lower_set != 0 {
332                for bit_offset in 0..BITS_PER_SET {
333                    if (new_lower_set & (1u64 << bit_offset)) != 0 {
334                        let id_to_add = new_lower_bound + bit_offset;
335                        // Insert in sorted order
336                        match new_below_bound.binary_search(&id_to_add) {
337                            Ok(_) => {} // Already present (shouldn't happen)
338                            Err(pos) => new_below_bound.insert(pos, id_to_add),
339                        }
340                    }
341                }
342            }
343
344            // Shift upper_set down to lower_set
345            if new_upper_set == 0 {
346                new_lower_bound = target_lower_bound;
347                new_lower_set = 0;
348                break;
349            }
350
351            new_lower_set = new_upper_set;
352            new_upper_set = 0;
353            new_lower_bound += BITS_PER_SET;
354        }
355
356        let result = Self {
357            upper_set: new_upper_set,
358            lower_set: new_lower_set,
359            lower_bound: new_lower_bound,
360            below_bound: if new_below_bound.is_empty() {
361                None
362            } else {
363                Some(new_below_bound.into_boxed_slice())
364            },
365        };
366
367        // Now set the ID
368        result.set(id)
369    }
370}
371
372impl Default for SnapshotIdSet {
373    fn default() -> Self {
374        Self::EMPTY
375    }
376}
377
378impl fmt::Debug for SnapshotIdSet {
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        write!(f, "SnapshotIdSet{{")?;
381        let ids: Vec<_> = self.iter().collect();
382        for (i, id) in ids.iter().enumerate() {
383            if i > 0 {
384                write!(f, ", ")?;
385            }
386            write!(f, "{}", id)?;
387        }
388        write!(f, "}}")
389    }
390}
391
392/// Iterator over snapshot IDs in a set.
393pub struct SnapshotIdSetIter<'a> {
394    set: &'a SnapshotIdSet,
395    below_index: usize,
396    lower_set: u64,
397    upper_set: u64,
398    current_offset: usize,
399}
400
401impl<'a> SnapshotIdSetIter<'a> {
402    fn new(set: &'a SnapshotIdSet) -> Self {
403        Self {
404            set,
405            below_index: 0,
406            lower_set: set.lower_set,
407            upper_set: set.upper_set,
408            current_offset: 0,
409        }
410    }
411}
412
413impl<'a> Iterator for SnapshotIdSetIter<'a> {
414    type Item = SnapshotId;
415
416    fn next(&mut self) -> Option<Self::Item> {
417        // First, yield from below_bound array
418        if let Some(ref arr) = self.set.below_bound
419            && self.below_index < arr.len()
420        {
421            let id = arr[self.below_index];
422            self.below_index += 1;
423            return Some(id);
424        }
425
426        // Then yield from lower_set
427        while self.current_offset < BITS_PER_SET {
428            if (self.lower_set & (1u64 << self.current_offset)) != 0 {
429                let id = self.set.lower_bound + self.current_offset;
430                self.current_offset += 1;
431                return Some(id);
432            }
433            self.current_offset += 1;
434        }
435
436        // Finally yield from upper_set
437        while self.current_offset < BITS_PER_SET * 2 {
438            let bit_offset = self.current_offset - BITS_PER_SET;
439            if (self.upper_set & (1u64 << bit_offset)) != 0 {
440                let id = self.set.lower_bound + self.current_offset;
441                self.current_offset += 1;
442                return Some(id);
443            }
444            self.current_offset += 1;
445        }
446
447        None
448    }
449}
450
451#[cfg(test)]
452mod tests {
453    use super::*;
454
455    #[test]
456    fn test_empty_set() {
457        let set = SnapshotIdSet::EMPTY;
458        assert!(set.is_empty());
459        assert!(!set.get(0));
460        assert!(!set.get(100));
461    }
462
463    #[test]
464    fn test_set_and_get_lower_range() {
465        let set = SnapshotIdSet::new();
466        let set = set.set(0);
467        assert!(set.get(0));
468        assert!(!set.get(1));
469
470        let set = set.set(63);
471        assert!(set.get(0));
472        assert!(set.get(63));
473        assert!(!set.get(64));
474    }
475
476    #[test]
477    fn test_set_and_get_upper_range() {
478        let set = SnapshotIdSet::new();
479        let set = set.set(64);
480        assert!(set.get(64));
481        assert!(!set.get(63));
482        assert!(!set.get(128));
483
484        let set = set.set(127);
485        assert!(set.get(64));
486        assert!(set.get(127));
487        assert!(!set.get(128));
488    }
489
490    #[test]
491    fn test_set_idempotent() {
492        let set = SnapshotIdSet::new();
493        let set1 = set.set(10);
494        let set2 = set1.set(10);
495        assert_eq!(set1, set2);
496    }
497
498    #[test]
499    fn test_clear() {
500        let set = SnapshotIdSet::new().set(10).set(20).set(30);
501        assert!(set.get(10));
502        assert!(set.get(20));
503        assert!(set.get(30));
504
505        let set = set.clear(20);
506        assert!(set.get(10));
507        assert!(!set.get(20));
508        assert!(set.get(30));
509    }
510
511    #[test]
512    fn test_clear_idempotent() {
513        let set = SnapshotIdSet::new().set(10);
514        let set1 = set.clear(10);
515        let set2 = set1.clear(10);
516        assert_eq!(set1, set2);
517    }
518
519    #[test]
520    fn test_below_bound_insertion() {
521        let mut set = SnapshotIdSet::new();
522        // Set lower_bound to 100
523        set = set.set(100);
524        assert_eq!(set.lower_bound, 0);
525
526        // Now insert something below lower_bound
527        set = set.set(50);
528        assert!(set.get(50));
529        assert!(set.get(100));
530
531        set = set.set(25);
532        set = set.set(75);
533        assert!(set.get(25));
534        assert!(set.get(50));
535        assert!(set.get(75));
536        assert!(set.get(100));
537
538        // Check that below_bound is sorted
539        let list = set.to_list();
540        assert_eq!(list, vec![25, 50, 75, 100]);
541    }
542
543    #[test]
544    fn test_below_bound_removal() {
545        // Build incrementally to avoid stack overflow from large shifts
546        let set = SnapshotIdSet::new();
547        let set = set.set(25);
548        let set = set.set(50);
549        let set = set.set(75);
550        let set = set.set(200);
551
552        let set = set.clear(50);
553        assert!(set.get(25));
554        assert!(!set.get(50));
555        assert!(set.get(75));
556        assert!(set.get(200));
557
558        let list = set.to_list();
559        assert_eq!(list, vec![25, 75, 200]);
560    }
561
562    #[test]
563    fn test_shift_and_set() {
564        let set = SnapshotIdSet::new();
565        let set = set.set(10);
566        assert_eq!(set.lower_bound, 0);
567
568        // Setting a value way above should shift the arrays
569        let set = set.set(200);
570        assert!(set.get(10));
571        assert!(set.get(200));
572
573        // 10 should now be in below_bound
574        assert!(set.below_bound.is_some());
575    }
576
577    #[test]
578    fn test_shift_and_set_boundary_values() {
579        let mut set = SnapshotIdSet::new();
580        let boundary = SNAPSHOT_ID_SIZE * 12 - 1;
581        set = set.set(boundary);
582        assert!(set.get(boundary));
583
584        set = set.set(boundary + 1);
585        assert!(set.get(boundary));
586        assert!(set.get(boundary + 1));
587    }
588
589    #[test]
590    fn test_set_below_lower_bound_inserts() {
591        let set = SnapshotIdSet::new().set(200);
592        let lower_bound = set.lower_bound;
593        assert!(lower_bound > 0);
594
595        let below = lower_bound - 1;
596        let set = set.set(below);
597        assert!(set.get(below));
598        assert!(set.get(200));
599    }
600
601    #[test]
602    fn test_and_not_fast_path() {
603        let set1 = SnapshotIdSet::new().set(10).set(20).set(30);
604        let set2 = SnapshotIdSet::new().set(20).set(40);
605
606        let result = set1.and_not(&set2);
607        assert!(result.get(10));
608        assert!(!result.get(20));
609        assert!(result.get(30));
610        assert!(!result.get(40));
611    }
612
613    #[test]
614    fn test_and_not_slow_path() {
615        let set1 = SnapshotIdSet::new().set(10).set(20).set(30);
616        // Create set2 with different lower_bound by setting high value first
617        let set2 = SnapshotIdSet::new().set(100).set(20);
618
619        let result = set1.and_not(&set2);
620        assert!(result.get(10));
621        assert!(!result.get(20));
622        assert!(result.get(30));
623    }
624
625    #[test]
626    fn test_or_fast_path() {
627        let set1 = SnapshotIdSet::new().set(10).set(20);
628        let set2 = SnapshotIdSet::new().set(20).set(30);
629
630        let result = set1.or(&set2);
631        assert!(result.get(10));
632        assert!(result.get(20));
633        assert!(result.get(30));
634    }
635
636    #[test]
637    fn test_or_slow_path() {
638        let set1 = SnapshotIdSet::new().set(10).set(20);
639        let set2 = SnapshotIdSet::new().set(100).set(30);
640
641        let result = set1.or(&set2);
642        assert!(result.get(10));
643        assert!(result.get(20));
644        assert!(result.get(30));
645        assert!(result.get(100));
646    }
647
648    #[test]
649    fn test_lowest_in_below_bound() {
650        // Build incrementally to avoid deep recursion
651        let set = SnapshotIdSet::new();
652        let set = set.set(25);
653        let set = set.set(50);
654        let set = set.set(200);
655        assert_eq!(set.lowest(1000), 25);
656        assert_eq!(set.lowest(100), 25);
657        assert_eq!(set.lowest(30), 25);
658    }
659
660    #[test]
661    fn test_lowest_in_lower_set() {
662        let set = SnapshotIdSet::new().set(10).set(20).set(30);
663        assert_eq!(set.lowest(1000), 10);
664        assert_eq!(set.lowest(25), 10);
665    }
666
667    #[test]
668    fn test_lowest_in_upper_set() {
669        let set = SnapshotIdSet::new().set(70).set(80).set(90);
670        assert_eq!(set.lowest(1000), 70);
671    }
672
673    #[test]
674    fn test_lowest_returns_upper_if_none_found() {
675        let set = SnapshotIdSet::new().set(100);
676        assert_eq!(set.lowest(50), 50);
677    }
678
679    #[test]
680    fn test_iterator() {
681        let set = SnapshotIdSet::new().set(10).set(20).set(5).set(30);
682        let list: Vec<_> = set.iter().collect();
683        // Should be in sorted order
684        assert_eq!(list, vec![5, 10, 20, 30]);
685    }
686
687    #[test]
688    fn test_iterator_empty() {
689        let set = SnapshotIdSet::new();
690        let list: Vec<_> = set.iter().collect();
691        assert_eq!(list, Vec::<SnapshotId>::new());
692    }
693
694    #[test]
695    fn test_iterator_all_ranges() {
696        let set = SnapshotIdSet::new()
697            .set(5) // below_bound (after shift)
698            .set(10) // lower_set (after shift)
699            .set(70) // upper_set (after shift)
700            .set(200); // causes shift
701
702        let list: Vec<_> = set.iter().collect();
703        assert_eq!(list, vec![5, 10, 70, 200]);
704    }
705
706    #[test]
707    fn test_to_list() {
708        let set = SnapshotIdSet::new().set(10).set(20).set(30);
709        assert_eq!(set.to_list(), vec![10, 20, 30]);
710    }
711
712    #[test]
713    fn test_debug_format() {
714        let set = SnapshotIdSet::new().set(10).set(20);
715        let debug_str = format!("{:?}", set);
716        assert_eq!(debug_str, "SnapshotIdSet{10, 20}");
717    }
718
719    #[test]
720    fn test_large_snapshot_ids() {
721        // Build incrementally to avoid deep recursion
722        let set = SnapshotIdSet::new();
723        let set = set.set(500);
724        let set = set.set(1000);
725        let set = set.set(2000);
726
727        assert!(set.get(500));
728        assert!(set.get(1000));
729        assert!(set.get(2000));
730        assert!(!set.get(1500));
731    }
732
733    #[test]
734    fn test_boundary_transitions() {
735        let set = SnapshotIdSet::new();
736
737        // Test transition from lower to upper
738        let set = set.set(63);
739        let set = set.set(64);
740        assert!(set.get(63));
741        assert!(set.get(64));
742
743        // Test transition from upper to above
744        let set = set.set(127);
745        let set = set.set(128);
746        assert!(set.get(127));
747        assert!(set.get(128));
748    }
749}