gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 87Markdown 12TOML 91924 Sutichaykuna

Move cache logic to own module (#432)

* Remove the measure_node and needs_measure methods from the LayoutTree trait

* Remove cache_mut method from the LayoutTree trait

* Update release notes

* Move caching logic to it's own module

* cargo fmt

* Fix caching strategy docs

3bda6444237f92c60a8a9ba24495554b9faa72c3 · ruwaq Nico Burns · (3 wata ñawpaq) · tayta 384b60bda · kay commitpi sach'ata qhaway

Semántico sayk'uy · 19

src/cache.rs

src/compute/mod.rs

src/data.rs

src/layout.rs

  • qullusqa struct Cache pub

src/lib.rs

src/node.rs

  • ukhunpi t'ikrakurqan fn dirty pub

6 qillqakuna · +151 −121

diff --git a/src/cache.rs b/src/cache.rs
new file mode 100644
index 000000000..65bf65280
--- /dev/null
+++ b/src/cache.rs
@@ -0,0 +1,139 @@
+//! A cache for storing the results of layout computation
+use crate::geometry::Size;
+use crate::layout::{RunMode, SizeAndBaselines};
+use crate::style::AvailableSpace;
+
+/// The number of cache entries for each node in the tree
+pub(crate) const CACHE_SIZE: usize = 7;
+
+/// Cached intermediate layout results
+#[derive(Debug, Clone, Copy)]
+pub struct CacheEntry {
+    /// The initial cached size of the node itself
+    known_dimensions: Size<Option<f32>>,
+    /// The initial cached size of the parent's node
+    available_space: Size<AvailableSpace>,
+    /// Whether or not layout should be recomputed
+    run_mode: RunMode,
+
+    /// The cached size and baselines of the item
+    cached_size_and_baselines: SizeAndBaselines,
+}
+
+/// A cache for caching the results of a sizing a Grid Item or Flexbox Item
+pub struct Cache {
+    /// An array of entries in the cache
+    entries: [Option<CacheEntry>; CACHE_SIZE],
+}
+
+impl Cache {
+    /// Create a new empty cache
+    pub const fn new() -> Self {
+        Self { entries: [None; CACHE_SIZE] }
+    }
+
+    /// Return the cache slot to cache the current computed result in
+    ///
+    /// ## Caching Strategy
+    ///
+    /// We need multiple cache slots, because a node's size is often queried by it's parent multiple times in the course of the layout
+    /// process, and we don't want later results to clobber earlier ones.
+    ///
+    /// The two variables that we care about when determining cache slot are:
+    ///
+    ///   - How many "known_dimensions" are set. In the worst case, a node may be called first with neither dimension known, then with one
+    ///     dimension known (either width of height - which doesn't matter for our purposes here), and then with both dimensions known.
+    ///   - Whether unknown dimensions are being sized under a min-content or a max-content available space constraint (definite available space
+    ///     shares a cache slot with max-content because a node will generally be sized under one or the other but not both).
+    ///
+    /// ## Cache slots:
+    ///
+    /// - Slot 0: Both known_dimensions were set
+    /// - Slot 1: width but not height known_dimension was set and the other dimension was either a MaxContent or Definite available space constraintraint
+    /// - Slot 2: width but not height known_dimension was set and the other dimension was a MinContent constraint
+    /// - Slot 3: height but not width known_dimension was set and the other dimension was either a MaxContent or Definite available space constraintable space constraint
+    /// - Slot 4: height but not width known_dimension was set and the other dimension was a MinContent constraint
+    /// - Slot 5: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
+    /// - Slot 6: Neither known_dimensions were set and we are sizing under a MinContent constraint
+    #[inline]
+    fn compute_cache_slot(known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>) -> usize {
+        let has_known_width = known_dimensions.width.is_some();
+        let has_known_height = known_dimensions.height.is_some();
+
+        // Slot 0: Both known_dimensions were set
+        if has_known_width && has_known_height {
+            return 0;
+        }
+
+        // Slot 1: width but not height known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
+        // Slot 2: width but not height known_dimension was set and the other dimension was a MinContent constraint
+        if has_known_width && !has_known_height {
+            return 1 + (available_space.height == AvailableSpace::MinContent) as usize;
+        }
+
+        // Slot 3: height but not width known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
+        // Slot 4: height but not width known_dimension was set and the other dimension was a MinContent constraint
+        if !has_known_width && has_known_height {
+            return 3 + (available_space.width == AvailableSpace::MinContent) as usize;
+        }
+
+        // Slot 5: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
+        // Slot 6: Neither known_dimensions were set and we are sizing under a MinContent constraint
+        5 + (available_space.width == AvailableSpace::MinContent) as usize
+    }
+
+    /// Try to retrieve a cached result from the cache
+    #[inline]
+    pub fn get(
+        &self,
+        known_dimensions: Size<Option<f32>>,
+        available_space: Size<AvailableSpace>,
+        run_mode: RunMode,
+    ) -> Option<SizeAndBaselines> {
+        for entry in self.entries.iter().flatten() {
+            // Cached ComputeSize results are not valid if we are running in PerformLayout mode
+            if entry.run_mode == RunMode::ComputeSize && run_mode == RunMode::PeformLayout {
+                return None;
+            }
+
+            let cached_size = entry.cached_size_and_baselines.size;
+
+            if (known_dimensions.width == entry.known_dimensions.width
+                || known_dimensions.width == Some(cached_size.width))
+                && (known_dimensions.height == entry.known_dimensions.height
+                    || known_dimensions.height == Some(cached_size.height))
+                && (known_dimensions.width.is_some()
+                    || entry.available_space.width.is_roughly_equal(available_space.width))
+                && (known_dimensions.height.is_some()
+                    || entry.available_space.height.is_roughly_equal(available_space.height))
+            {
+                return Some(entry.cached_size_and_baselines);
+            }
+        }
+
+        None
+    }
+
+    /// Store a computed size in the cache
+    pub fn store(
+        &mut self,
+        known_dimensions: Size<Option<f32>>,
+        available_space: Size<AvailableSpace>,
+        run_mode: RunMode,
+        cached_size_and_baselines: SizeAndBaselines,
+    ) {
+        let cache_slot = Self::compute_cache_slot(known_dimensions, available_space);
+        self.entries[cache_slot] =
+            Some(CacheEntry { known_dimensions, available_space, run_mode, cached_size_and_baselines });
+    }
+
+    /// Clear all cache entries
+    pub fn clear(&mut self) {
+        self.entries = [None; CACHE_SIZE];
+    }
+
+    /// Returns true if all cache entries are None, else false
+    pub fn is_empty(&self) -> bool {
+        !self.entries.iter().any(|entry| entry.is_some())
+    }
+}
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
index bb8fd5e3b..ca85b4187 100644
--- a/src/compute/mod.rs
+++ b/src/compute/mod.rs
@@ -9,10 +9,9 @@ pub(crate) mod flexbox;
 #[cfg(feature = "grid")]
 pub(crate) mod grid;
 
-use crate::data::CACHE_SIZE;
 use crate::error::TaffyError;
 use crate::geometry::{Point, Size};
-use crate::layout::{Cache, Layout, RunMode, SizeAndBaselines, SizingMode};
+use crate::layout::{Layout, RunMode, SizeAndBaselines, SizingMode};
 use crate::node::Taffy;
 use crate::style::{AvailableSpace, Display};
 use crate::sys::round;
@@ -126,7 +125,7 @@ fn compute_node_layout(
     // First we check if we have a cached result for the given input
     let cache_run_mode = if !has_children { RunMode::PeformLayout } else { run_mode };
     if let Some(cached_size_and_baselines) =
-        compute_from_cache(tree, node, known_dimensions, available_space, cache_run_mode)
+        tree.nodes[node_key].cache.get(known_dimensions, available_space, cache_run_mode)
     {
         #[cfg(feature = "debug")]
         NODE_LOGGER.labelled_debug_log("CACHE", cached_size_and_baselines.size);
@@ -206,13 +205,7 @@ fn compute_node_layout(
     };
 
     // Cache result
-    let cache_slot = compute_cache_slot(known_dimensions, available_space);
-    tree.nodes[node_key].size_cache[cache_slot] = Some(Cache {
-        known_dimensions,
-        available_space,
-        run_mode: cache_run_mode,
-        cached_size_and_baselines: computed_size_and_baselines,
-    });
+    tree.nodes[node_key].cache.store(known_dimensions, available_space, cache_run_mode, computed_size_and_baselines);
 
     #[cfg(feature = "debug")]
     NODE_LOGGER.labelled_debug_log("RESULT", computed_size_and_baselines.size);
@@ -237,92 +230,6 @@ fn debug_log_node(
     NODE_LOGGER.labelled_debug_log("available_space", available_space);
 }
 
-/// Return the cache slot to cache the current computed result in
-///
-/// ## Caching Strategy
-///
-/// We need multiple cache slots, because a node's size is often queried by it's parent multiple times in the course of the layout
-/// process, and we don't want later results to clobber earlier ones.
-///
-/// The two variables that we care about when determining cache slot are:
-///
-///   - How many "known_dimensions" are set. In the worst case, a node may be called first with neither dimensions known known_dimensions,
-///     then with one dimension known (either width of height - which doesn't matter for our purposes here), and then with both dimensions
-///     known.
-///   - Whether unknown dimensions are being sized under a min-content or a max-content available space constraint (definite available space
-///     shares a cache slot with max-content because a node will generally be sized under one or the other but not both).
-///
-/// ## Cache slots:
-///
-/// - Slot 0: Both known_dimensions were set
-/// - Slot 1: 1 of 2 known_dimensions were set and the other dimension was either a MaxContent or Definite available space constraint
-/// - Slot 2: 1 of 2 known_dimensions were set and the other dimension was a MinContent constraint
-/// - Slot 3: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
-/// - Slot 4: Neither known_dimensions were set and we are sizing under a MinContent constraint
-#[inline]
-fn compute_cache_slot(known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>) -> usize {
-    let has_known_width = known_dimensions.width.is_some();
-    let has_known_height = known_dimensions.height.is_some();
-
-    // Slot 0: Both known_dimensions were set
-    if has_known_width && has_known_height {
-        return 0;
-    }
-
-    // Slot 1: width but not height known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
-    // Slot 2: width but not height known_dimension was set and the other dimension was a MinContent constraint
-    if has_known_width && !has_known_height {
-        return 1 + (available_space.height == AvailableSpace::MinContent) as usize;
-    }
-
-    // Slot 3: height but not width known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
-    // Slot 4: height but not width known_dimension was set and the other dimension was a MinContent constraint
-    if !has_known_width && has_known_height {
-        return 3 + (available_space.width == AvailableSpace::MinContent) as usize;
-    }
-
-    // Slot 5: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
-    // Slot 6: Neither known_dimensions were set and we are sizing under a MinContent constraint
-    5 + (available_space.width == AvailableSpace::MinContent) as usize
-}
-
-/// Try to get the computation result from the cache.
-#[inline]
-fn compute_from_cache(
-    tree: &mut Taffy,
-    node: NodeId,
-    known_dimensions: Size<Option<f32>>,
-    available_space: Size<AvailableSpace>,
-    run_mode: RunMode,
-) -> Option<SizeAndBaselines> {
-    let node_key = node.into();
-    for idx in 0..CACHE_SIZE {
-        let entry = tree.nodes[node_key].size_cache[idx];
-        if let Some(entry) = entry {
-            // Cached ComputeSize results are not valid if we are running in PerformLayout mode
-            if entry.run_mode == RunMode::ComputeSize && run_mode == RunMode::PeformLayout {
-                return None;
-            }
-
-            let cached_size = entry.cached_size_and_baselines.size;
-
-            if (known_dimensions.width == entry.known_dimensions.width
-                || known_dimensions.width == Some(cached_size.width))
-                && (known_dimensions.height == entry.known_dimensions.height
-                    || known_dimensions.height == Some(cached_size.height))
-                && (known_dimensions.width.is_some()
-                    || entry.available_space.width.is_roughly_equal(available_space.width))
-                && (known_dimensions.height.is_some()
-                    || entry.available_space.height.is_roughly_equal(available_space.height))
-            {
-                return Some(entry.cached_size_and_baselines);
-            }
-        }
-    }
-
-    None
-}
-
 /// The public interface to Taffy's hidden node algorithm implementation
 struct HiddenAlgorithm;
 impl LayoutAlgorithm for HiddenAlgorithm {
diff --git a/src/data.rs b/src/data.rs
index 03a06c673..4cad00277 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -2,12 +2,10 @@
 //!
 //! Used to compute layout for Taffy trees
 //!
-use crate::layout::{Cache, Layout};
+use crate::cache::Cache;
+use crate::layout::Layout;
 use crate::style::Style;
 
-/// The number of cache entries for each node in the tree
-pub(crate) const CACHE_SIZE: usize = 7;
-
 /// Layout information for a given [`Node`](crate::node::Node)
 ///
 /// Stored in a [`Taffy`].
@@ -20,15 +18,15 @@ pub(crate) struct NodeData {
     /// Should we try and measure this node?
     pub(crate) needs_measure: bool,
 
-    /// The primary cached results of the layout computation
-    pub(crate) size_cache: [Option<Cache>; CACHE_SIZE],
+    /// The cached results of the layout computation
+    pub(crate) cache: Cache,
 }
 
 impl NodeData {
     /// Create the data for a new node
     #[must_use]
     pub const fn new(style: Style) -> Self {
-        Self { style, size_cache: [None; CACHE_SIZE], layout: Layout::new(), needs_measure: false }
+        Self { style, cache: Cache::new(), layout: Layout::new(), needs_measure: false }
     }
 
     /// Marks a node and all of its parents (recursively) as dirty
@@ -36,6 +34,6 @@ impl NodeData {
     /// This clears any cached data and signals that the data must be recomputed.
     #[inline]
     pub fn mark_dirty(&mut self) {
-        self.size_cache = [None; CACHE_SIZE];
+        self.cache.clear()
     }
 }
diff --git a/src/layout.rs b/src/layout.rs
index 937a97a8a..e4d655219 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -1,7 +1,6 @@
-//! Final and cached data structures that represent the high-level UI layout
+//! Final data structures that represent the high-level UI layout
 
 use crate::geometry::{Point, Size};
-use crate::style::AvailableSpace;
 
 /// Whether we are performing a full layout, or we merely need to size the node
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -76,17 +75,3 @@ impl Layout {
         Self { order, size: Size::zero(), location: Point::ZERO }
     }
 }
-
-/// Cached intermediate layout results
-#[derive(Debug, Clone, Copy)]
-pub struct Cache {
-    /// The initial cached size of the node itself
-    pub(crate) known_dimensions: Size<Option<f32>>,
-    /// The initial cached size of the parent's node
-    pub(crate) available_space: Size<AvailableSpace>,
-    /// Whether or not layout should be recomputed
-    pub(crate) run_mode: RunMode,
-
-    /// The cached size and baselines of the item
-    pub(crate) cached_size_and_baselines: SizeAndBaselines,
-}
diff --git a/src/lib.rs b/src/lib.rs
index cb0151a5b..47b99498f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -19,6 +19,7 @@ extern crate alloc;
 extern crate serde;
 
 pub mod axis;
+pub mod cache;
 #[cfg(feature = "std")]
 #[doc(hidden)]
 pub mod debug;
diff --git a/src/node.rs b/src/node.rs
index 1e68c1094..3af6c0e30 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -409,7 +409,7 @@ impl Taffy {
 
     /// Indicates whether the layout of this node (and its children) need to be recomputed
     pub fn dirty(&self, node: NodeId) -> TaffyResult<bool> {
-        Ok(self.nodes[node.into()].size_cache.iter().all(|entry| entry.is_none()))
+        Ok(self.nodes[node.into()].cache.is_empty())
     }
 
     /// Updates the stored layout of the provided `node` and its children