use core::iter::FusedIterator;
use super::{DecodeUnitRef, Region, RegionError, TileGrid, UnitGroup};
use crate::media::UnitSelection;
impl<'a> UnitGroup<'a> {
pub fn units_in(self, region: Region) -> Result<RegionUnits<'a>, RegionError> {
Ok(RegionUnits {
group: self,
cells: RegionCells::new(self.grid(), self.selection(), region)?,
})
}
}
#[derive(Clone, Debug)]
pub struct RegionUnits<'a> {
group: UnitGroup<'a>,
cells: RegionCells<'a>,
}
impl RegionUnits<'_> {
pub const fn work_bound(&self) -> u64 {
self.cells.work_bound
}
}
impl<'a> Iterator for RegionUnits<'a> {
type Item = DecodeUnitRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.cells
.next()
.map(|cell| self.group.cell(cell).expect("selected grid cell"))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.cells.size_hint()
}
}
impl FusedIterator for RegionUnits<'_> {}
#[derive(Clone, Debug)]
struct RegionCells<'a> {
selection: UnitSelection<'a>,
columns: u32,
first_column: u32,
end_column: u32,
next_cell: u32,
end_cell: u32,
upper: usize,
work_bound: u64,
#[cfg(test)]
probes: u64,
}
impl<'a> RegionCells<'a> {
fn new(
grid: TileGrid,
selection: UnitSelection<'a>,
region: Region,
) -> Result<Self, RegionError> {
if region.right() > grid.width() || region.bottom() > grid.height() {
return Err(RegionError::OutOfBounds);
}
let mut cells = Self {
selection,
columns: grid.columns(),
first_column: 0,
end_column: 0,
next_cell: 0,
end_cell: 0,
upper: 0,
work_bound: 0,
#[cfg(test)]
probes: 0,
};
if region.is_empty() || selection.is_empty() {
return Ok(cells);
}
cells.first_column = region.x() / grid.tile_width();
cells.end_column = region.right().div_ceil(grid.tile_width());
let first_row = region.y() / grid.tile_height();
let end_row = region.bottom().div_ceil(grid.tile_height());
cells.next_cell = first_row * cells.columns + cells.first_column;
cells.end_cell = (end_row - 1) * cells.columns + cells.end_column;
let enclosing = selection
.range(cells.next_cell..cells.end_cell)
.unwrap()
.len() as u64;
let rows = u64::from(end_row - first_row);
let columns = u64::from(cells.end_column - cells.first_column);
cells.upper = enclosing.min(rows * columns) as usize;
if cells.upper == 0 {
cells.next_cell = cells.end_cell;
} else {
cells.work_bound = 1 + cells.upper as u64 + 2 * rows.min(enclosing);
}
Ok(cells)
}
}
impl Iterator for RegionCells<'_> {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
while self.next_cell < self.end_cell {
#[cfg(test)]
{
self.probes += 1;
}
let Some(cell) = self
.selection
.range(self.next_cell..self.end_cell)
.unwrap()
.next()
else {
self.next_cell = self.end_cell;
break;
};
let row = cell / self.columns;
let column = cell % self.columns;
if column < self.first_column {
self.next_cell = row * self.columns + self.first_column;
} else if column >= self.end_column {
let next_row_start =
(u64::from(row) + 1) * u64::from(self.columns) + u64::from(self.first_column);
self.next_cell = next_row_start.min(u64::from(self.end_cell)) as u32;
} else {
self.next_cell = cell + 1;
self.upper -= 1;
return Some(cell);
}
}
self.upper = 0;
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(self.upper))
}
}
impl FusedIterator for RegionCells<'_> {}
#[cfg(test)]
mod tests;