From c0cd1e565267775bbade515d2389de137f9cc281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Esp=C3=ADn?= Date: Mon, 18 Sep 2023 15:22:45 +0200 Subject: [PATCH] feat: Cache nodes validations (#305) --- crates/core/src/layout.rs | 6 +-- crates/dom/src/dom.rs | 4 +- crates/dom/src/dom_adapter.rs | 91 ++++++++++++++++++++++----------- crates/torin/benches/bench.rs | 24 ++++----- crates/torin/src/dom_adapter.rs | 4 +- crates/torin/src/torin.rs | 16 +++--- crates/torin/tests/test.rs | 66 ++++++++++++------------ 7 files changed, 122 insertions(+), 89 deletions(-) diff --git a/crates/core/src/layout.rs b/crates/core/src/layout.rs index 0f7b3ec95..9be7bbc19 100644 --- a/crates/core/src/layout.rs +++ b/crates/core/src/layout.rs @@ -13,17 +13,17 @@ pub fn process_layout( scale_factor: f32, ) -> (Layers, ViewportsCollection) { let rdom = fdom.rdom(); - let dom_adapter = DioxusDOMAdapter::new(rdom); + let mut dom_adapter = DioxusDOMAdapter::new_with_cache(rdom); let skia_measurer = SkiaMeasurer::new(rdom, font_collection); // Finds the best Node from where to start measuring - fdom.layout().find_best_root(&dom_adapter); + fdom.layout().find_best_root(&mut dom_adapter); let root_id = fdom.rdom().root_id(); // Measure the layout fdom.layout() - .measure(root_id, area, &mut Some(skia_measurer), &dom_adapter); + .measure(root_id, area, &mut Some(skia_measurer), &mut dom_adapter); // Create the layers let mut layers = Layers::default(); diff --git a/crates/dom/src/dom.rs b/crates/dom/src/dom.rs index 535e2560a..4d0a1391c 100644 --- a/crates/dom/src/dom.rs +++ b/crates/dom/src/dom.rs @@ -123,10 +123,10 @@ impl FreyaDOM { /// Process the given mutations from the [`VirtualDOM`](dioxus_core::VirtualDom). pub fn apply_mutations(&mut self, mutations: Mutations, scale_factor: f32) -> (bool, bool) { - let dom_adapter = DioxusDOMAdapter::new(self.rdom()); + let mut dom_adapter = DioxusDOMAdapter::new_with_cache(self.rdom()); // Apply the mutations to the layout self.layout() - .apply_mutations(&mutations, &self.dioxus_integration_state, &dom_adapter); + .apply_mutations(&mutations, &self.dioxus_integration_state, &mut dom_adapter); // Apply the mutations the integration state self.dioxus_integration_state diff --git a/crates/dom/src/dom_adapter.rs b/crates/dom/src/dom_adapter.rs index 6903298c5..e97279bb4 100644 --- a/crates/dom/src/dom_adapter.rs +++ b/crates/dom/src/dom_adapter.rs @@ -1,5 +1,6 @@ use dioxus_native_core::{prelude::NodeType, real_dom::NodeImmutable, tree::TreeRef, NodeId}; use freya_node_state::LayoutState; +use rustc_hash::FxHashMap; use torin::prelude::*; use crate::dom::DioxusDOM; @@ -7,11 +8,23 @@ use crate::dom::DioxusDOM; /// RealDOM adapter for Torin. pub struct DioxusDOMAdapter<'a> { pub rdom: &'a DioxusDOM, + + valid_nodes_cache: Option>, } impl<'a> DioxusDOMAdapter<'a> { pub fn new(rdom: &'a DioxusDOM) -> Self { - Self { rdom } + Self { + rdom, + valid_nodes_cache: None, + } + } + + pub fn new_with_cache(rdom: &'a DioxusDOM) -> Self { + Self { + rdom, + valid_nodes_cache: Some(FxHashMap::default()), + } } } @@ -51,17 +64,17 @@ impl DOMAdapter for DioxusDOMAdapter<'_> { self.rdom.tree_ref().parent_id(*node_id) } - fn children_of(&self, node_id: &NodeId) -> Vec { + fn children_of(&mut self, node_id: &NodeId) -> Vec { self.rdom .tree_ref() .children_ids(*node_id) .into_iter() - .filter(|id| is_node_valid(self.rdom, id)) + .filter(|id| is_node_valid(self.rdom, &mut self.valid_nodes_cache, id)) .collect::>() } - fn is_node_valid(&self, node_id: &NodeId) -> bool { - is_node_valid(self.rdom, node_id) + fn is_node_valid(&mut self, node_id: &NodeId) -> bool { + is_node_valid(self.rdom, &mut self.valid_nodes_cache, node_id) } fn closest_common_parent(&self, node_id_a: &NodeId, node_id_b: &NodeId) -> Option { @@ -134,38 +147,58 @@ fn find_common_parent(rdom: &DioxusDOM, node_a: NodeId, node_b: NodeId) -> Optio } /// Check is the given Node is valid or not, this means not being a placeholder or an unconnected Node. -fn is_node_valid(rdom: &DioxusDOM, node_id: &NodeId) -> bool { +fn is_node_valid( + rdom: &DioxusDOM, + valid_nodes_cache: &mut Option>, + node_id: &NodeId, +) -> bool { + // Check if Node was valid from cache + if let Some(valid_nodes_cache) = valid_nodes_cache { + if let Some(is_valid) = valid_nodes_cache.get(node_id) { + return *is_valid; + } + } + let node = rdom.get(*node_id); - if let Some(node) = node { - let is_placeholder = matches!(*node.node_type(), NodeType::Placeholder); + let is_valid = 'validation: { + if let Some(node) = node { + let is_placeholder = matches!(*node.node_type(), NodeType::Placeholder); - // Placeholders can't be measured - if is_placeholder { - return false; - } + // Placeholders can't be measured + if is_placeholder { + break 'validation false; + } - // Make sure this Node isn't part of an unconnected Node - // This walkes up to the ancestor that has a height of 0 and checks if it has the same ID as the root Node - // If it has the same ID, it means that is not an unconnected ID, otherwise, it is and should be skipped. - let tree = rdom.tree_ref(); - let mut current = *node_id; - loop { - let height = tree.height(current); - if let Some(height) = height { - if height == 0 { - break; + // Make sure this Node isn't part of an unconnected Node + // This walkes up to the ancestor that has a height of 0 and checks if it has the same ID as the root Node + // If it has the same ID, it means that is not an unconnected ID, otherwise, it is and should be skipped. + let tree = rdom.tree_ref(); + let mut current = *node_id; + loop { + let height = tree.height(current); + if let Some(height) = height { + if height == 0 { + break; + } } - } - let parent_current = tree.parent_id(current); - if let Some(parent_current) = parent_current { - current = parent_current; + let parent_current = tree.parent_id(current); + if let Some(parent_current) = parent_current { + current = parent_current; + } } + + current == rdom.root_id() + } else { + false } + }; - current == rdom.root_id() - } else { - false + // Save the validation result in the cache + if let Some(valid_nodes_cache) = valid_nodes_cache { + valid_nodes_cache.insert(*node_id, is_valid); } + + is_valid } diff --git a/crates/torin/benches/bench.rs b/crates/torin/benches/bench.rs index 2092659fa..ccc15fe33 100644 --- a/crates/torin/benches/bench.rs +++ b/crates/torin/benches/bench.rs @@ -35,7 +35,7 @@ impl TestingDOM { } impl DOMAdapter for TestingDOM { - fn children_of(&self, node_id: &usize) -> Vec { + fn children_of(&mut self, node_id: &usize) -> Vec { self.mapper .get(node_id) .map(|c| c.1.clone()) @@ -54,7 +54,7 @@ impl DOMAdapter for TestingDOM { self.mapper.get(node_id).map(|c| c.3.clone()) } - fn is_node_valid(&self, _node_id: &usize) -> bool { + fn is_node_valid(&mut self, _node_id: &usize) -> bool { true } @@ -122,12 +122,12 @@ fn criterion_benchmark(c: &mut Criterion) { b.iter(|| { black_box({ let mut layout = Torin::::new(); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ) }); }) @@ -179,12 +179,12 @@ fn criterion_benchmark(c: &mut Criterion) { } } - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); b.iter(|| { @@ -198,12 +198,12 @@ fn criterion_benchmark(c: &mut Criterion) { ), ); layout.invalidate(1); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ) }); }) @@ -255,12 +255,12 @@ fn criterion_benchmark(c: &mut Criterion) { } } - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); b.iter(|| { @@ -274,12 +274,12 @@ fn criterion_benchmark(c: &mut Criterion) { ), ); layout.invalidate(2001); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ) }); }) diff --git a/crates/torin/src/dom_adapter.rs b/crates/torin/src/dom_adapter.rs index a9e0111ae..965a24c4d 100644 --- a/crates/torin/src/dom_adapter.rs +++ b/crates/torin/src/dom_adapter.rs @@ -47,10 +47,10 @@ pub trait DOMAdapter { fn parent_of(&self, node_id: &NodeKey) -> Option; /// Get the children of a Node - fn children_of(&self, node_id: &NodeKey) -> Vec; + fn children_of(&mut self, node_id: &NodeKey) -> Vec; /// Check whether the given Node is valid (isn't a placeholder, unconnected node..) - fn is_node_valid(&self, node_id: &NodeKey) -> bool; + fn is_node_valid(&mut self, node_id: &NodeKey) -> bool; /// Get the closest common parent Node of two Nodes fn closest_common_parent(&self, node_id_a: &NodeKey, node_id_b: &NodeKey) -> Option; diff --git a/crates/torin/src/torin.rs b/crates/torin/src/torin.rs index 3aa8204eb..833278288 100644 --- a/crates/torin/src/torin.rs +++ b/crates/torin/src/torin.rs @@ -53,7 +53,7 @@ impl Torin { &mut self, mutations: &Mutations, dioxus_integration_state: &DioxusState, - dom_adapter: &impl DOMAdapter, + dom_adapter: &mut impl DOMAdapter, ) { use dioxus_core::Mutation; @@ -131,7 +131,7 @@ impl Torin { pub fn remove( &mut self, node_id: Key, - dom_adapter: &impl DOMAdapter, + dom_adapter: &mut impl DOMAdapter, invalidate_parent: bool, ) { // Remove itself @@ -153,7 +153,7 @@ impl Torin { self.dirty.insert(node_id); } - pub fn safe_invalidate(&mut self, node_id: Key, dom_adapter: &impl DOMAdapter) { + pub fn safe_invalidate(&mut self, node_id: Key, dom_adapter: &mut impl DOMAdapter) { if dom_adapter.is_node_valid(&node_id) { self.invalidate(node_id) } @@ -163,7 +163,7 @@ impl Torin { pub fn check_dirty_dependants( &mut self, node_id: Key, - dom_adapter: &impl DOMAdapter, + dom_adapter: &mut impl DOMAdapter, ignore: bool, ) { if (self.dirty.contains(&node_id) && ignore) || !dom_adapter.is_node_valid(&node_id) { @@ -220,7 +220,7 @@ impl Torin { } /// Find the best root Node from where to start measuring - pub fn find_best_root(&mut self, dom_adapter: &impl DOMAdapter) { + pub fn find_best_root(&mut self, dom_adapter: &mut impl DOMAdapter) { if self.results.is_empty() { return; } @@ -235,7 +235,7 @@ impl Torin { suggested_root_id: Key, suggested_root_area: Area, measurer: &mut Option>, - dom_adapter: &impl DOMAdapter, + dom_adapter: &mut impl DOMAdapter, ) { // If there are previosuly cached results // But no dirty nodes, we can simply skip the measurement @@ -312,7 +312,7 @@ fn measure_node( available_parent_area: &Area, measurer: &mut Option>, must_cache: bool, - dom_adapter: &impl DOMAdapter, + dom_adapter: &mut impl DOMAdapter, ) -> (bool, NodeAreas) { let must_run = layout.dirty.contains(&node_id) || layout.results.get(&node_id).is_none(); if must_run { @@ -491,7 +491,7 @@ fn measure_inner_nodes( measurer: &mut Option>, must_cache: bool, mode: &mut MeasureMode, - dom_adapter: &impl DOMAdapter, + dom_adapter: &mut impl DOMAdapter, ) { let children = dom_adapter.children_of(node_id); diff --git a/crates/torin/tests/test.rs b/crates/torin/tests/test.rs index 524ef761d..39c747106 100644 --- a/crates/torin/tests/test.rs +++ b/crates/torin/tests/test.rs @@ -49,7 +49,7 @@ impl TestingDOM { } impl DOMAdapter for TestingDOM { - fn children_of(&self, node_id: &usize) -> Vec { + fn children_of(&mut self, node_id: &usize) -> Vec { self.mapper .get(node_id) .map(|c| c.1.clone()) @@ -68,7 +68,7 @@ impl DOMAdapter for TestingDOM { self.mapper.get(node_id).map(|c| c.3.clone()) } - fn is_node_valid(&self, _node_id: &usize) -> bool { + fn is_node_valid(&mut self, _node_id: &usize) -> bool { true } @@ -120,12 +120,12 @@ pub fn root_100per_children_50per50per() { ), ); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -180,12 +180,12 @@ pub fn root_200px_children_50per50per() { ), ); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -244,7 +244,7 @@ pub fn layout_dirty_nodes() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); // CASE 1 @@ -365,7 +365,7 @@ pub fn direction() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -390,12 +390,12 @@ pub fn direction() { ); layout.invalidate(0); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -446,12 +446,12 @@ pub fn scroll() { ), ); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -495,7 +495,7 @@ pub fn padding() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -534,7 +534,7 @@ pub fn caching() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -556,7 +556,7 @@ pub fn caching() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -601,7 +601,7 @@ pub fn sibling_increments_area() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -661,7 +661,7 @@ pub fn node_removal() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -679,11 +679,11 @@ pub fn node_removal() { Rect::new(Point2D::new(0.0, 200.0), Size2D::new(200.0, 200.0)), ); - layout.remove(2, &mocked_dom, true); + layout.remove(2, &mut mocked_dom, true); mocked_dom.remove(2); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); assert_eq!(layout.get_dirty_nodes(), &HashSet::from([1, 3])); @@ -691,7 +691,7 @@ pub fn node_removal() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -741,7 +741,7 @@ pub fn display_horizontal() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -792,12 +792,12 @@ pub fn display_vertical_with_inner_children() { ), ); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -883,12 +883,12 @@ pub fn deep_tree() { ), ); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); mocked_dom.set_node( @@ -901,14 +901,14 @@ pub fn deep_tree() { ); layout.invalidate(4); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); assert_eq!(layout.get_root_candidate(), RootNodeCandidate::Valid(4)); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!(layout.get_root_candidate(), RootNodeCandidate::None); @@ -954,7 +954,7 @@ pub fn stacked() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -977,13 +977,13 @@ pub fn stacked() { ); layout.invalidate(2); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -1033,12 +1033,12 @@ pub fn two_cols_auto() { ), ); - layout.find_best_root(&mocked_dom); + layout.find_best_root(&mut mocked_dom); layout.measure( 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(400.0, 400.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); assert_eq!( @@ -1097,7 +1097,7 @@ pub fn margin() { 0, Rect::new(Point2D::new(0.0, 0.0), Size2D::new(1000.0, 1000.0)), &mut measurer, - &mocked_dom, + &mut mocked_dom, ); let node_areas = layout.get(1).unwrap();