#[derive(Debug)]
pub(super) struct DirtyRegions {
dirty: u64,
total_cells: usize,
}
impl DirtyRegions {
const CHUNK_SHIFT: u32 = 10; const CHUNK_SIZE: usize = 1 << Self::CHUNK_SHIFT;
pub(super) fn new(total_cells: usize) -> Self {
debug_assert!(total_cells > 0, "requires a non-zero sized terminal");
Self { dirty: 0, total_cells }
}
pub(super) fn is_clean(&self) -> bool {
self.dirty == 0
}
pub(super) fn mark(&mut self, cell_index: usize) {
self.dirty |= 1u64 << ((cell_index >> Self::CHUNK_SHIFT) & 0b0011_1111);
}
pub(super) fn mark_all(&mut self) {
self.dirty = u64::MAX;
}
fn active_mask(&self) -> u64 {
let active_chunks = self.total_cells.div_ceil(Self::CHUNK_SIZE);
if active_chunks >= 64 { u64::MAX } else { (1u64 << active_chunks) - 1 }
}
pub(super) fn is_all_active_dirty(&self) -> bool {
let mask = self.active_mask();
self.dirty & mask == mask
}
pub(super) fn clear(&mut self) {
self.dirty = 0;
}
pub(super) fn drain(&mut self) -> DirtyChunkIter {
let dirty = self.dirty & self.active_mask();
self.dirty = 0;
DirtyChunkIter { dirty, total_cells: self.total_cells }
}
}
pub(super) struct DirtyChunkIter {
dirty: u64,
total_cells: usize,
}
impl Iterator for DirtyChunkIter {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
if self.dirty == 0 {
return None;
}
let start_chunk = self.dirty.trailing_zeros() as usize;
let run_len = (!(self.dirty >> start_chunk)).trailing_zeros() as usize;
let start = start_chunk * DirtyRegions::CHUNK_SIZE;
let end = ((start_chunk + run_len) * DirtyRegions::CHUNK_SIZE).min(self.total_cells);
self.dirty &= !(((1u64 << run_len) - 1) << start_chunk);
Some((start, end))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clean_on_creation() {
let dr = DirtyRegions::new(10_000);
assert!(dr.is_clean());
}
#[test]
fn mark_single_cell() {
let mut dr = DirtyRegions::new(10_000);
dr.mark(42);
assert!(!dr.is_clean());
let ranges: Vec<_> = dr.drain().collect();
assert_eq!(ranges, vec![(0, 1024)]);
}
#[test]
fn mark_adjacent_chunks_merge() {
let mut dr = DirtyRegions::new(10_000);
dr.mark(500); dr.mark(1500); dr.mark(2500); let ranges: Vec<_> = dr.drain().collect();
assert_eq!(ranges, vec![(0, 3072)]);
}
#[test]
fn mark_separated_chunks() {
let mut dr = DirtyRegions::new(10_000);
dr.mark(0); dr.mark(5000); let ranges: Vec<_> = dr.drain().collect();
assert_eq!(ranges, vec![(0, 1024), (4096, 5120)]);
}
#[test]
fn all_dirty_detection() {
let mut dr = DirtyRegions::new(10_000); dr.mark_all();
assert!(dr.is_all_active_dirty());
}
#[test]
fn drain_clamps_to_total_cells() {
let mut dr = DirtyRegions::new(1500); dr.mark(1400); let ranges: Vec<_> = dr.drain().collect();
assert_eq!(ranges, vec![(1024, 1500)]); }
#[test]
fn drain_resets_dirty_state() {
let mut dr = DirtyRegions::new(10_000);
dr.mark(42);
let _ = dr.drain().count();
assert!(dr.is_clean());
}
#[test]
fn all_dirty_single_range() {
let mut dr = DirtyRegions::new(2048); dr.mark_all();
let ranges: Vec<_> = dr.drain().collect();
assert_eq!(ranges, vec![(0, 2048)]);
}
}