use cranpose_core::{MemoryApplier, NodeId, NodeMarks, collections::map::HashMap};
use cranpose_ui::Point;
use super::{
AbsOrigin, Level, LowerContext, TranslateAncestorContext, children_have_origin_sinks,
inline_leaf::fold_kept_leaf, layer_identity, note_space_change, read_placed_node_data,
rebuild_tracking_hits, release_for_rebuild, remove_dirty_descendants,
try_update_retained_layer, update_dirty_inline, write_node_layer,
};
use crate::{
NodeUpdate,
graph::{LayerNode, RenderGraph, RenderNode},
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(super) struct ReplaceDirtyLayersReport {
updated: bool,
pub(super) hit_graph_dirty: bool,
}
struct DirtyWalk<'a> {
nodes: &'a mut HashMap<NodeId, NodeUpdate>,
ancestry: &'a NodeMarks,
applier: &'a MemoryApplier,
}
impl DirtyWalk<'_> {
fn reborrow(&mut self) -> DirtyWalk<'_> {
DirtyWalk {
nodes: self.nodes,
ancestry: self.ancestry,
applier: self.applier,
}
}
#[inline]
fn marks(&self, identity: Option<NodeId>) -> u8 {
identity.map_or(DIRTY_NODE | DIRTY_ANCESTOR, |id| {
self.ancestry.get(self.applier, id)
})
}
}
const DIRTY_ANCESTOR: u8 = 1;
const DIRTY_NODE: u8 = 2;
pub(super) fn walk_dirty_layers(
applier: &MemoryApplier,
graph: &mut RenderGraph,
dirty: &mut HashMap<NodeId, NodeUpdate>,
update: u64,
) -> Option<(ReplaceDirtyLayersReport, Option<RetainedPath>)> {
let RenderGraph {
root,
dirty_ancestry: ancestry,
..
} = graph;
let inherited = root.translated_content_context;
let root_children = AbsOrigin::ROOT.children_of(root);
let lone = match dirty.keys().next() {
Some(&node) if dirty.len() == 1 => Some(node),
_ => None,
};
ancestry.mark(applier, dirty, update);
let mut walk = DirtyWalk {
nodes: dirty,
ancestry: &ancestry.marks,
applier,
};
let walked = replace_dirty_layers_from_applier(
applier,
root,
root_children,
walk.reborrow(),
inherited,
update,
);
let path = match (&walked, lone) {
(Some(report), Some(node)) if report.updated && walk.nodes.is_empty() => {
RetainedPath::to(root, &walk, node, update)
}
_ => None,
};
walked.map(|report| (report, path))
}
#[derive(Clone, Default)]
pub(crate) struct DirtyAncestry {
marks: NodeMarks,
nodes: usize,
update: u64,
}
impl DirtyAncestry {
#[inline]
fn mark(&mut self, applier: &MemoryApplier, dirty: &HashMap<NodeId, NodeUpdate>, update: u64) {
let kept = self.update + 1 == update
&& self.nodes == dirty.len()
&& dirty
.keys()
.all(|&node| self.marks.get(applier, node) & DIRTY_NODE != 0);
self.update = update;
if kept {
return;
}
self.nodes = dirty.len();
mark_ancestry(&mut self.marks, applier, dirty);
}
}
#[inline(never)]
fn mark_ancestry(
marks: &mut NodeMarks,
applier: &MemoryApplier,
dirty: &HashMap<NodeId, NodeUpdate>,
) {
let mut fresh = std::mem::take(marks);
fresh.reset(applier);
for &node in dirty.keys() {
fresh.add_with_ancestors(applier, node, DIRTY_NODE, DIRTY_ANCESTOR);
}
*marks = fresh;
}
#[derive(Clone)]
pub(crate) struct RetainedPath {
pub(super) node: NodeId,
pub(super) steps: Vec<(usize, Option<NodeId>)>,
pub(super) pass: ParentPass,
pub(super) update: u64,
pub(super) in_space: bool,
}
impl RetainedPath {
fn to(root: &LayerNode, walk: &DirtyWalk<'_>, node: NodeId, update: u64) -> Option<Self> {
let mut steps = Vec::new();
let pass = path_steps(
root,
AbsOrigin::ROOT.children_of(root),
root.translated_content_context,
walk,
node,
&mut steps,
)?;
let in_space = root.graphics_layer.space.is_some() || {
let mut layer = root;
steps
.iter()
.any(|&(index, _)| match layer.children.get(index) {
Some(RenderNode::Layer(child)) => {
layer = child;
child.graphics_layer.space.is_some()
}
_ => false,
})
};
Some(Self {
node,
steps,
pass,
update,
in_space,
})
}
}
fn path_steps(
parent: &LayerNode,
parent_children: AbsOrigin,
inherited_translated_content_context: bool,
walk: &DirtyWalk<'_>,
node: NodeId,
steps: &mut Vec<(usize, Option<NodeId>)>,
) -> Option<ParentPass> {
let translated = inherited_translated_content_context || parent.translated_content_context;
for (index, child) in parent.children.iter().enumerate() {
let layer = match child {
RenderNode::Layer(layer) => layer,
RenderNode::Inline(inline) if inline.node_id == node => {
steps.push((index, Some(node)));
return Some(ParentPass::of(parent, parent_children, translated));
}
RenderNode::Inline(_) | RenderNode::Primitive(_) | RenderNode::DrawRun(_) => continue,
};
let identity = layer_identity(layer);
if identity == Some(node) {
steps.push((index, identity));
return Some(ParentPass::of(parent, parent_children, translated));
}
if walk.marks(identity) & DIRTY_ANCESTOR == 0 {
continue;
}
steps.push((index, identity));
let children = parent_children.children_of(layer);
if let Some(pass) = path_steps(layer, children, translated, walk, node, steps) {
return Some(pass);
}
steps.pop();
}
None
}
pub(super) fn update_along_retained_path(
applier: &MemoryApplier,
root: &mut LayerNode,
path: &RetainedPath,
dirty: &mut HashMap<NodeId, NodeUpdate>,
update: u64,
) -> Option<Option<ReplaceDirtyLayersReport>> {
if dirty.len() != 1 || path.update + 1 != update || !leads_to(root, &path.steps) {
return None;
}
let kind = dirty.remove(&path.node)?;
if path.in_space {
note_space_change();
}
Some(update_at_step(
applier,
root,
path,
&path.steps,
kind,
dirty,
update,
))
}
fn leads_to(mut layer: &LayerNode, steps: &[(usize, Option<NodeId>)]) -> bool {
let Some((&(last, identity), way)) = steps.split_last() else {
return false;
};
for &(index, identity) in way {
match layer.children.get(index) {
Some(RenderNode::Layer(child)) if layer_identity(child) == identity => layer = child,
_ => return false,
}
}
match layer.children.get(last) {
Some(RenderNode::Layer(child)) => layer_identity(child) == identity,
Some(RenderNode::Inline(inline)) => Some(inline.node_id) == identity,
Some(RenderNode::Primitive(_) | RenderNode::DrawRun(_)) | None => false,
}
}
fn update_at_step(
applier: &MemoryApplier,
layer: &mut LayerNode,
path: &RetainedPath,
steps: &[(usize, Option<NodeId>)],
kind: NodeUpdate,
dirty: &mut HashMap<NodeId, NodeUpdate>,
update: u64,
) -> Option<ReplaceDirtyLayersReport> {
let (&(index, _), rest) = steps.split_first()?;
let hit_graph_dirty = match (layer.children.get_mut(index)?, rest.is_empty()) {
(slot @ RenderNode::Layer(_), true) => {
let RenderNode::Layer(child) = slot else {
unreachable!("the slot holds a layer");
};
let hit_graph_dirty =
update_dirty_layer(applier, child, path.node, kind, dirty, path.pass, update)?.0;
fold_kept_leaf(applier, slot, path.pass.content_offset);
hit_graph_dirty
}
(RenderNode::Layer(child), false) => {
update_at_step(applier, child, path, rest, kind, dirty, update)?.hit_graph_dirty
}
(slot @ RenderNode::Inline(_), true) => {
let context = path.pass.lower_context();
update_dirty_inline(applier, slot, path.node, kind, context, dirty)?
}
_ => return None,
};
note_updated_children(layer, update);
Some(ReplaceDirtyLayersReport {
updated: true,
hit_graph_dirty,
})
}
fn replace_dirty_layers_from_applier(
applier: &MemoryApplier,
parent: &mut LayerNode,
parent_children: AbsOrigin,
mut dirty: DirtyWalk<'_>,
inherited_translated_content_context: bool,
update: u64,
) -> Option<ReplaceDirtyLayersReport> {
if dirty.nodes.is_empty() {
return Some(ReplaceDirtyLayersReport::default());
}
let child_inherited_translated_content_context =
inherited_translated_content_context || parent.translated_content_context;
let pass = ParentPass::of(
parent,
parent_children,
child_inherited_translated_content_context,
);
let mut report = ReplaceDirtyLayersReport::default();
for child in &mut parent.children {
if dirty.nodes.is_empty() {
break;
}
let child_layer = match child {
RenderNode::Layer(child_layer) => child_layer,
RenderNode::Inline(_) => {
update_inline_child(applier, child, &mut dirty, pass, &mut report)?;
continue;
}
RenderNode::Primitive(_) | RenderNode::DrawRun(_) => continue,
};
let identity = layer_identity(child_layer);
let marks = dirty.marks(identity);
if marks == 0 {
continue;
}
if child_layer.graphics_layer.space.is_some() {
note_space_change();
}
if marks & DIRTY_NODE != 0
&& let Some(node_id) = identity
&& let Some(kind) = dirty.nodes.remove(&node_id)
{
let (hit_graph_dirty, retained) = update_dirty_layer(
applier,
child_layer,
node_id,
kind,
dirty.nodes,
pass,
update,
)?;
report.hit_graph_dirty |= hit_graph_dirty;
report.updated = true;
if retained {
let child_children = parent_children.children_of(child_layer);
let child_report = replace_dirty_layers_from_applier(
applier,
child_layer,
child_children,
dirty.reborrow(),
child_inherited_translated_content_context,
update,
)?;
report.hit_graph_dirty |= child_report.hit_graph_dirty;
}
fold_kept_leaf(applier, child, pass.content_offset);
continue;
}
if marks & DIRTY_ANCESTOR == 0 {
continue;
}
let child_children = parent_children.children_of(child_layer);
let child_report = replace_dirty_layers_from_applier(
applier,
child_layer,
child_children,
dirty.reborrow(),
child_inherited_translated_content_context,
update,
)?;
report.updated |= child_report.updated;
report.hit_graph_dirty |= child_report.hit_graph_dirty;
}
if report.updated {
note_updated_children(parent, update);
}
Some(report)
}
#[inline]
fn update_inline_child(
applier: &MemoryApplier,
child: &mut RenderNode,
dirty: &mut DirtyWalk<'_>,
pass: ParentPass,
report: &mut ReplaceDirtyLayersReport,
) -> Option<()> {
let RenderNode::Inline(inline) = child else {
return Some(());
};
let node_id = inline.node_id;
if dirty.marks(Some(node_id)) & DIRTY_NODE == 0 {
return Some(());
}
let Some(kind) = dirty.nodes.remove(&node_id) else {
return Some(());
};
let context = pass.lower_context();
report.hit_graph_dirty |=
update_dirty_inline(applier, child, node_id, kind, context, dirty.nodes)?;
report.updated = true;
Some(())
}
fn note_updated_children(parent: &mut LayerNode, update: u64) {
parent.refresh_child_facts();
parent.has_origin_sinks |= children_have_origin_sinks(&parent.children);
parent.note_descendant_change(update);
}
#[derive(Clone, Copy)]
pub(crate) struct ParentPass {
motion_context_animated: bool,
translated_content_context: bool,
content_offset: Point,
children: AbsOrigin,
}
impl ParentPass {
fn of(parent: &LayerNode, children: AbsOrigin, translated_content_context: bool) -> Self {
Self {
motion_context_animated: parent.motion_context_animated,
translated_content_context,
content_offset: parent.content_offset,
children,
}
}
fn lower_context(self) -> LowerContext {
LowerContext {
inherited_motion_context_animated: self.motion_context_animated,
inherited_translated_content_context: self.translated_content_context,
parent_abs: Some(self.children),
parent_content_offset: self.content_offset,
}
}
}
fn update_dirty_layer(
applier: &MemoryApplier,
layer: &mut LayerNode,
node_id: NodeId,
kind: NodeUpdate,
dirty_nodes: &mut HashMap<NodeId, NodeUpdate>,
pass: ParentPass,
update: u64,
) -> Option<(bool, bool)> {
if let Some(hit_graph_dirty) = try_update_retained_layer(
applier,
layer,
dirty_nodes,
kind,
Level::Child,
TranslateAncestorContext {
inherited_motion_context_animated: pass.motion_context_animated,
update,
inherited_translated_content_context: pass.translated_content_context,
parent_content_offset: pass.content_offset,
parent_abs: pass.children,
},
) {
return Some((hit_graph_dirty, true));
}
remove_dirty_descendants(layer, dirty_nodes);
let context = pass.lower_context();
let hit_graph_dirty = rebuild_tracking_hits(layer, |layer| {
release_for_rebuild(layer);
read_placed_node_data(applier, node_id, Level::Child, |data| {
write_node_layer(applier, node_id, data, context, layer);
})
.is_some()
})?;
remove_dirty_descendants(layer, dirty_nodes);
layer.note_content_change(update);
Some((hit_graph_dirty, false))
}