use std::time::Instant;
use super::PixelRect;
pub(crate) const RING: usize = 16;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct DamageRects {
rects: [PixelRect; RING],
len: u8,
}
impl DamageRects {
pub(crate) const fn new() -> Self {
Self {
rects: [PixelRect::new(0, 0, 0, 0); RING],
len: 0,
}
}
pub fn as_slice(&self) -> &[PixelRect] {
&self.rects[..self.len as usize]
}
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
fn push_unique(&mut self, rect: PixelRect) -> bool {
if self.as_slice().contains(&rect) {
return true;
}
if self.len() == RING {
return false;
}
self.rects[self.len()] = rect;
self.len += 1;
true
}
fn bounding_box(&self) -> PixelRect {
self.as_slice()
.iter()
.fold(PixelRect::default(), |acc, r| acc.union(r))
}
fn union_area(&self) -> u64 {
let rects = self.as_slice();
let mut xs = [0u64; 2 * RING];
let mut ys = [0u64; 2 * RING];
for (i, r) in rects.iter().enumerate() {
xs[2 * i] = r.x as u64;
xs[2 * i + 1] = r.x as u64 + r.width as u64;
ys[2 * i] = r.y as u64;
ys[2 * i + 1] = r.y as u64 + r.height as u64;
}
let xs = sorted_unique(&mut xs[..2 * rects.len()]);
let ys = sorted_unique(&mut ys[..2 * rects.len()]);
let mut area = 0;
for xw in xs.windows(2) {
for yw in ys.windows(2) {
let covered = rects.iter().any(|r| {
(r.x as u64) <= xw[0]
&& xw[1] <= r.x as u64 + r.width as u64
&& (r.y as u64) <= yw[0]
&& yw[1] <= r.y as u64 + r.height as u64
});
if covered {
area += (xw[1] - xw[0]) * (yw[1] - yw[0]);
}
}
}
area
}
}
fn sorted_unique(values: &mut [u64]) -> &[u64] {
values.sort_unstable();
let mut len = 0;
for i in 0..values.len() {
if len == 0 || values[i] != values[len - 1] {
values[len] = values[i];
len += 1;
}
}
&values[..len]
}
impl std::fmt::Debug for DamageRects {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.as_slice()).finish()
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Damage {
Rects(DamageRects),
Full,
}
impl Damage {
pub(crate) const NONE: Damage = Damage::Rects(DamageRects::new());
pub(crate) fn is_none(&self) -> bool {
matches!(self, Damage::Rects(r) if r.is_empty())
}
pub(crate) fn add(&mut self, rect: PixelRect) {
let Damage::Rects(rects) = self else {
return;
};
if !rects.push_unique(rect) {
let bbox = rects.bounding_box().union(&rect);
*rects = DamageRects::new();
rects.push_unique(bbox);
}
}
fn settled(self, width: u32, height: u32) -> Damage {
match self {
Damage::Rects(rects) if covers_half(&rects, width, height) => Damage::Full,
other => other,
}
}
}
fn covers_half(rects: &DamageRects, width: u32, height: u32) -> bool {
let frame = width as u64 * height as u64;
frame > 0 && rects.union_area() * 2 >= frame
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Pending {
pub(crate) damage: Damage,
pub(crate) resized: bool,
pub(crate) wrote: bool,
pub(crate) force_full: bool,
}
impl Pending {
pub(crate) const fn new() -> Self {
Self {
damage: Damage::NONE,
resized: false,
wrote: false,
force_full: false,
}
}
pub(crate) fn publishes(&self) -> bool {
self.resized || !self.damage.is_none()
}
}
#[derive(Debug, Clone, Copy)]
struct Entry {
generation: u64,
damage: Damage,
at: Option<Instant>,
}
pub(crate) struct DamageRing {
entries: [Entry; RING],
next: usize,
}
impl DamageRing {
pub(crate) fn new() -> Self {
Self {
entries: [Entry {
generation: 0,
damage: Damage::Full,
at: None,
}; RING],
next: 0,
}
}
pub(crate) fn record(
&mut self,
generation: u64,
damage: Damage,
width: u32,
height: u32,
at: Instant,
) {
self.entries[self.next] = Entry {
generation,
damage: damage.settled(width, height),
at: Some(at),
};
self.next = (self.next + 1) % RING;
}
fn entry(&self, generation: u64) -> Option<&Entry> {
self.entries
.iter()
.find(|e| e.generation == generation && e.at.is_some())
}
pub(crate) fn commit_instant(&self, generation: u64) -> Option<Instant> {
self.entry(generation).and_then(|e| e.at)
}
pub(crate) fn plan(
&self,
cursor: Option<u64>,
latest: u64,
width: u32,
height: u32,
) -> UploadPlan {
let Some(cursor) = cursor else {
return UploadPlan::Full;
};
if cursor == latest {
return UploadPlan::Nothing;
}
if cursor > latest || latest - cursor > RING as u64 {
return UploadPlan::Full;
}
let mut merged = DamageRects::new();
for generation in cursor + 1..=latest {
match self.entry(generation).map(|e| e.damage) {
Some(Damage::Rects(rects)) => {
for &rect in rects.as_slice() {
if !merged.push_unique(rect) {
return UploadPlan::Full;
}
}
}
Some(Damage::Full) | None => return UploadPlan::Full,
}
}
if merged.is_empty() {
return UploadPlan::Full;
}
if covers_half(&merged, width, height) {
return UploadPlan::Full;
}
UploadPlan::Rects(merged)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UploadPlan {
Nothing,
Full,
Rects(DamageRects),
}