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
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