gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 238Markdown 7TOML 31453 Sutichaykuna

⎇ dc9862a93be77bb2af8f5b27f6efe6304f3126e1 src/tree/cache.rs Rust · 181 siq'ikuna · 8.6 KB Ñawpa willay Pi imata llamkarqan ch'uya

Fix clippy lints (2024-03) (#630) dc9862a93 · Nico Burns ·

1//! A cache for storing the results of layout computation
2use crate::geometry::Size;
3use crate::style::AvailableSpace;
4use crate::tree::{LayoutOutput, RunMode};
5
6/// The number of cache entries for each node in the tree
7const CACHE_SIZE: usize = 9;
8
9/// Cached intermediate layout results
10#[derive(Debug, Clone, Copy)]
11pub(crate) struct CacheEntry<T> {
12    /// The initial cached size of the node itself
13    known_dimensions: Size<Option<f32>>,
14    /// The initial cached size of the parent's node
15    available_space: Size<AvailableSpace>,
16    /// The cached size and baselines of the item
17    content: T,
18}
19
20/// A cache for caching the results of a sizing a Grid Item or Flexbox Item
21pub struct Cache {
22    /// The cache entry for the node's final layout
23    final_layout_entry: Option<CacheEntry<LayoutOutput>>,
24    /// The cache entries for the node's preliminary size measurements
25    measure_entries: [Option<CacheEntry<Size<f32>>>; CACHE_SIZE],
26}
27
28impl Default for Cache {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl Cache {
35    /// Create a new empty cache
36    pub const fn new() -> Self {
37        Self { final_layout_entry: None, measure_entries: [None; CACHE_SIZE] }
38    }
39
40    /// Return the cache slot to cache the current computed result in
41    ///
42    /// ## Caching Strategy
43    ///
44    /// 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
45    /// process, and we don't want later results to clobber earlier ones.
46    ///
47    /// The two variables that we care about when determining cache slot are:
48    ///
49    ///   - How many "known_dimensions" are set. In the worst case, a node may be called first with neither dimension known, then with one
50    ///     dimension known (either width of height - which doesn't matter for our purposes here), and then with both dimensions known.
51    ///   - Whether unknown dimensions are being sized under a min-content or a max-content available space constraint (definite available space
52    ///     shares a cache slot with max-content because a node will generally be sized under one or the other but not both).
53    ///
54    /// ## Cache slots:
55    ///
56    /// - Slot 0: Both known_dimensions were set
57    /// - Slots 1-4: 1 of 2 known_dimensions were set and:
58    ///   - Slot 1: width but not height known_dimension was set and the other dimension was either a MaxContent or Definite available space constraintraint
59    ///   - Slot 2: width but not height known_dimension was set and the other dimension was a MinContent constraint
60    ///   - 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
61    ///   - Slot 4: height but not width known_dimension was set and the other dimension was a MinContent constraint
62    /// - Slots 5-8: Neither known_dimensions were set and:
63    ///   - Slot 5: x-axis available space is MaxContent or Definite and y-axis available space is MaxContent or Definite
64    ///   - Slot 6: x-axis available space is MaxContent or Definite and y-axis available space is MinContent
65    ///   - Slot 7: x-axis available space is MinContent and y-axis available space is MaxContent or Definite
66    ///   - Slot 8: x-axis available space is MinContent and y-axis available space is MinContent
67    #[inline]
68    fn compute_cache_slot(known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>) -> usize {
69        use AvailableSpace::{Definite, MaxContent, MinContent};
70
71        let has_known_width = known_dimensions.width.is_some();
72        let has_known_height = known_dimensions.height.is_some();
73
74        // Slot 0: Both known_dimensions were set
75        if has_known_width && has_known_height {
76            return 0;
77        }
78
79        // Slot 1: width but not height known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
80        // Slot 2: width but not height known_dimension was set and the other dimension was a MinContent constraint
81        if has_known_width && !has_known_height {
82            return 1 + (available_space.height == MinContent) as usize;
83        }
84
85        // Slot 3: height but not width known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
86        // Slot 4: height but not width known_dimension was set and the other dimension was a MinContent constraint
87        if has_known_height && !has_known_width {
88            return 3 + (available_space.width == MinContent) as usize;
89        }
90
91        // Slots 5-8: Neither known_dimensions were set and:
92        match (available_space.width, available_space.height) {
93            // Slot 5: x-axis available space is MaxContent or Definite and y-axis available space is MaxContent or Definite
94            (MaxContent | Definite(_), MaxContent | Definite(_)) => 5,
95            // Slot 6: x-axis available space is MaxContent or Definite and y-axis available space is MinContent
96            (MaxContent | Definite(_), MinContent) => 6,
97            // Slot 7: x-axis available space is MinContent and y-axis available space is MaxContent or Definite
98            (MinContent, MaxContent | Definite(_)) => 7,
99            // Slot 8: x-axis available space is MinContent and y-axis available space is MinContent
100            (MinContent, MinContent) => 8,
101        }
102    }
103
104    /// Try to retrieve a cached result from the cache
105    #[inline]
106    pub fn get(
107        &self,
108        known_dimensions: Size<Option<f32>>,
109        available_space: Size<AvailableSpace>,
110        run_mode: RunMode,
111    ) -> Option<LayoutOutput> {
112        match run_mode {
113            RunMode::PerformLayout => self
114                .final_layout_entry
115                .filter(|entry| {
116                    let cached_size = entry.content.size;
117                    (known_dimensions.width == entry.known_dimensions.width
118                        || known_dimensions.width == Some(cached_size.width))
119                        && (known_dimensions.height == entry.known_dimensions.height
120                            || known_dimensions.height == Some(cached_size.height))
121                        && (known_dimensions.width.is_some()
122                            || entry.available_space.width.is_roughly_equal(available_space.width))
123                        && (known_dimensions.height.is_some()
124                            || entry.available_space.height.is_roughly_equal(available_space.height))
125                })
126                .map(|e| e.content),
127            RunMode::ComputeSize => {
128                for entry in self.measure_entries.iter().flatten() {
129                    let cached_size = entry.content;
130
131                    if (known_dimensions.width == entry.known_dimensions.width
132                        || known_dimensions.width == Some(cached_size.width))
133                        && (known_dimensions.height == entry.known_dimensions.height
134                            || known_dimensions.height == Some(cached_size.height))
135                        && (known_dimensions.width.is_some()
136                            || entry.available_space.width.is_roughly_equal(available_space.width))
137                        && (known_dimensions.height.is_some()
138                            || entry.available_space.height.is_roughly_equal(available_space.height))
139                    {
140                        return Some(LayoutOutput::from_outer_size(cached_size));
141                    }
142                }
143
144                None
145            }
146            RunMode::PerformHiddenLayout => None,
147        }
148    }
149
150    /// Store a computed size in the cache
151    pub fn store(
152        &mut self,
153        known_dimensions: Size<Option<f32>>,
154        available_space: Size<AvailableSpace>,
155        run_mode: RunMode,
156        layout_output: LayoutOutput,
157    ) {
158        match run_mode {
159            RunMode::PerformLayout => {
160                self.final_layout_entry = Some(CacheEntry { known_dimensions, available_space, content: layout_output })
161            }
162            RunMode::ComputeSize => {
163                let cache_slot = Self::compute_cache_slot(known_dimensions, available_space);
164                self.measure_entries[cache_slot] =
165                    Some(CacheEntry { known_dimensions, available_space, content: layout_output.size });
166            }
167            RunMode::PerformHiddenLayout => {}
168        }
169    }
170
171    /// Clear all cache entries
172    pub fn clear(&mut self) {
173        self.final_layout_entry = None;
174        self.measure_entries = [None; CACHE_SIZE];
175    }
176
177    /// Returns true if all cache entries are None, else false
178    pub fn is_empty(&self) -> bool {
179        self.final_layout_entry.is_none() && !self.measure_entries.iter().any(|entry| entry.is_some())
180    }
181}