Skip to main content

hya_core/
intervals.rs

1//! Byte-range set with a maintained coverage invariant.
2//!
3//! Ranges are half-open `[lo, hi)` over byte positions. The set is kept sorted
4//! and coalesced at all times, which makes `total()` exact and makes the
5//! coverage audit in `Scheduler` a cheap sum rather than a merge.
6
7use core::cmp::Ordering;
8
9/// A half-open byte range.
10#[derive(Clone, Copy, PartialEq, Eq, Debug)]
11pub struct Range {
12    pub lo: u64,
13    pub hi: u64,
14}
15
16impl Range {
17    #[inline]
18    pub fn new(lo: u64, hi: u64) -> Self {
19        debug_assert!(hi >= lo, "range {lo}..{hi} inverted");
20        Range { lo, hi }
21    }
22
23    #[inline]
24    pub fn len(&self) -> u64 {
25        self.hi.saturating_sub(self.lo)
26    }
27
28    #[inline]
29    pub fn is_empty(&self) -> bool {
30        self.hi <= self.lo
31    }
32}
33
34/// A sorted, coalesced set of disjoint byte ranges.
35#[derive(Clone, Default, Debug)]
36pub struct IntervalSet {
37    iv: Vec<Range>,
38}
39
40impl IntervalSet {
41    pub fn new() -> Self {
42        IntervalSet { iv: Vec::new() }
43    }
44
45    /// The full range `[0, size)`.
46    pub fn full(size: u64) -> Self {
47        if size == 0 {
48            return Self::new();
49        }
50        IntervalSet {
51            iv: vec![Range::new(0, size)],
52        }
53    }
54
55    pub fn is_empty(&self) -> bool {
56        self.iv.is_empty()
57    }
58
59    pub fn len(&self) -> usize {
60        self.iv.len()
61    }
62
63    pub fn ranges(&self) -> &[Range] {
64        &self.iv
65    }
66
67    /// Total bytes covered.
68    pub fn total(&self) -> u64 {
69        self.iv.iter().map(|r| r.len()).sum()
70    }
71
72    /// Remove and return up to `n` bytes from the lowest range.
73    ///
74    /// Front-to-back allocation keeps the set small (usually one range) and
75    /// makes sequential-write patterns friendly to the OS page cache.
76    pub fn take_front(&mut self, n: u64) -> Option<Range> {
77        if n == 0 {
78            return None;
79        }
80        let first = *self.iv.first()?;
81        if first.len() <= n {
82            self.iv.remove(0);
83            Some(first)
84        } else {
85            let out = Range::new(first.lo, first.lo + n);
86            self.iv[0] = Range::new(first.lo + n, first.hi);
87            Some(out)
88        }
89    }
90
91    /// Insert a range, coalescing with neighbours. Overlapping inserts are
92    /// merged rather than duplicated, so re-inserting a reclaimed range that
93    /// partially overlaps an existing gap is safe.
94    pub fn insert(&mut self, r: Range) {
95        if r.is_empty() {
96            return;
97        }
98        let pos = self
99            .iv
100            .binary_search_by(|p| {
101                if p.hi < r.lo {
102                    Ordering::Less
103                } else if p.lo > r.hi {
104                    Ordering::Greater
105                } else {
106                    Ordering::Equal
107                }
108            })
109            .unwrap_or_else(|e| e);
110
111        // Merge with any ranges touching or overlapping [r.lo, r.hi].
112        let mut lo = r.lo;
113        let mut hi = r.hi;
114        let mut end = pos;
115        while end < self.iv.len() && self.iv[end].lo <= hi {
116            lo = lo.min(self.iv[end].lo);
117            hi = hi.max(self.iv[end].hi);
118            end += 1;
119        }
120        let mut start = pos;
121        while start > 0 && self.iv[start - 1].hi >= lo {
122            start -= 1;
123            lo = lo.min(self.iv[start].lo);
124            hi = hi.max(self.iv[start].hi);
125        }
126        self.iv.splice(start..end, [Range::new(lo, hi)]);
127    }
128
129    /// Remove `[lo, hi)` from the set, splitting ranges as needed.
130    pub fn remove(&mut self, lo: u64, hi: u64) {
131        if hi <= lo {
132            return;
133        }
134        let mut out: Vec<Range> = Vec::with_capacity(self.iv.len() + 1);
135        for r in self.iv.iter().copied() {
136            if r.hi <= lo || r.lo >= hi {
137                out.push(r);
138                continue;
139            }
140            if r.lo < lo {
141                out.push(Range::new(r.lo, lo));
142            }
143            if r.hi > hi {
144                out.push(Range::new(hi, r.hi));
145            }
146        }
147        self.iv = out;
148    }
149
150    /// True when every range is non-empty, sorted, and strictly disjoint from
151    /// its neighbours (i.e. the coalescing invariant holds).
152    pub fn invariant_holds(&self) -> bool {
153        for w in self.iv.windows(2) {
154            if w[0].hi >= w[1].lo || w[0].is_empty() {
155                return false;
156            }
157        }
158        self.iv.last().map(|r| !r.is_empty()).unwrap_or(true)
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn take_front_splits_and_preserves_total() {
168        let mut s = IntervalSet::full(1000);
169        let a = s.take_front(300).unwrap();
170        assert_eq!((a.lo, a.hi), (0, 300));
171        assert_eq!(s.total(), 700);
172        let b = s.take_front(900).unwrap();
173        assert_eq!((b.lo, b.hi), (300, 1000));
174        assert!(s.is_empty());
175        assert!(s.take_front(10).is_none());
176    }
177
178    #[test]
179    fn insert_coalesces_adjacent_and_overlapping() {
180        let mut s = IntervalSet::new();
181        s.insert(Range::new(10, 20));
182        s.insert(Range::new(20, 30)); // adjacent
183        assert_eq!(s.len(), 1);
184        assert_eq!(s.total(), 20);
185        s.insert(Range::new(25, 40)); // overlapping
186        assert_eq!(s.len(), 1);
187        assert_eq!(s.ranges()[0], Range::new(10, 40));
188        s.insert(Range::new(100, 110)); // disjoint
189        assert_eq!(s.len(), 2);
190        assert!(s.invariant_holds());
191    }
192
193    #[test]
194    fn insert_bridges_two_ranges() {
195        let mut s = IntervalSet::new();
196        s.insert(Range::new(0, 10));
197        s.insert(Range::new(20, 30));
198        s.insert(Range::new(10, 20));
199        assert_eq!(s.len(), 1);
200        assert_eq!(s.ranges()[0], Range::new(0, 30));
201    }
202
203    #[test]
204    fn remove_splits() {
205        let mut s = IntervalSet::full(100);
206        s.remove(30, 40);
207        assert_eq!(s.len(), 2);
208        assert_eq!(s.total(), 90);
209        assert!(s.invariant_holds());
210    }
211
212    #[test]
213    fn empty_and_degenerate_inserts_are_noops() {
214        let mut s = IntervalSet::new();
215        s.insert(Range::new(5, 5));
216        assert!(s.is_empty());
217        assert_eq!(IntervalSet::full(0).total(), 0);
218    }
219}