Skip to content

Commit

Permalink
feat: Cache nodes validations (#305)
Browse files Browse the repository at this point in the history
  • Loading branch information
marc2332 authored Sep 18, 2023
1 parent aa192b4 commit c0cd1e5
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 89 deletions.
6 changes: 3 additions & 3 deletions crates/core/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions crates/dom/src/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
91 changes: 62 additions & 29 deletions crates/dom/src/dom_adapter.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
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;

/// RealDOM adapter for Torin.
pub struct DioxusDOMAdapter<'a> {
pub rdom: &'a DioxusDOM,

valid_nodes_cache: Option<FxHashMap<NodeId, bool>>,
}

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()),
}
}
}

Expand Down Expand Up @@ -51,17 +64,17 @@ impl DOMAdapter<NodeId> for DioxusDOMAdapter<'_> {
self.rdom.tree_ref().parent_id(*node_id)
}

fn children_of(&self, node_id: &NodeId) -> Vec<NodeId> {
fn children_of(&mut self, node_id: &NodeId) -> Vec<NodeId> {
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::<Vec<NodeId>>()
}

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<NodeId> {
Expand Down Expand Up @@ -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<FxHashMap<NodeId, bool>>,
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
}
24 changes: 12 additions & 12 deletions crates/torin/benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl TestingDOM {
}

impl DOMAdapter<usize> for TestingDOM {
fn children_of(&self, node_id: &usize) -> Vec<usize> {
fn children_of(&mut self, node_id: &usize) -> Vec<usize> {
self.mapper
.get(node_id)
.map(|c| c.1.clone())
Expand All @@ -54,7 +54,7 @@ impl DOMAdapter<usize> 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
}

Expand Down Expand Up @@ -122,12 +122,12 @@ fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| {
black_box({
let mut layout = Torin::<usize>::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,
)
});
})
Expand Down Expand Up @@ -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(|| {
Expand All @@ -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,
)
});
})
Expand Down Expand Up @@ -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(|| {
Expand All @@ -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,
)
});
})
Expand Down
4 changes: 2 additions & 2 deletions crates/torin/src/dom_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ pub trait DOMAdapter<NodeKey> {
fn parent_of(&self, node_id: &NodeKey) -> Option<NodeKey>;

/// Get the children of a Node
fn children_of(&self, node_id: &NodeKey) -> Vec<NodeKey>;
fn children_of(&mut self, node_id: &NodeKey) -> Vec<NodeKey>;

/// 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<NodeKey>;
Expand Down
16 changes: 8 additions & 8 deletions crates/torin/src/torin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl Torin<NodeId> {
&mut self,
mutations: &Mutations,
dioxus_integration_state: &DioxusState,
dom_adapter: &impl DOMAdapter<NodeId>,
dom_adapter: &mut impl DOMAdapter<NodeId>,
) {
use dioxus_core::Mutation;

Expand Down Expand Up @@ -131,7 +131,7 @@ impl<Key: NodeKey> Torin<Key> {
pub fn remove(
&mut self,
node_id: Key,
dom_adapter: &impl DOMAdapter<Key>,
dom_adapter: &mut impl DOMAdapter<Key>,
invalidate_parent: bool,
) {
// Remove itself
Expand All @@ -153,7 +153,7 @@ impl<Key: NodeKey> Torin<Key> {
self.dirty.insert(node_id);
}

pub fn safe_invalidate(&mut self, node_id: Key, dom_adapter: &impl DOMAdapter<Key>) {
pub fn safe_invalidate(&mut self, node_id: Key, dom_adapter: &mut impl DOMAdapter<Key>) {
if dom_adapter.is_node_valid(&node_id) {
self.invalidate(node_id)
}
Expand All @@ -163,7 +163,7 @@ impl<Key: NodeKey> Torin<Key> {
pub fn check_dirty_dependants(
&mut self,
node_id: Key,
dom_adapter: &impl DOMAdapter<Key>,
dom_adapter: &mut impl DOMAdapter<Key>,
ignore: bool,
) {
if (self.dirty.contains(&node_id) && ignore) || !dom_adapter.is_node_valid(&node_id) {
Expand Down Expand Up @@ -220,7 +220,7 @@ impl<Key: NodeKey> Torin<Key> {
}

/// Find the best root Node from where to start measuring
pub fn find_best_root(&mut self, dom_adapter: &impl DOMAdapter<Key>) {
pub fn find_best_root(&mut self, dom_adapter: &mut impl DOMAdapter<Key>) {
if self.results.is_empty() {
return;
}
Expand All @@ -235,7 +235,7 @@ impl<Key: NodeKey> Torin<Key> {
suggested_root_id: Key,
suggested_root_area: Area,
measurer: &mut Option<impl LayoutMeasurer<Key>>,
dom_adapter: &impl DOMAdapter<Key>,
dom_adapter: &mut impl DOMAdapter<Key>,
) {
// If there are previosuly cached results
// But no dirty nodes, we can simply skip the measurement
Expand Down Expand Up @@ -312,7 +312,7 @@ fn measure_node<Key: NodeKey>(
available_parent_area: &Area,
measurer: &mut Option<impl LayoutMeasurer<Key>>,
must_cache: bool,
dom_adapter: &impl DOMAdapter<Key>,
dom_adapter: &mut impl DOMAdapter<Key>,
) -> (bool, NodeAreas) {
let must_run = layout.dirty.contains(&node_id) || layout.results.get(&node_id).is_none();
if must_run {
Expand Down Expand Up @@ -491,7 +491,7 @@ fn measure_inner_nodes<Key: NodeKey>(
measurer: &mut Option<impl LayoutMeasurer<Key>>,
must_cache: bool,
mode: &mut MeasureMode,
dom_adapter: &impl DOMAdapter<Key>,
dom_adapter: &mut impl DOMAdapter<Key>,
) {
let children = dom_adapter.children_of(node_id);

Expand Down
Loading

0 comments on commit c0cd1e5

Please sign in to comment.