1#![allow(clippy::unusual_byte_groupings)]
4
5use crate::geometry::Size;
6use crate::style::AvailableSpace;
7use crate::tree::{CollapsibleMarginSet, LayoutInput, LayoutOutput, RunMode};
8use crate::RequestedAxis;
9
10const CACHE_SIZE: usize = 9;
12
13const INFINITY_BITS: u32 = 0b_0_11111111_00000000000000000000000_u32;
18const NEG_INFINITY_BITS: u32 = 0b_1_11111111_00000000000000000000000_u32;
20
21const SIGN_BIT_1: u64 = 1u64 << 63;
27const SIGN_BIT_2: u64 = 1u64 << 31;
29const BOTH_SIGN_BITS_MASK: u64 = SIGN_BIT_1 | SIGN_BIT_2;
31const NON_SIGN_BITS_MASK: u64 = !BOTH_SIGN_BITS_MASK;
33
34const X_AXIS_VALUE_MASK: u64 = (u32::MAX as u64) << 32;
37
38#[inline(always)]
40fn option_cache_key(input: Option<f32>) -> u32 {
41 match input {
42 Some(value) => value.to_bits(),
43 None => INFINITY_BITS,
44 }
45}
46
47#[inline(always)]
49fn size_option_cache_key(input: Size<Option<f32>>) -> u64 {
50 (option_cache_key(input.width) as u64) << 32 | option_cache_key(input.height) as u64
51}
52
53#[inline(always)]
55fn available_space_cache_key(input: AvailableSpace) -> u32 {
56 match input {
57 AvailableSpace::Definite(value) => (-value).to_bits(),
58 AvailableSpace::MinContent => NEG_INFINITY_BITS,
59 AvailableSpace::MaxContent => INFINITY_BITS,
60 }
61}
62
63#[inline(always)]
65#[allow(dead_code)]
66fn size_available_space_cache_key(input: Size<AvailableSpace>) -> u64 {
67 (available_space_cache_key(input.width) as u64) << 32 | available_space_cache_key(input.height) as u64
68}
69
70#[inline(always)]
73fn mixed_cache_key(kd: Option<f32>, avs: AvailableSpace) -> u32 {
74 kd.map(|kd| kd.to_bits()).unwrap_or_else(|| available_space_cache_key(avs))
75}
76
77#[inline(always)]
80fn size_mixed_cache_key(kd: Size<Option<f32>>, avs: Size<AvailableSpace>) -> u64 {
81 (mixed_cache_key(kd.width, avs.width) as u64) << 32 | mixed_cache_key(kd.height, avs.height) as u64
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[cfg_attr(feature = "serde", derive(Serialize))]
87struct CacheKey {
88 kd_available_space: u64,
90 parent_size: u64,
92 known_dimensions_are_definite: Size<bool>,
95}
96
97impl CacheKey {
98 #[inline(always)]
99 #[allow(dead_code)]
100 fn parent_size(&self) -> u64 {
102 self.parent_size & NON_SIGN_BITS_MASK
103 }
104
105 fn x_axis_parent_size(&self) -> u64 {
108 self.parent_size & (X_AXIS_VALUE_MASK & NON_SIGN_BITS_MASK)
109 }
110
111 fn requested_axis_bits(&self) -> u64 {
113 self.parent_size & BOTH_SIGN_BITS_MASK
114 }
115
116 fn size_is_valid_for(&self, other: &CacheKey) -> bool {
120 let entry_axis = self.requested_axis_bits();
121 entry_axis == BOTH_SIGN_BITS_MASK || entry_axis == other.requested_axis_bits()
122 }
123}
124
125impl From<&LayoutInput> for CacheKey {
126 fn from(input: &LayoutInput) -> Self {
127 let extra_bits = match input.axis {
129 RequestedAxis::Horizontal => SIGN_BIT_1,
130 RequestedAxis::Vertical => SIGN_BIT_2,
131 RequestedAxis::Both => SIGN_BIT_1 | SIGN_BIT_2,
132 };
133
134 Self {
135 kd_available_space: size_mixed_cache_key(input.known_dimensions, input.available_space),
136 parent_size: (size_option_cache_key(input.parent_size) & NON_SIGN_BITS_MASK) | extra_bits,
137 known_dimensions_are_definite: input
138 .known_dimensions_are_definite
139 .zip_map(input.known_dimensions, |is_definite, kd| is_definite || kd.is_none()),
140 }
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq)]
146#[cfg_attr(feature = "serde", derive(Serialize))]
147pub(crate) struct CacheEntry<T> {
148 key: CacheKey,
150 content: T,
152}
153
154#[derive(Debug, Clone, PartialEq)]
156#[cfg_attr(feature = "serde", derive(Serialize))]
157pub struct Cache {
158 final_layout_entry: Option<CacheEntry<LayoutOutput>>,
160 measure_entries: [Option<CacheEntry<Size<f32>>>; CACHE_SIZE],
162 recently_used_entries: u16,
164 next_measure_entry: u8,
166 is_empty: bool,
168}
169
170impl Default for Cache {
171 fn default() -> Self {
172 Self::new()
173 }
174}
175
176impl Cache {
177 pub const fn new() -> Self {
179 Self {
180 final_layout_entry: None,
181 measure_entries: [None; CACHE_SIZE],
182 recently_used_entries: 0,
183 next_measure_entry: 0,
184 is_empty: true,
185 }
186 }
187
188 #[inline]
190 pub fn get(&mut self, input: &LayoutInput) -> Option<LayoutOutput> {
191 let key = CacheKey::from(input);
192 match input.run_mode {
193 RunMode::PerformLayout => self.final_layout_entry.filter(|entry| entry.key == key).map(|e| e.content),
194 RunMode::ComputeSize => {
195 for (index, entry) in self.measure_entries.iter().enumerate() {
196 let Some(entry) = entry else { continue };
197 if entry.key.kd_available_space == key.kd_available_space
198 && entry.key.known_dimensions_are_definite == key.known_dimensions_are_definite
199 && (entry.key.x_axis_parent_size() == key.x_axis_parent_size())
200 && entry.key.size_is_valid_for(&key)
201 {
202 self.recently_used_entries |= 1 << index;
203 return Some(LayoutOutput::from_outer_size(entry.content));
204 }
205 }
206
207 None
208 }
209 RunMode::PerformHiddenLayout => None,
210 }
211 }
212
213 pub fn store(&mut self, input: &LayoutInput, layout_output: LayoutOutput) {
215 let key = CacheKey::from(input);
216 match input.run_mode {
217 RunMode::PerformLayout => {
218 self.is_empty = false;
219 self.final_layout_entry = Some(CacheEntry { key, content: layout_output })
220 }
221 RunMode::ComputeSize => {
222 if layout_output.margins_can_collapse_through
227 || layout_output.top_margin != CollapsibleMarginSet::ZERO
228 || layout_output.bottom_margin != CollapsibleMarginSet::ZERO
229 {
230 return;
231 }
232 self.is_empty = false;
233 if let Some(index) =
234 self.measure_entries.iter().position(|entry| entry.is_some_and(|entry| entry.key == key))
235 {
236 self.measure_entries[index].as_mut().unwrap().content = layout_output.size;
237 self.recently_used_entries |= 1 << index;
238 return;
239 }
240 while self.recently_used_entries & (1 << self.next_measure_entry) != 0 {
241 self.recently_used_entries &= !(1 << self.next_measure_entry);
242 self.next_measure_entry += 1;
243 if self.next_measure_entry == CACHE_SIZE as u8 {
244 self.next_measure_entry = 0;
245 }
246 }
247 let entry_index = self.next_measure_entry as usize;
248 self.measure_entries[entry_index] = Some(CacheEntry { key, content: layout_output.size });
249 self.recently_used_entries |= 1 << entry_index;
250 self.next_measure_entry += 1;
251 if self.next_measure_entry == CACHE_SIZE as u8 {
252 self.next_measure_entry = 0;
253 }
254 }
255 RunMode::PerformHiddenLayout => {}
256 }
257 }
258
259 pub fn clear(&mut self) -> ClearState {
261 if self.is_empty {
262 return ClearState::AlreadyEmpty;
263 }
264 self.is_empty = true;
265 self.final_layout_entry = None;
266 self.measure_entries = [None; CACHE_SIZE];
267 self.recently_used_entries = 0;
268 self.next_measure_entry = 0;
269 ClearState::Cleared
270 }
271
272 pub fn is_empty(&self) -> bool {
274 self.final_layout_entry.is_none() && !self.measure_entries.iter().any(|entry| entry.is_some())
275 }
276}
277
278pub enum ClearState {
280 Cleared,
282 AlreadyEmpty,
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::geometry::Line;
290 use crate::tree::SizingMode;
291
292 fn input(width: f32) -> LayoutInput {
293 LayoutInput {
294 run_mode: RunMode::ComputeSize,
295 sizing_mode: SizingMode::InherentSize,
296 axis: RequestedAxis::Both,
297 known_dimensions: Size { width: Some(width), height: None },
298 known_dimensions_are_definite: Size { width: true, height: true },
299 parent_size: Size::NONE,
300 available_space: Size { width: AvailableSpace::MaxContent, height: AvailableSpace::MaxContent },
301 vertical_margins_are_collapsible: Line::FALSE,
302 }
303 }
304
305 fn output(width: f32) -> LayoutOutput {
306 LayoutOutput::from_outer_size(Size { width, height: width })
307 }
308
309 #[test]
310 fn recently_used_measure_entries_get_a_second_chance() {
311 let mut cache = Cache::new();
312 for width in 0..CACHE_SIZE {
313 cache.store(&input(width as f32), output(width as f32));
314 }
315 cache.store(&input(CACHE_SIZE as f32), output(CACHE_SIZE as f32));
316
317 assert_eq!(cache.get(&input(1.0)), Some(output(1.0)));
318 cache.store(&input((CACHE_SIZE + 1) as f32), output((CACHE_SIZE + 1) as f32));
319
320 assert_eq!(cache.get(&input(1.0)), Some(output(1.0)));
321 assert_eq!(cache.get(&input(2.0)), None);
322 }
323
324 #[test]
325 fn storing_an_existing_measurement_updates_it_in_place() {
326 let mut cache = Cache::new();
327 cache.store(&input(1.0), output(1.0));
328 cache.store(&input(2.0), output(2.0));
329 cache.store(&input(1.0), output(3.0));
330
331 assert_eq!(cache.measure_entries.iter().flatten().count(), 2);
332 assert_eq!(cache.get(&input(1.0)), Some(output(3.0)));
333 }
334
335 #[test]
336 fn measurements_with_margin_collapse_metadata_are_not_cached() {
337 let mut cache = Cache::new();
338
339 let mut collapse_through = output(1.0);
340 collapse_through.margins_can_collapse_through = true;
341 cache.store(&input(1.0), collapse_through);
342 assert_eq!(cache.get(&input(1.0)), None);
343
344 let mut carried_margin = output(2.0);
345 carried_margin.top_margin = CollapsibleMarginSet::from_margin(10.0);
346 cache.store(&input(2.0), carried_margin);
347 assert_eq!(cache.get(&input(2.0)), None);
348 }
349
350 #[test]
351 fn retrieving_a_measurement_only_marks_its_slot_as_used() {
352 let mut cache = Cache::new();
353 cache.store(&input(1.0), output(1.0));
354 cache.store(&input(2.0), output(2.0));
355 cache.recently_used_entries = 0;
356 let entries = cache.measure_entries;
357
358 assert_eq!(cache.get(&input(1.0)), Some(output(1.0)));
359 assert_eq!(cache.measure_entries, entries);
360 assert_ne!(cache.recently_used_entries, 0);
361 }
362}