use std::sync::Arc;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::node::PanelId;
use crate::overlay::{AnchorFailure, OverlayId};
use crate::rect::Rect;
use crate::resolver::ResolvedLayout;
const EPSILON: f32 = 1e-4;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RectChange<Id> {
pub id: Id,
pub from: Rect,
pub to: Rect,
}
pub type PanelRectChange = RectChange<PanelId>;
pub type OverlayRectChange = RectChange<OverlayId>;
#[derive(Debug)]
pub struct DiffResult<'a, Id> {
pub added: &'a [Id],
pub removed: &'a [Id],
pub moved: &'a [RectChange<Id>],
pub resized: &'a [RectChange<Id>],
pub unchanged: &'a [Id],
}
pub type LayoutDiff<'a> = DiffResult<'a, PanelId>;
#[derive(Debug)]
pub struct OverlayDiff<'a> {
pub added: &'a [OverlayId],
pub removed: &'a [OverlayId],
pub moved: &'a [RectChange<OverlayId>],
pub resized: &'a [RectChange<OverlayId>],
pub unchanged: &'a [OverlayId],
pub anchor_failed: &'a [OverlayId],
}
fn position_changed(a: &Rect, b: &Rect) -> bool {
(a.x - b.x).abs() > EPSILON || (a.y - b.y).abs() > EPSILON
}
fn size_changed(a: &Rect, b: &Rect) -> bool {
(a.w - b.w).abs() > EPSILON || (a.h - b.h).abs() > EPSILON
}
fn classify<Id: Copy>(
id: Id,
old_rect: &Rect,
new_rect: &Rect,
moved: &mut Vec<RectChange<Id>>,
resized: &mut Vec<RectChange<Id>>,
unchanged: &mut Vec<Id>,
) {
let pos = position_changed(old_rect, new_rect);
let size = size_changed(old_rect, new_rect);
match (pos, size) {
(false, false) => unchanged.push(id),
(true, true) => {
let change = RectChange {
id,
from: *old_rect,
to: *new_rect,
};
moved.push(change);
resized.push(change);
}
(true, false) => moved.push(RectChange {
id,
from: *old_rect,
to: *new_rect,
}),
(false, true) => resized.push(RectChange {
id,
from: *old_rect,
to: *new_rect,
}),
}
}
pub struct DiffScratch<Id> {
pub(crate) added: Vec<Id>,
pub(crate) removed: Vec<Id>,
pub(crate) moved: Vec<RectChange<Id>>,
pub(crate) resized: Vec<RectChange<Id>>,
pub(crate) unchanged: Vec<Id>,
}
impl<Id> Default for DiffScratch<Id> {
fn default() -> Self {
Self {
added: Vec::new(),
removed: Vec::new(),
moved: Vec::new(),
resized: Vec::new(),
unchanged: Vec::new(),
}
}
}
impl<Id> DiffScratch<Id> {
fn clear(&mut self) {
self.added.clear();
self.removed.clear();
self.moved.clear();
self.resized.clear();
self.unchanged.clear();
}
pub fn as_diff(&self) -> DiffResult<'_, Id> {
DiffResult {
added: &self.added,
removed: &self.removed,
moved: &self.moved,
resized: &self.resized,
unchanged: &self.unchanged,
}
}
}
pub type PanelDiffScratch = DiffScratch<PanelId>;
#[derive(Default)]
pub struct OverlayDiffScratch {
pub(crate) inner: DiffScratch<OverlayId>,
pub(crate) anchor_failed: Vec<OverlayId>,
curr_rect_indices: FxHashMap<OverlayId, usize>,
curr_failed_ids: FxHashSet<OverlayId>,
prev_rect_ids: FxHashSet<OverlayId>,
}
impl OverlayDiffScratch {
fn clear(&mut self) {
self.inner.clear();
self.anchor_failed.clear();
self.curr_rect_indices.clear();
self.curr_failed_ids.clear();
self.prev_rect_ids.clear();
}
pub fn as_diff(&self) -> OverlayDiff<'_> {
OverlayDiff {
added: &self.inner.added,
removed: &self.inner.removed,
moved: &self.inner.moved,
resized: &self.inner.resized,
unchanged: &self.inner.unchanged,
anchor_failed: &self.anchor_failed,
}
}
}
#[derive(Default)]
pub struct PanelScratch {
pub(crate) old_ids: FxHashSet<PanelId>,
pub(crate) new_ids: FxHashSet<PanelId>,
pub(crate) inner: PanelDiffScratch,
}
impl PanelScratch {
pub fn as_diff(&self) -> LayoutDiff<'_> {
self.inner.as_diff()
}
}
pub fn diff(old: &ResolvedLayout, new: &ResolvedLayout) -> PanelScratch {
let mut scratch = PanelScratch::default();
diff_reuse(old, new, &mut scratch);
scratch
}
pub(crate) fn diff_reuse<'a>(
old: &ResolvedLayout,
new: &ResolvedLayout,
scratch: &'a mut PanelScratch,
) -> LayoutDiff<'a> {
scratch.old_ids.clear();
scratch.old_ids.extend(old.panel_ids());
scratch.new_ids.clear();
scratch.new_ids.extend(new.panel_ids());
scratch.inner.clear();
scratch
.inner
.removed
.extend(scratch.old_ids.difference(&scratch.new_ids).copied());
scratch
.inner
.added
.extend(scratch.new_ids.difference(&scratch.old_ids).copied());
for &pid in scratch.old_ids.intersection(&scratch.new_ids) {
let (Some(old_rect), Some(new_rect)) = (old.get(pid), new.get(pid)) else {
continue;
};
classify(
pid,
old_rect,
new_rect,
&mut scratch.inner.moved,
&mut scratch.inner.resized,
&mut scratch.inner.unchanged,
);
}
scratch.inner.as_diff()
}
pub(crate) fn diff_same_panels_reuse<'a>(
old: &ResolvedLayout,
new: &ResolvedLayout,
scratch: &'a mut PanelScratch,
) -> LayoutDiff<'a> {
scratch.inner.clear();
for (pid, new_rect) in new.iter() {
let Some(old_rect) = old.get(pid) else {
continue;
};
classify(
pid,
old_rect,
new_rect,
&mut scratch.inner.moved,
&mut scratch.inner.resized,
&mut scratch.inner.unchanged,
);
}
scratch.inner.as_diff()
}
pub(crate) fn first_frame<'a>(
layout: &ResolvedLayout,
scratch: &'a mut PanelScratch,
) -> LayoutDiff<'a> {
scratch.inner.clear();
scratch.inner.added.extend(layout.panel_ids());
scratch.inner.as_diff()
}
pub(crate) fn diff_overlays<'a>(
prev_rects: &[(OverlayId, Arc<str>, Rect)],
curr_rects: &[(OverlayId, Arc<str>, Rect)],
curr_failures: &[(OverlayId, Arc<str>, AnchorFailure)],
scratch: &'a mut OverlayDiffScratch,
) -> OverlayDiff<'a> {
scratch.clear();
scratch.curr_rect_indices.extend(
curr_rects
.iter()
.enumerate()
.map(|(index, (id, _, _))| (*id, index)),
);
scratch
.curr_failed_ids
.extend(curr_failures.iter().map(|(id, _, _)| *id));
scratch
.prev_rect_ids
.extend(prev_rects.iter().map(|(id, _, _)| *id));
for (old_id, _, old_rect) in prev_rects {
let in_curr = scratch
.curr_rect_indices
.get(old_id)
.map(|index| &curr_rects[*index].2);
let now_failed = scratch.curr_failed_ids.contains(old_id);
match (in_curr, now_failed) {
(Some(new_rect), _) => classify(
*old_id,
old_rect,
new_rect,
&mut scratch.inner.moved,
&mut scratch.inner.resized,
&mut scratch.inner.unchanged,
),
(None, true) => scratch.anchor_failed.push(*old_id),
(None, false) => scratch.inner.removed.push(*old_id),
}
}
for (new_id, _, _) in curr_rects {
if !scratch.prev_rect_ids.contains(new_id) {
scratch.inner.added.push(*new_id);
}
}
scratch.as_diff()
}
pub(crate) fn first_frame_overlays<'a>(
rects: &[(OverlayId, Arc<str>, Rect)],
failures: &[(OverlayId, Arc<str>, AnchorFailure)],
scratch: &'a mut OverlayDiffScratch,
) -> OverlayDiff<'a> {
scratch.clear();
scratch
.inner
.added
.extend(rects.iter().map(|(id, _, _)| *id));
scratch
.anchor_failed
.extend(failures.iter().map(|(id, _, _)| *id));
scratch.as_diff()
}