use std::collections::VecDeque;
use std::sync::atomic::{AtomicU64, Ordering};
use crabshell_ui::Rect;
const HISTORY: usize = 4;
const MARGIN: u32 = 1;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct PixelRect {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl PixelRect {
pub const fn new(x: u32, y: u32, width: u32, height: u32) -> PixelRect {
PixelRect {
x,
y,
width,
height,
}
}
pub fn right(&self) -> u32 {
self.x + self.width
}
pub fn bottom(&self) -> u32 {
self.y + self.height
}
pub fn area(&self) -> u64 {
u64::from(self.width) * u64::from(self.height)
}
pub fn overlaps(&self, other: &PixelRect) -> bool {
self.x < other.right()
&& other.x < self.right()
&& self.y < other.bottom()
&& other.y < self.bottom()
}
fn union(&self, other: &PixelRect) -> PixelRect {
let x = self.x.min(other.x);
let y = self.y.min(other.y);
PixelRect::new(
x,
y,
self.right().max(other.right()) - x,
self.bottom().max(other.bottom()) - y,
)
}
}
pub(crate) fn to_pixels(rect: Rect, scale: f32, width: u32, height: u32) -> Option<PixelRect> {
if !(rect.width > 0.0 && rect.height > 0.0) {
return None;
}
let left = ((rect.x * scale).floor() as u32).saturating_sub(MARGIN);
let top = ((rect.y * scale).floor() as u32).saturating_sub(MARGIN);
let right = ((rect.right() * scale).ceil() as u32)
.saturating_add(MARGIN)
.min(width);
let bottom = ((rect.bottom() * scale).ceil() as u32)
.saturating_add(MARGIN)
.min(height);
(left < right && top < bottom).then(|| PixelRect::new(left, top, right - left, bottom - top))
}
pub(crate) fn merge(mut rects: Vec<PixelRect>) -> Vec<PixelRect> {
rects.retain(|rect| rect.area() > 0);
let mut merged = true;
while merged {
merged = false;
'outer: for i in 0..rects.len() {
for j in i + 1..rects.len() {
if rects[i].overlaps(&rects[j]) {
let other = rects.swap_remove(j);
rects[i] = rects[i].union(&other);
merged = true;
break 'outer;
}
}
}
}
rects
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BufferAge {
drawn: Option<(u64, u64)>,
}
static HISTORIES: AtomicU64 = AtomicU64::new(0);
#[derive(Debug)]
pub struct DamageHistory {
id: u64,
next: u64,
shape: Option<(u32, u32, u32)>,
recent: VecDeque<(u64, Vec<PixelRect>)>,
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Plan {
pub(crate) repaint: Vec<PixelRect>,
pub(crate) report: Vec<PixelRect>,
}
impl Default for DamageHistory {
fn default() -> Self {
Self::new()
}
}
impl DamageHistory {
pub fn new() -> DamageHistory {
DamageHistory {
id: HISTORIES.fetch_add(1, Ordering::Relaxed),
next: 0,
shape: None,
recent: VecDeque::new(),
}
}
pub(crate) fn plan(
&mut self,
damage: &[Rect],
scale: f32,
width: u32,
height: u32,
age: &mut BufferAge,
) -> Plan {
let full = vec![PixelRect::new(0, 0, width, height)];
let frame = self.next;
self.next += 1;
let shape = (width, height, scale.to_bits());
let mut changed = if self.shape == Some(shape) {
merge(
damage
.iter()
.filter_map(|rect| to_pixels(*rect, scale, width, height))
.collect(),
)
} else {
self.shape = Some(shape);
self.recent.clear();
full.clone()
};
if more_than_half(&changed, width, height) {
changed = full.clone();
}
let missed = match age.drawn {
Some((history, drawn))
if history == self.id
&& self
.recent
.front()
.is_none_or(|(first, _)| *first <= drawn + 1) =>
{
Some(
self
.recent
.iter()
.filter(|(number, _)| *number > drawn)
.flat_map(|(_, rects)| rects.iter().copied())
.collect::<Vec<_>>(),
)
}
_ => None,
};
let repaint = match missed {
Some(missed) => {
let repaint = merge(changed.iter().copied().chain(missed).collect());
if more_than_half(&repaint, width, height) {
full
} else {
repaint
}
}
None => full,
};
self.recent.push_back((frame, changed.clone()));
while self.recent.len() > HISTORY {
self.recent.pop_front();
}
age.drawn = Some((self.id, frame));
Plan {
repaint,
report: changed,
}
}
}
fn more_than_half(rects: &[PixelRect], width: u32, height: u32) -> bool {
let covered: u64 = rects.iter().map(PixelRect::area).sum();
covered * 2 > u64::from(width) * u64::from(height)
}