Skip to main content

ftui_render/
roaring_bitmap.rs

1#![forbid(unsafe_code)]
2
3//! Minimal Roaring Bitmap for cell-level dirty region tracking.
4//!
5//! Roaring bitmaps efficiently represent sets of integers by adaptively
6//! switching between two container types based on density:
7//!
8//! - **Array container**: Sorted `Vec<u16>` for sparse regions (< 4096 entries).
9//! - **Bitmap container**: `[u64; 1024]` (8 KB) for dense regions (>= 4096 entries).
10//!
11//! Indices are split into `(high16, low16)`: the high 16 bits select the
12//! container, the low 16 bits select the position within it.
13//!
14//! # Usage
15//!
16//! ```rust
17//! use ftui_render::roaring_bitmap::RoaringBitmap;
18//!
19//! let mut bm = RoaringBitmap::new();
20//! bm.insert(42);
21//! bm.insert(1000);
22//! bm.insert(42); // duplicate is a no-op
23//!
24//! assert!(bm.contains(42));
25//! assert_eq!(bm.cardinality(), 2);
26//!
27//! let items: Vec<u32> = bm.iter().collect();
28//! assert_eq!(items, vec![42, 1000]);
29//! ```
30
31/// Threshold at which an array container is promoted to a bitmap container.
32const ARRAY_TO_BITMAP_THRESHOLD: usize = 4096;
33
34// ============================================================================
35// Container Types
36// ============================================================================
37
38/// A sorted array of 16-bit values (sparse container).
39#[derive(Clone, Debug)]
40struct ArrayContainer {
41    values: Vec<u16>,
42}
43
44impl ArrayContainer {
45    fn new() -> Self {
46        Self { values: Vec::new() }
47    }
48
49    fn insert(&mut self, value: u16) -> bool {
50        match self.values.binary_search(&value) {
51            Ok(_) => false, // already present
52            Err(pos) => {
53                self.values.insert(pos, value);
54                true
55            }
56        }
57    }
58
59    fn contains(&self, value: u16) -> bool {
60        self.values.binary_search(&value).is_ok()
61    }
62
63    fn cardinality(&self) -> usize {
64        self.values.len()
65    }
66
67    fn should_promote(&self) -> bool {
68        self.values.len() >= ARRAY_TO_BITMAP_THRESHOLD
69    }
70
71    /// Convert to bitmap container.
72    fn to_bitmap(&self) -> BitmapContainer {
73        let mut bitmap = BitmapContainer::new();
74        for &v in &self.values {
75            bitmap.insert(v);
76        }
77        bitmap
78    }
79}
80
81/// A fixed-size bitmap of 2^16 bits (dense container).
82#[derive(Clone, Debug)]
83struct BitmapContainer {
84    words: Box<[u64; 1024]>,
85    count: usize,
86}
87
88impl BitmapContainer {
89    fn new() -> Self {
90        Self {
91            words: Box::new([0u64; 1024]),
92            count: 0,
93        }
94    }
95
96    fn insert(&mut self, value: u16) -> bool {
97        let word_idx = (value >> 6) as usize;
98        let bit = 1u64 << (value & 63);
99        if self.words[word_idx] & bit == 0 {
100            self.words[word_idx] |= bit;
101            self.count += 1;
102            true
103        } else {
104            false
105        }
106    }
107
108    fn contains(&self, value: u16) -> bool {
109        let word_idx = (value >> 6) as usize;
110        let bit = 1u64 << (value & 63);
111        self.words[word_idx] & bit != 0
112    }
113
114    fn cardinality(&self) -> usize {
115        self.count
116    }
117
118    fn iter(&self) -> BitmapIter<'_> {
119        BitmapIter {
120            words: &self.words,
121            word_idx: 0,
122            current_word: 0,
123            started: false,
124        }
125    }
126}
127
128struct BitmapIter<'a> {
129    words: &'a [u64; 1024],
130    word_idx: usize,
131    current_word: u64,
132    started: bool,
133}
134
135impl Iterator for BitmapIter<'_> {
136    type Item = u16;
137
138    fn next(&mut self) -> Option<u16> {
139        if !self.started {
140            if self.word_idx < 1024 {
141                self.current_word = self.words[0];
142            }
143            self.started = true;
144        }
145
146        loop {
147            if self.current_word != 0 {
148                let bit = self.current_word.trailing_zeros() as u16;
149                self.current_word &= self.current_word - 1; // clear lowest set bit
150                return Some((self.word_idx as u16) * 64 + bit);
151            }
152            self.word_idx += 1;
153            if self.word_idx >= 1024 {
154                return None;
155            }
156            self.current_word = self.words[self.word_idx];
157        }
158    }
159}
160
161// ============================================================================
162// Container Enum
163// ============================================================================
164
165#[derive(Clone, Debug)]
166enum Container {
167    Array(ArrayContainer),
168    Bitmap(BitmapContainer),
169}
170
171impl Container {
172    fn new_array() -> Self {
173        Self::Array(ArrayContainer::new())
174    }
175
176    fn insert(&mut self, value: u16) -> bool {
177        match self {
178            Self::Array(arr) => {
179                let inserted = arr.insert(value);
180                if arr.should_promote() {
181                    *self = Self::Bitmap(arr.to_bitmap());
182                }
183                inserted
184            }
185            Self::Bitmap(bm) => bm.insert(value),
186        }
187    }
188
189    fn contains(&self, value: u16) -> bool {
190        match self {
191            Self::Array(arr) => arr.contains(value),
192            Self::Bitmap(bm) => bm.contains(value),
193        }
194    }
195
196    fn cardinality(&self) -> usize {
197        match self {
198            Self::Array(arr) => arr.cardinality(),
199            Self::Bitmap(bm) => bm.cardinality(),
200        }
201    }
202}
203
204// ============================================================================
205// Roaring Bitmap
206// ============================================================================
207
208/// Key-container pair, sorted by key.
209#[derive(Clone, Debug)]
210struct ContainerEntry {
211    key: u16,
212    container: Container,
213}
214
215/// A Roaring Bitmap for efficiently tracking sets of `u32` indices.
216///
217/// Optimized for the mix of sparse and dense dirty regions typical in
218/// terminal UI rendering. Indices are split into (high16, low16) pairs
219/// to select containers adaptively.
220#[derive(Clone, Debug)]
221pub struct RoaringBitmap {
222    containers: Vec<ContainerEntry>,
223}
224
225impl RoaringBitmap {
226    /// Create an empty bitmap.
227    #[must_use]
228    pub fn new() -> Self {
229        Self {
230            containers: Vec::new(),
231        }
232    }
233
234    /// Insert a value into the bitmap. Returns `true` if the value was new.
235    pub fn insert(&mut self, value: u32) -> bool {
236        let key = (value >> 16) as u16;
237        let low = value as u16;
238
239        match self.containers.binary_search_by_key(&key, |e| e.key) {
240            Ok(idx) => self.containers[idx].container.insert(low),
241            Err(idx) => {
242                let mut entry = ContainerEntry {
243                    key,
244                    container: Container::new_array(),
245                };
246                entry.container.insert(low);
247                self.containers.insert(idx, entry);
248                true
249            }
250        }
251    }
252
253    /// Check if the bitmap contains a value.
254    #[must_use]
255    pub fn contains(&self, value: u32) -> bool {
256        let key = (value >> 16) as u16;
257        let low = value as u16;
258
259        match self.containers.binary_search_by_key(&key, |e| e.key) {
260            Ok(idx) => self.containers[idx].container.contains(low),
261            Err(_) => false,
262        }
263    }
264
265    /// Return the number of values in the bitmap.
266    #[must_use]
267    pub fn cardinality(&self) -> usize {
268        self.containers
269            .iter()
270            .map(|e| e.container.cardinality())
271            .sum()
272    }
273
274    /// Check if the bitmap is empty.
275    #[must_use]
276    pub fn is_empty(&self) -> bool {
277        self.containers
278            .iter()
279            .all(|e| e.container.cardinality() == 0)
280    }
281
282    /// Remove all values.
283    pub fn clear(&mut self) {
284        self.containers.clear();
285    }
286
287    /// Iterate over all values in sorted order.
288    pub fn iter(&self) -> RoaringIter<'_> {
289        RoaringIter {
290            containers: &self.containers,
291            container_idx: 0,
292            inner: InnerIter::Empty,
293        }
294    }
295
296    /// Compute the union of two bitmaps.
297    #[must_use]
298    pub fn union(&self, other: &Self) -> Self {
299        let mut result = self.clone();
300        for value in other.iter() {
301            result.insert(value);
302        }
303        result
304    }
305
306    /// Compute the intersection of two bitmaps.
307    #[must_use]
308    pub fn intersection(&self, other: &Self) -> Self {
309        let mut result = Self::new();
310        // Iterate over the smaller bitmap for efficiency.
311        let (smaller, larger) = if self.cardinality() <= other.cardinality() {
312            (self, other)
313        } else {
314            (other, self)
315        };
316        for value in smaller.iter() {
317            if larger.contains(value) {
318                result.insert(value);
319            }
320        }
321        result
322    }
323
324    /// Insert a range of values `[start, end)`.
325    pub fn insert_range(&mut self, start: u32, end: u32) {
326        for value in start..end {
327            self.insert(value);
328        }
329    }
330}
331
332impl Default for RoaringBitmap {
333    fn default() -> Self {
334        Self::new()
335    }
336}
337
338// ============================================================================
339// Iterator
340// ============================================================================
341
342enum InnerIter<'a> {
343    Empty,
344    Array {
345        key: u16,
346        iter: std::slice::Iter<'a, u16>,
347    },
348    Bitmap {
349        key: u16,
350        iter: BitmapIter<'a>,
351    },
352}
353
354/// Iterator over all values in a [`RoaringBitmap`] in sorted order.
355pub struct RoaringIter<'a> {
356    containers: &'a [ContainerEntry],
357    container_idx: usize,
358    inner: InnerIter<'a>,
359}
360
361impl Iterator for RoaringIter<'_> {
362    type Item = u32;
363
364    fn next(&mut self) -> Option<u32> {
365        loop {
366            match &mut self.inner {
367                InnerIter::Empty => {}
368                InnerIter::Array { key, iter } => {
369                    if let Some(&low) = iter.next() {
370                        return Some(((*key as u32) << 16) | low as u32);
371                    }
372                }
373                InnerIter::Bitmap { key, iter } => {
374                    if let Some(low) = iter.next() {
375                        return Some(((*key as u32) << 16) | low as u32);
376                    }
377                }
378            }
379
380            // Advance to next container.
381            if self.container_idx >= self.containers.len() {
382                return None;
383            }
384            let entry = &self.containers[self.container_idx];
385            self.container_idx += 1;
386
387            self.inner = match &entry.container {
388                Container::Array(arr) => InnerIter::Array {
389                    key: entry.key,
390                    iter: arr.values.iter(),
391                },
392                Container::Bitmap(bm) => InnerIter::Bitmap {
393                    key: entry.key,
394                    iter: bm.iter(),
395                },
396            };
397        }
398    }
399}
400
401// ============================================================================
402// Tests
403// ============================================================================
404
405#[cfg(test)]
406mod tests {
407    use super::*;
408
409    #[test]
410    fn empty_bitmap() {
411        let bm = RoaringBitmap::new();
412        assert_eq!(bm.cardinality(), 0);
413        assert!(bm.is_empty());
414        assert!(!bm.contains(0));
415        assert_eq!(bm.iter().count(), 0);
416    }
417
418    #[test]
419    fn insert_and_contains() {
420        let mut bm = RoaringBitmap::new();
421        assert!(bm.insert(42));
422        assert!(!bm.insert(42)); // duplicate
423        assert!(bm.contains(42));
424        assert!(!bm.contains(43));
425        assert_eq!(bm.cardinality(), 1);
426    }
427
428    #[test]
429    fn insert_multiple_containers() {
430        let mut bm = RoaringBitmap::new();
431        // Values in different containers (different high 16 bits).
432        bm.insert(0);
433        bm.insert(65536); // container key = 1
434        bm.insert(131072); // container key = 2
435
436        assert_eq!(bm.cardinality(), 3);
437        assert!(bm.contains(0));
438        assert!(bm.contains(65536));
439        assert!(bm.contains(131072));
440    }
441
442    #[test]
443    fn iteration_order() {
444        let mut bm = RoaringBitmap::new();
445        bm.insert(100);
446        bm.insert(5);
447        bm.insert(50);
448        bm.insert(1);
449
450        let values: Vec<u32> = bm.iter().collect();
451        assert_eq!(values, vec![1, 5, 50, 100]);
452    }
453
454    #[test]
455    fn iteration_across_containers() {
456        let mut bm = RoaringBitmap::new();
457        bm.insert(65537); // container 1, value 1
458        bm.insert(10); // container 0, value 10
459        bm.insert(65536); // container 1, value 0
460
461        let values: Vec<u32> = bm.iter().collect();
462        assert_eq!(values, vec![10, 65536, 65537]);
463    }
464
465    #[test]
466    fn clear() {
467        let mut bm = RoaringBitmap::new();
468        bm.insert(1);
469        bm.insert(2);
470        bm.insert(3);
471        bm.clear();
472        assert_eq!(bm.cardinality(), 0);
473        assert!(bm.is_empty());
474    }
475
476    #[test]
477    fn union_basic() {
478        let mut a = RoaringBitmap::new();
479        a.insert(1);
480        a.insert(3);
481
482        let mut b = RoaringBitmap::new();
483        b.insert(2);
484        b.insert(3);
485
486        let c = a.union(&b);
487        assert_eq!(c.cardinality(), 3);
488        assert!(c.contains(1));
489        assert!(c.contains(2));
490        assert!(c.contains(3));
491    }
492
493    #[test]
494    fn intersection_basic() {
495        let mut a = RoaringBitmap::new();
496        a.insert(1);
497        a.insert(2);
498        a.insert(3);
499
500        let mut b = RoaringBitmap::new();
501        b.insert(2);
502        b.insert(3);
503        b.insert(4);
504
505        let c = a.intersection(&b);
506        assert_eq!(c.cardinality(), 2);
507        assert!(c.contains(2));
508        assert!(c.contains(3));
509        assert!(!c.contains(1));
510        assert!(!c.contains(4));
511    }
512
513    #[test]
514    fn intersection_empty() {
515        let mut a = RoaringBitmap::new();
516        a.insert(1);
517
518        let b = RoaringBitmap::new();
519        let c = a.intersection(&b);
520        assert!(c.is_empty());
521    }
522
523    #[test]
524    fn array_to_bitmap_promotion() {
525        let mut bm = RoaringBitmap::new();
526        // Insert 4096 values to trigger promotion.
527        for i in 0..ARRAY_TO_BITMAP_THRESHOLD as u32 {
528            bm.insert(i);
529        }
530
531        assert_eq!(bm.cardinality(), ARRAY_TO_BITMAP_THRESHOLD);
532
533        // Verify all values are still accessible.
534        for i in 0..ARRAY_TO_BITMAP_THRESHOLD as u32 {
535            assert!(bm.contains(i), "missing value {i} after promotion");
536        }
537
538        // Verify container was promoted to bitmap.
539        match &bm.containers[0].container {
540            Container::Bitmap(_) => {} // expected
541            Container::Array(_) => panic!("container should have been promoted to bitmap"),
542        }
543    }
544
545    #[test]
546    fn cell_index_dirty_tracking() {
547        // Simulate a 80x24 terminal with cell-level dirty tracking.
548        let width: u32 = 80;
549        let _height: u32 = 24;
550        let mut dirty = RoaringBitmap::new();
551
552        // Mark some cells dirty.
553        let cell = |x: u32, y: u32| -> u32 { y * width + x };
554
555        dirty.insert(cell(0, 0));
556        dirty.insert(cell(79, 0)); // last column, first row
557        dirty.insert(cell(40, 12)); // middle
558
559        assert_eq!(dirty.cardinality(), 3);
560        assert!(dirty.contains(cell(0, 0)));
561        assert!(dirty.contains(cell(79, 0)));
562        assert!(dirty.contains(cell(40, 12)));
563        assert!(!dirty.contains(cell(1, 0)));
564    }
565
566    #[test]
567    fn large_screen_dirty_tracking() {
568        // Simulate a 300x100 terminal.
569        let width: u32 = 300;
570        let height: u32 = 100;
571        let mut dirty = RoaringBitmap::new();
572
573        // Mark an entire row dirty.
574        for x in 0..width {
575            dirty.insert(10 * width + x);
576        }
577        assert_eq!(dirty.cardinality(), width as usize);
578
579        // Mark all cells dirty.
580        dirty.clear();
581        for y in 0..height {
582            for x in 0..width {
583                dirty.insert(y * width + x);
584            }
585        }
586        assert_eq!(dirty.cardinality(), (width * height) as usize);
587    }
588
589    #[test]
590    fn insert_range_basic() {
591        let mut bm = RoaringBitmap::new();
592        bm.insert_range(10, 20);
593        assert_eq!(bm.cardinality(), 10);
594        for i in 10..20 {
595            assert!(bm.contains(i));
596        }
597        assert!(!bm.contains(9));
598        assert!(!bm.contains(20));
599    }
600
601    #[test]
602    fn union_across_containers() {
603        let mut a = RoaringBitmap::new();
604        a.insert(100); // container 0
605
606        let mut b = RoaringBitmap::new();
607        b.insert(65636); // container 1
608
609        let c = a.union(&b);
610        assert_eq!(c.cardinality(), 2);
611        assert!(c.contains(100));
612        assert!(c.contains(65636));
613    }
614
615    #[test]
616    fn bitmap_iteration_correctness() {
617        let mut bm = BitmapContainer::new();
618        bm.insert(0);
619        bm.insert(63);
620        bm.insert(64);
621        bm.insert(65535);
622
623        let values: Vec<u16> = bm.iter().collect();
624        assert_eq!(values, vec![0, 63, 64, 65535]);
625    }
626
627    #[test]
628    fn default_is_empty() {
629        let bm = RoaringBitmap::default();
630        assert!(bm.is_empty());
631    }
632}