From ad22ff8c76629232c9590ed18f09901d2e2c54e3 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 30 Oct 2026 01:00:31 +0100 Subject: [PATCH] datastructures crate: intervalmap --- Cargo.toml | 2 +- crates/datastructures/Cargo.toml | 6 + crates/datastructures/src/lib.rs | 471 +++++++++++++++++++++++++++++++ 3 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 crates/datastructures/Cargo.toml create mode 100644 crates/datastructures/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index cf45237..ff43240 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ "crates/window", "crates/game", "crates/text", - "crates/bevy_vulkan_render", "crates/renderer_device", "crates/renderer_resources", "crates/renderer_rendergraph", + "crates/bevy_vulkan_render", "crates/renderer_device", "crates/renderer_resources", "crates/renderer_rendergraph", "crates/datastructures", ] [dependencies] diff --git a/crates/datastructures/Cargo.toml b/crates/datastructures/Cargo.toml new file mode 100644 index 0000000..83911bb --- /dev/null +++ b/crates/datastructures/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "datastructures" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/crates/datastructures/src/lib.rs b/crates/datastructures/src/lib.rs new file mode 100644 index 0000000..78eca49 --- /dev/null +++ b/crates/datastructures/src/lib.rs @@ -0,0 +1,471 @@ +//! A map that stores values over disjoint, half-open intervals. + +use std::collections::BTreeMap; + +/// Operations applied to the covered and uncovered parts of a range. +pub trait IntervalUpdateOps { + /// Updates the value of a subrange that was already present in the map. + fn update(&mut self, range: core::range::Range, value: &mut V); + + /// Creates a value for a subrange that was not previously present. + fn infill(&mut self, range: core::range::Range) -> V; +} + +impl IntervalUpdateOps for &mut T +where + T: IntervalUpdateOps + ?Sized, +{ + fn update(&mut self, range: core::range::Range, value: &mut V) { + (**self).update(range, value); + } + + fn infill(&mut self, range: core::range::Range) -> V { + (**self).infill(range) + } +} + +/// An interval and its associated value. +#[derive(Debug, Clone)] +struct Entry { + /// The covered interval; its start is always the BTreeMap key. + range: core::range::Range, + value: V, +} + +/// A map from disjoint, half-open ranges of ordered keys to values. +/// +/// Stored ranges are pairwise disjoint and sorted by their start key. +#[derive(Debug, Clone)] +pub struct DisjointIntervalMap { + map: BTreeMap>, +} + +impl DisjointIntervalMap { + /// Creates an empty interval map. + pub const fn new() -> Self { + Self { + map: BTreeMap::new(), + } + } + + /// Applies operations to every existing and missing subrange within `range`. + /// + /// On return, every point in the non-empty range is covered by exactly one + /// map entry. Existing values are updated, and missing intervals are + /// created through [`IntervalUpdateOps::infill`]. + pub fn split_apply( + &mut self, + range: core::range::Range, + mut ops: impl IntervalUpdateOps, + ) where + K: Ord + Copy, + V: Clone, + { + if range.is_empty() { + return; + } + + self.split(range.start); + self.split(range.end); + + let mut cursor = range.start; + let mut gaps = Vec::new(); + for entry in self + .map + .range_mut(range.start..range.end) + .map(|(_, entry)| entry) + { + if cursor < entry.range.start { + gaps.push(core::range::Range { + start: cursor, + end: entry.range.start, + }); + } + + let entry_range = entry.range; + ops.update(entry_range, &mut entry.value); + cursor = entry_range.end; + } + + if cursor < range.end { + gaps.push(core::range::Range { + start: cursor, + end: range.end, + }); + } + + for gap in gaps { + let value = ops.infill(gap); + self.map.insert(gap.start, Entry { range: gap, value }); + } + } + + /// Inserts `range` unless it is empty or overlaps an existing interval. + #[allow(clippy::result_unit_err)] + pub fn try_insert(&mut self, range: core::range::Range, value: V) -> Result<(), ()> + where + K: Ord + Copy, + { + if range.is_empty() { + return Err(()); + } + + if self + .map + .range(..range.end) + .next_back() + .is_some_and(|(_, entry)| entry.range.end > range.start) + { + return Err(()); + } + + self.map.insert(range.start, Entry { range, value }); + Ok(()) + } + + /// Removes all mapped portions covered by `range`. + pub fn remove(&mut self, range: core::range::Range) + where + K: Ord + Copy, + V: Clone, + { + if range.is_empty() { + return; + } + + self.split(range.start); + self.split(range.end); + let keys: Vec = self + .map + .range(range.start..range.end) + .map(|(key, _)| *key) + .collect(); + for key in keys { + self.map.remove(&key); + } + } + + /// Splits the interval containing `pos` into two intervals at `pos`. + /// + /// Does nothing if no interval contains `pos` or an interval already + /// starts there. + pub fn split(&mut self, pos: K) + where + K: Ord + Copy, + V: Clone, + { + let Some((key, interval)) = self + .map + .range_mut(..=pos) + .next_back() + .map(|(key, entry)| (*key, entry.range)) + else { + return; + }; + + if key == pos || pos >= interval.end { + return; + } + + let entry = self.map.remove(&key).expect("candidate entry must exist"); + let right_value = entry.value.clone(); + let left_range = core::range::Range { + start: key, + end: pos, + }; + let right_range = core::range::Range { + start: pos, + end: entry.range.end, + }; + self.map.insert( + key, + Entry { + range: left_range, + value: entry.value, + }, + ); + self.map.insert( + pos, + Entry { + range: right_range, + value: right_value, + }, + ); + } + + /// Returns the value of the interval containing `pos`, if any. + pub fn get(&self, pos: K) -> Option<&V> + where + K: Ord, + { + let (_, entry) = self + .map + .range((std::ops::Bound::Unbounded, std::ops::Bound::Included(&pos))) + .next_back()?; + (pos < entry.range.end).then_some(&entry.value) + } + + /// Returns a mutable reference to the value of the interval containing `pos`. + pub fn get_mut(&mut self, pos: K) -> Option<&mut V> + where + K: Ord, + { + let (_, entry) = self + .map + .range_mut((std::ops::Bound::Unbounded, std::ops::Bound::Included(&pos))) + .next_back()?; + (pos < entry.range.end).then_some(&mut entry.value) + } + + /// Iterates over intervals and their values in start-key order. + pub fn iter(&self) -> impl Iterator, &V)> { + self.map.values().map(|entry| (&entry.range, &entry.value)) + } + + /// Iterates over values in start-key order. + pub fn values(&self) -> impl Iterator { + self.map.values().map(|entry| &entry.value) + } + + /// Iterates over intervals in start-key order. + pub fn keys(&self) -> impl Iterator> { + self.map.values().map(|entry| &entry.range) + } + + /// Returns the number of stored intervals. + pub fn len(&self) -> usize { + self.map.len() + } + + /// Returns whether the map contains no intervals. + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + /// Removes all intervals from the map. + pub fn clear(&mut self) { + self.map.clear(); + } +} + +impl Default for DisjointIntervalMap { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::{DisjointIntervalMap, IntervalUpdateOps}; + + fn r(start: i32, end: i32) -> core::range::Range { + core::range::Range::from(start..end) + } + + #[derive(Default)] + struct CountingOps { + updates: Vec<(i32, i32)>, + infills: Vec<(i32, i32)>, + } + + impl IntervalUpdateOps for CountingOps { + fn update(&mut self, range: core::range::Range, value: &mut i32) { + *value += 1; + self.updates.push((range.start, range.end)); + } + + fn infill(&mut self, range: core::range::Range) -> i32 { + self.infills.push((range.start, range.end)); + 100 + range.start + } + } + + fn intervals(map: &DisjointIntervalMap) -> Vec<(i32, i32, i32)> { + map.iter() + .map(|(range, value)| (range.start, range.end, *value)) + .collect() + } + + #[test] + fn try_insert_overlap_adjacency_and_lookup() { + let mut map = DisjointIntervalMap::new(); + assert!(map.try_insert(r(5, 10), 1).is_ok()); + assert!(map.try_insert(r(0, 5), 2).is_ok()); + assert!(map.try_insert(r(10, 15), 3).is_ok()); + + for overlap in [r(4, 6), r(9, 12), r(6, 8), r(4, 11)] { + assert_eq!(map.try_insert(overlap, 9), Err(())); + } + assert_eq!(map.try_insert(r(2, 2), 9), Err(())); + + assert_eq!(map.get(0), Some(&2)); + assert_eq!(map.get(5), Some(&1)); + assert_eq!(map.get(14), Some(&3)); + assert_eq!(map.get(15), None); + assert_eq!(map.get(-1), None); + *map.get_mut(5).unwrap() = 7; + assert_eq!(map.get(5), Some(&7)); + assert!(map.get_mut(15).is_none()); + } + + #[test] + fn split_boundaries_and_interior() { + let mut map = DisjointIntervalMap::new(); + assert!(map.try_insert(r(0, 10), 4).is_ok()); + + map.split(5); + assert_eq!(intervals(&map), vec![(0, 5, 4), (5, 10, 4)]); + for pos in [0, 10, -1, 11] { + let before = intervals(&map); + map.split(pos); + assert_eq!(intervals(&map), before); + } + + let mut empty = DisjointIntervalMap::::new(); + empty.split(1); + assert!(empty.is_empty()); + } + + #[test] + fn remove_exact_partial_spanning_and_noop_ranges() { + let mut exact = DisjointIntervalMap::new(); + exact.try_insert(r(0, 5), 1).unwrap(); + exact.remove(r(0, 5)); + assert!(exact.is_empty()); + + let mut partial = DisjointIntervalMap::new(); + partial.try_insert(r(0, 5), 1).unwrap(); + partial.try_insert(r(5, 10), 2).unwrap(); + partial.remove(r(3, 7)); + assert_eq!(intervals(&partial), vec![(0, 3, 1), (7, 10, 2)]); + + partial.remove(r(8, 12)); + assert_eq!(intervals(&partial), vec![(0, 3, 1), (7, 8, 2)]); + + let mut spanning = DisjointIntervalMap::new(); + for start in [0, 5, 10] { + spanning.try_insert(r(start, start + 5), start).unwrap(); + } + spanning.remove(r(2, 13)); + assert_eq!(intervals(&spanning), vec![(0, 2, 0), (13, 15, 10)]); + + let before = intervals(&spanning); + spanning.remove(r(20, 25)); + spanning.remove(r(4, 4)); + assert_eq!(intervals(&spanning), before); + } + + #[test] + fn split_apply_on_empty_and_fully_covered_maps() { + let mut empty = DisjointIntervalMap::new(); + let mut ops = CountingOps::default(); + empty.split_apply(r(2, 6), &mut ops); + assert_eq!(intervals(&empty), vec![(2, 6, 102)]); + assert!(ops.updates.is_empty()); + assert_eq!(ops.infills, vec![(2, 6)]); + + let mut covered = DisjointIntervalMap::new(); + covered.try_insert(r(0, 10), 5).unwrap(); + let mut ops = CountingOps::default(); + covered.split_apply(r(0, 10), &mut ops); + assert_eq!(intervals(&covered), vec![(0, 10, 6)]); + assert_eq!(ops.updates, vec![(0, 10)]); + assert!(ops.infills.is_empty()); + } + + #[test] + fn split_apply_mixed_coverage_splits_and_fills_gaps() { + let mut map = DisjointIntervalMap::new(); + map.try_insert(r(0, 4), 1).unwrap(); + map.try_insert(r(6, 10), 2).unwrap(); + let mut ops = CountingOps::default(); + map.split_apply(r(2, 8), &mut ops); + + assert_eq!( + intervals(&map), + vec![(0, 2, 1), (2, 4, 2), (4, 6, 104), (6, 8, 3), (8, 10, 2)] + ); + assert_eq!(ops.updates, vec![(2, 4), (6, 8)]); + assert_eq!(ops.infills, vec![(4, 6)]); + } + + #[test] + fn split_apply_inside_one_entry_and_empty_range() { + let mut map = DisjointIntervalMap::new(); + map.try_insert(r(0, 10), 7).unwrap(); + let mut ops = CountingOps::default(); + map.split_apply(r(3, 5), &mut ops); + assert_eq!(intervals(&map), vec![(0, 3, 7), (3, 5, 8), (5, 10, 7)]); + assert_eq!(ops.updates, vec![(3, 5)]); + assert!(ops.infills.is_empty()); + + let before = intervals(&map); + let prior_ops = ops.updates.clone(); + let prior_infills = ops.infills.clone(); + map.split_apply(r(4, 4), &mut ops); + assert_eq!(intervals(&map), before); + assert_eq!(ops.updates, prior_ops); + assert_eq!(ops.infills, prior_infills); + } + + #[test] + fn split_apply_mutable_ops_accumulate_state() { + let mut map = DisjointIntervalMap::new(); + let mut ops = CountingOps::default(); + map.split_apply(r(0, 2), &mut ops); + map.split_apply(r(4, 6), &mut ops); + assert_eq!(ops.infills, vec![(0, 2), (4, 6)]); + assert_eq!(map.len(), 2); + } + + #[test] + fn iteration_and_collection_basics() { + let mut map = DisjointIntervalMap::new(); + assert!(map.is_empty()); + map.try_insert(r(5, 8), 2).unwrap(); + map.try_insert(r(0, 3), 1).unwrap(); + + assert_eq!(map.len(), 2); + assert!(!map.is_empty()); + assert_eq!( + map.iter() + .map(|(range, value)| ((range.start, range.end), *value)) + .collect::>(), + vec![((0, 3), 1), ((5, 8), 2)] + ); + assert_eq!( + map.keys() + .map(|range| (range.start, range.end)) + .collect::>(), + vec![(0, 3), (5, 8)] + ); + assert_eq!(map.values().copied().collect::>(), vec![1, 2]); + + map.clear(); + assert!(map.is_empty()); + assert_eq!(map.len(), 0); + } + + #[test] + fn operations_preserve_disjoint_nonempty_sorted_invariant() { + let mut map = DisjointIntervalMap::new(); + map.try_insert(r(0, 4), 1).unwrap(); + map.try_insert(r(6, 10), 2).unwrap(); + map.split(2); + map.remove(r(3, 7)); + let mut ops = CountingOps::default(); + map.split_apply(r(-2, 12), &mut ops); + map.try_insert(r(20, 24), 3).unwrap(); + map.split(22); + map.remove(r(1, 2)); + + let ranges = map.keys().collect::>(); + for range in &ranges { + assert!(!range.is_empty()); + } + for pair in ranges.windows(2) { + assert!(pair[0].start < pair[1].start); + assert!(pair[0].end <= pair[1].start); + } + } +}