use crate::{
hash::{hash_subtree, hash_view_content},
node::{LayoutCache, LayoutConstraints, NodeId, TreeNode, TreeStats},
reconcile::ReconcileContext,
};
use repose_core::{Rect, View, ViewId};
use rustc_hash::{FxHashMap, FxHashSet};
use slotmap::SlotMap;
use smallvec::SmallVec;
pub struct ViewTree {
nodes: SlotMap<NodeId, TreeNode>,
root: Option<NodeId>,
dirty: FxHashSet<NodeId>,
paint_dirty: FxHashSet<NodeId>,
generation: u64,
view_id_map: FxHashMap<ViewId, NodeId>,
key_map: FxHashMap<(NodeId, u64), NodeId>,
pub stats: TreeStats,
pub removed_ids: Vec<NodeId>,
}
impl Default for ViewTree {
fn default() -> Self {
Self::new()
}
}
impl ViewTree {
pub fn new() -> Self {
Self {
nodes: SlotMap::with_key(),
root: None,
dirty: FxHashSet::default(),
paint_dirty: FxHashSet::default(),
generation: 0,
view_id_map: FxHashMap::default(),
key_map: FxHashMap::default(),
stats: TreeStats::default(),
removed_ids: Vec::new(),
}
}
pub fn generation(&self) -> u64 {
self.generation
}
pub fn root(&self) -> Option<NodeId> {
self.root
}
pub fn get(&self, id: NodeId) -> Option<&TreeNode> {
self.nodes.get(id)
}
pub fn get_mut(&mut self, id: NodeId) -> Option<&mut TreeNode> {
self.nodes.get_mut(id)
}
pub fn get_by_view_id(&self, view_id: ViewId) -> Option<&TreeNode> {
self.view_id_map
.get(&view_id)
.and_then(|id| self.nodes.get(*id))
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn is_dirty(&self, id: NodeId) -> bool {
self.dirty.contains(&id)
}
pub fn dirty_nodes(&self) -> &FxHashSet<NodeId> {
&self.dirty
}
pub fn clear_dirty(&mut self) {
self.dirty.clear();
}
pub fn mark_dirty(&mut self, id: NodeId) {
self.dirty.insert(id);
let mut current = id;
while let Some(node) = self.nodes.get(current) {
if let Some(parent) = node.parent {
self.dirty.insert(parent);
current = parent;
} else {
break;
}
}
}
pub fn update(&mut self, new_root: &View) -> NodeId {
self.removed_ids.clear();
self.generation += 1;
self.stats = TreeStats::default();
let mut ctx = ReconcileContext::new(self.generation);
let root_id = if let Some(existing_root) = self.root {
self.reconcile_node(existing_root, new_root, None, 0, &mut ctx)
} else {
self.create_node(new_root, None, 0, &mut ctx)
};
self.root = Some(root_id);
self.collect_garbage();
self.stats.total_nodes = self.nodes.len();
self.stats.dirty_nodes = self.dirty.len();
self.stats.reconciled_nodes = ctx.reconciled;
self.stats.skipped_nodes = ctx.skipped;
self.stats.created_nodes = ctx.created;
self.stats.removed_nodes = ctx.removed;
root_id
}
fn reconcile_node(
&mut self,
node_id: NodeId,
view: &View,
parent: Option<NodeId>,
depth: u32,
ctx: &mut ReconcileContext,
) -> NodeId {
let content_hash = hash_view_content(view);
let old_hash = self.nodes[node_id].content_hash;
let content_changed = old_hash != content_hash;
let new_children_hashes = self.reconcile_children(node_id, &view.children, depth, ctx);
let new_subtree_hash = hash_subtree(content_hash, &new_children_hashes);
let view_id = self.compute_view_id(view, node_id, parent, depth);
let subtree_changed;
{
let node = self.nodes.get_mut(node_id).unwrap();
node.parent = parent;
node.depth = depth;
node.generation = self.generation;
if content_changed {
node.kind = view.kind.clone();
node.modifier = view.modifier.clone();
node.content_hash = content_hash;
node.user_key = view.modifier.key;
node.invalidate_layout();
ctx.reconciled += 1;
}
subtree_changed = node.subtree_hash != new_subtree_hash;
if subtree_changed {
node.subtree_hash = new_subtree_hash;
} else if !content_changed {
ctx.skipped += 1;
}
node.view_id = view_id;
}
if subtree_changed {
self.mark_dirty(node_id);
}
self.view_id_map.insert(view_id, node_id);
node_id
}
fn reconcile_children(
&mut self,
parent_id: NodeId,
new_children: &[View],
parent_depth: u32,
ctx: &mut ReconcileContext,
) -> Vec<u64> {
let child_depth = parent_depth + 1;
let old_children: SmallVec<[NodeId; 4]> = self
.nodes
.get(parent_id)
.map(|n| n.children.clone())
.unwrap_or_default();
let mut keyed_children: FxHashMap<u64, NodeId> = FxHashMap::default();
let mut unkeyed_children: Vec<NodeId> = Vec::new();
for &child_id in &old_children {
if let Some(node) = self.nodes.get(child_id) {
if let Some(key) = node.user_key {
keyed_children.insert(key, child_id);
} else {
unkeyed_children.push(child_id);
}
}
}
let mut new_child_ids: SmallVec<[NodeId; 4]> = SmallVec::new();
let mut new_subtree_hashes: Vec<u64> = Vec::with_capacity(new_children.len());
let mut unkeyed_index = 0;
let mut used_nodes: FxHashSet<NodeId> = FxHashSet::default();
for new_child in new_children {
let child_id = if let Some(key) = new_child.modifier.key {
if let Some(&existing_id) = keyed_children.get(&key) {
used_nodes.insert(existing_id);
self.reconcile_node(existing_id, new_child, Some(parent_id), child_depth, ctx)
} else {
self.create_node(new_child, Some(parent_id), child_depth, ctx)
}
} else {
if unkeyed_index < unkeyed_children.len() {
let existing_id = unkeyed_children[unkeyed_index];
unkeyed_index += 1;
used_nodes.insert(existing_id);
self.reconcile_node(existing_id, new_child, Some(parent_id), child_depth, ctx)
} else {
self.create_node(new_child, Some(parent_id), child_depth, ctx)
}
};
new_child_ids.push(child_id);
if let Some(node) = self.nodes.get(child_id) {
new_subtree_hashes.push(node.subtree_hash);
}
}
for &old_child in &old_children {
if !used_nodes.contains(&old_child) {
self.mark_for_removal(old_child, ctx);
}
}
if let Some(parent) = self.nodes.get_mut(parent_id) {
parent.children = new_child_ids;
}
new_subtree_hashes
}
fn create_node(
&mut self,
view: &View,
parent: Option<NodeId>,
depth: u32,
ctx: &mut ReconcileContext,
) -> NodeId {
let content_hash = hash_view_content(view);
let node_id = self.nodes.insert_with_key(|id| {
TreeNode::new(
id,
0,
view.kind.clone(),
view.modifier.clone(),
self.generation,
)
});
ctx.created += 1;
{
let node = self.nodes.get_mut(node_id).unwrap();
node.parent = parent;
node.depth = depth;
node.content_hash = content_hash;
node.user_key = view.modifier.key;
}
let child_depth = depth + 1;
let mut child_ids: SmallVec<[NodeId; 4]> = SmallVec::new();
let mut child_hashes: Vec<u64> = Vec::with_capacity(view.children.len());
for child_view in &view.children {
let child_id = self.create_node(child_view, Some(node_id), child_depth, ctx);
child_ids.push(child_id);
child_hashes.push(self.nodes[child_id].subtree_hash);
}
let view_id = self.compute_view_id(view, node_id, parent, depth);
let subtree_hash = hash_subtree(content_hash, &child_hashes);
let node = self.nodes.get_mut(node_id).unwrap();
node.children = child_ids;
node.subtree_hash = subtree_hash;
node.view_id = view_id;
self.view_id_map.insert(view_id, node_id);
self.dirty.insert(node_id);
node_id
}
fn compute_view_id(
&self,
view: &View,
node_id: NodeId,
parent: Option<NodeId>,
index_in_parent: u32,
) -> ViewId {
if view.id != 0 {
return view.id;
}
let parent_id = parent
.and_then(|p| self.nodes.get(p))
.map(|n| n.view_id)
.unwrap_or(0);
let salt = view.modifier.key.unwrap_or(index_in_parent as u64);
let mut id = parent_id.wrapping_mul(31).wrapping_add(salt);
id = id.wrapping_mul(0x9E3779B97F4A7C15);
id ^= id >> 30;
if id == 0 {
id = 1;
}
id
}
fn mark_for_removal(&mut self, node_id: NodeId, ctx: &mut ReconcileContext) {
if let Some(node) = self.nodes.get(node_id) {
self.view_id_map.remove(&node.view_id);
let children: SmallVec<[NodeId; 4]> = node.children.clone();
for child_id in children {
self.mark_for_removal(child_id, ctx);
}
ctx.removed += 1;
}
if let Some(node) = self.nodes.get_mut(node_id) {
node.generation = 0; }
}
fn collect_garbage(&mut self) {
let current_gen = self.generation;
let to_remove: Vec<NodeId> = self
.nodes
.iter()
.filter(|(_, node)| node.generation != current_gen)
.map(|(id, _)| id)
.collect();
for id in to_remove {
if let Some(node) = self.nodes.remove(id) {
self.view_id_map.remove(&node.view_id);
self.dirty.remove(&id);
self.removed_ids.push(id);
}
}
}
pub fn set_layout(
&mut self,
id: NodeId,
rect: Rect,
screen_rect: Rect,
constraints: LayoutConstraints,
) {
if let Some(node) = self.nodes.get_mut(id) {
node.layout_cache = Some(LayoutCache {
rect,
screen_rect,
constraints,
generation: self.generation,
});
}
}
pub fn iter(&self) -> impl Iterator<Item = &TreeNode> {
self.nodes.values()
}
pub fn iter_with_ids(&self) -> impl Iterator<Item = (NodeId, &TreeNode)> {
self.nodes.iter()
}
pub fn walk<F>(&self, mut f: F)
where
F: FnMut(&TreeNode, u32) -> bool,
{
if let Some(root_id) = self.root {
self.walk_node(root_id, 0, &mut f);
}
}
fn walk_node<F>(&self, id: NodeId, depth: u32, f: &mut F)
where
F: FnMut(&TreeNode, u32) -> bool,
{
if let Some(node) = self.nodes.get(id) {
if !f(node, depth) {
return;
}
for &child_id in &node.children {
self.walk_node(child_id, depth + 1, f);
}
}
}
pub fn children(&self, id: NodeId) -> Option<&[NodeId]> {
self.nodes.get(id).map(|n| n.children.as_slice())
}
}
#[cfg(test)]
mod tests {
use super::*;
use repose_core::{Color, Modifier, View, ViewKind};
fn text_view(text: &str) -> View {
View::new(
0,
ViewKind::Text {
text: text.to_string(),
color: Color::WHITE,
font_size: 16.0,
soft_wrap: true,
max_lines: None,
overflow: repose_core::TextOverflow::Visible,
},
)
}
fn box_view() -> View {
View::new(0, ViewKind::Box)
}
#[test]
fn test_create_tree() {
let mut tree = ViewTree::new();
let root = box_view().with_children(vec![text_view("Hello"), text_view("World")]);
tree.update(&root);
assert_eq!(tree.len(), 3); assert!(tree.root().is_some());
}
#[test]
fn test_unchanged_tree_skips() {
let mut tree = ViewTree::new();
let root = box_view().with_children(vec![text_view("Hello")]);
tree.update(&root);
let gen1 = tree.generation();
tree.update(&root);
let gen2 = tree.generation();
assert_eq!(gen2, gen1 + 1);
assert!(tree.stats.skipped_nodes > 0);
}
#[test]
fn test_changed_content_reconciles() {
let mut tree = ViewTree::new();
let root1 = box_view().with_children(vec![text_view("Hello")]);
tree.update(&root1);
let root2 = box_view().with_children(vec![text_view("Changed")]);
tree.update(&root2);
assert!(tree.stats.reconciled_nodes > 0);
}
#[test]
fn test_keyed_children_stable() {
let mut tree = ViewTree::new();
let root1 = box_view().with_children(vec![
text_view("A").modifier(Modifier::new().key(1)),
text_view("B").modifier(Modifier::new().key(2)),
text_view("C").modifier(Modifier::new().key(3)),
]);
tree.update(&root1);
let b_view_id = tree
.root()
.and_then(|r| tree.children(r))
.and_then(|c| c.get(1).copied())
.and_then(|id| tree.get(id))
.map(|n| n.view_id);
let root2 = box_view().with_children(vec![
text_view("C").modifier(Modifier::new().key(3)),
text_view("A").modifier(Modifier::new().key(1)),
text_view("B").modifier(Modifier::new().key(2)),
]);
tree.update(&root2);
assert_eq!(tree.len(), 4); }
}