Skip to main content

fret_ui/
cache_key.rs

1use fret_core::{Corners, Px, Rect, TextStyle};
2use std::collections::hash_map::DefaultHasher;
3use std::hash::Hash;
4use std::hash::Hasher;
5
6/// Utility helpers for building view-cache cache keys.
7///
8/// The view-cache boundary (`ElementContext::view_cache`) relies on a deterministic `u64` key to
9/// decide whether cached output can be reused. Callers should include any inputs that affect
10/// rendered output but may not otherwise force a re-render (e.g. text style, clip/mask geometry,
11/// view-specific configuration).
12pub struct CacheKeyBuilder {
13    hasher: DefaultHasher,
14}
15
16impl CacheKeyBuilder {
17    pub fn new() -> Self {
18        Self {
19            hasher: DefaultHasher::new(),
20        }
21    }
22
23    pub fn write_u64(&mut self, value: u64) {
24        self.hasher.write_u64(value);
25    }
26
27    pub fn write_u32(&mut self, value: u32) {
28        self.hasher.write_u32(value);
29    }
30
31    pub fn write_bool(&mut self, value: bool) {
32        self.hasher.write_u8(if value { 1 } else { 0 });
33    }
34
35    pub fn write_px(&mut self, value: Px) {
36        self.hasher.write_u32(value.0.to_bits());
37    }
38
39    pub fn write_rect(&mut self, rect: Rect) {
40        self.write_px(rect.origin.x);
41        self.write_px(rect.origin.y);
42        self.write_px(rect.size.width);
43        self.write_px(rect.size.height);
44    }
45
46    pub fn write_corners(&mut self, corners: Corners) {
47        self.write_px(corners.top_left);
48        self.write_px(corners.top_right);
49        self.write_px(corners.bottom_right);
50        self.write_px(corners.bottom_left);
51    }
52
53    pub fn write_text_style(&mut self, style: &TextStyle) {
54        // Note: `TextStyle` does not implement `Hash` directly. We intentionally hash the style's
55        // fields in a stable, bitwise way.
56        style.font.hash(&mut self.hasher);
57        style.weight.hash(&mut self.hasher);
58        style.slant.hash(&mut self.hasher);
59
60        self.hasher.write_u32(style.size.0.to_bits());
61        self.hasher
62            .write_u32(style.line_height.map(|h| h.0.to_bits()).unwrap_or(0));
63        self.hasher
64            .write_u32(style.letter_spacing_em.map(f32::to_bits).unwrap_or(0));
65    }
66
67    pub fn finish(self) -> u64 {
68        self.hasher.finish()
69    }
70}
71
72impl Default for CacheKeyBuilder {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78pub fn mix(seed: u64, value: u64) -> u64 {
79    let mut b = CacheKeyBuilder::new();
80    b.write_u64(seed);
81    b.write_u64(value);
82    b.finish()
83}
84
85pub fn rect_key(rect: Rect) -> u64 {
86    let mut b = CacheKeyBuilder::new();
87    b.write_rect(rect);
88    b.finish()
89}
90
91pub fn corners_key(corners: Corners) -> u64 {
92    let mut b = CacheKeyBuilder::new();
93    b.write_corners(corners);
94    b.finish()
95}
96
97pub fn text_style_key(style: &TextStyle) -> u64 {
98    let mut b = CacheKeyBuilder::new();
99    b.write_text_style(style);
100    b.finish()
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use fret_core::FontId;
107    use fret_core::text::FontWeight;
108    use fret_core::text::TextSlant;
109
110    #[test]
111    fn text_style_key_changes_when_style_changes() {
112        let style = TextStyle {
113            font: FontId::default(),
114            size: Px(13.0),
115            weight: FontWeight::NORMAL,
116            slant: TextSlant::Normal,
117            line_height: None,
118            line_height_em: None,
119            line_height_policy: Default::default(),
120            letter_spacing_em: None,
121            features: Vec::new(),
122            axes: Vec::new(),
123            vertical_placement: fret_core::TextVerticalPlacement::CenterMetricsBox,
124            leading_distribution: Default::default(),
125            strut_style: None,
126        };
127        let a = text_style_key(&style);
128        let b = text_style_key(&TextStyle {
129            size: Px(14.0),
130            ..style.clone()
131        });
132        assert_ne!(a, b);
133    }
134
135    #[test]
136    fn mix_is_stable_for_same_inputs() {
137        assert_eq!(mix(1, 2), mix(1, 2));
138        assert_ne!(mix(1, 2), mix(2, 1));
139    }
140}