use alloc::vec::Vec;
use crate::geometry::Rect;
pub(crate) const MAX_RECTS: usize = 16;
#[derive(Clone, Debug, Default)]
pub(crate) struct DirtyRects {
rects: Vec<Rect>,
}
impl DirtyRects {
pub(crate) fn add(&mut self, rect: Rect, width: u32, height: u32) {
let Some(mut rect) = rect.clamp(width, height) else {
return;
};
if self
.rects
.iter()
.any(|existing| existing.contains_rect(&rect))
{
return;
}
loop {
let before = self.rects.len();
self.rects.retain(|existing| {
if rect.contains_rect(existing) {
return false;
}
let same_rows = existing.y == rect.y && existing.h == rect.h;
let same_cols = existing.x == rect.x && existing.w == rect.w;
let touch_x = existing.x <= rect.right() && rect.x <= existing.right();
let touch_y = existing.y <= rect.bottom() && rect.y <= existing.bottom();
if (same_rows && touch_x) || (same_cols && touch_y) {
rect = rect.union(existing);
return false;
}
true
});
if self.rects.len() == before {
break;
}
}
self.rects.push(rect);
if self.rects.len() > MAX_RECTS {
let bounds = self
.rects
.iter()
.skip(1)
.fold(self.rects[0], |bounds, rect| bounds.union(rect));
self.rects.clear();
self.rects.push(bounds);
}
}
pub(crate) fn is_empty(&self) -> bool {
self.rects.is_empty()
}
pub(crate) fn rects(&self) -> &[Rect] {
&self.rects
}
pub(crate) fn take(&mut self) -> Vec<Rect> {
core::mem::take(&mut self.rects)
}
pub(crate) fn clear(&mut self) {
self.rects.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn covered(rects: &[Rect], x: u32, y: u32) -> bool {
rects
.iter()
.any(|r| x >= r.x && x < r.right() && y >= r.y && y < r.bottom())
}
#[test]
fn dirty_rects_cover_every_write_merge_neighbours_and_stay_in_bounds() {
let mut dirty = DirtyRects::default();
for x in 2..5 {
dirty.add(Rect::new(x, 1, 1, 1), 10, 4);
}
assert_eq!(dirty.rects(), &[Rect::new(2, 1, 3, 1)]);
dirty.add(Rect::new(3, 1, 1, 1), 10, 4);
assert_eq!(dirty.rects().len(), 1);
dirty.add(Rect::new(0, 0, 10, 2), 10, 4);
assert_eq!(dirty.rects(), &[Rect::new(0, 0, 10, 2)]);
dirty.add(Rect::new(8, 3, 100, 100), 10, 4);
dirty.add(Rect::new(10, 0, 1, 1), 10, 4);
dirty.add(Rect::new(u32::MAX, u32::MAX, u32::MAX, u32::MAX), 10, 4);
assert!(dirty.rects().contains(&Rect::new(8, 3, 2, 1)));
assert!(
dirty
.rects()
.iter()
.all(|r| r.right() <= 10 && r.bottom() <= 4)
);
let mut dirty = DirtyRects::default();
let mut written = Vec::new();
for i in 0..40u32 {
let (x, y) = ((i * 7) % 50, (i * 13) % 30);
dirty.add(Rect::new(x, y, 1, 1), 50, 30);
written.push((x, y));
assert!(dirty.rects().len() <= MAX_RECTS);
}
let rects = dirty.take();
for (x, y) in written {
assert!(covered(&rects, x, y), "({x}, {y}) lost");
}
assert!(dirty.is_empty());
}
}