text_typeset/atlas/
cache.rs1use std::collections::HashMap;
2
3use etagere::AllocId;
4
5use crate::types::FontFaceId;
6
7#[derive(Clone, Copy, Eq, PartialEq, Hash)]
8pub struct GlyphCacheKey {
9 pub font_face_id: FontFaceId,
10 pub glyph_id: u16,
11 pub size_bits: u32,
12}
13
14impl GlyphCacheKey {
15 pub fn new(font_face_id: FontFaceId, glyph_id: u16, size_px: f32) -> Self {
16 Self {
17 font_face_id,
18 glyph_id,
19 size_bits: size_px.to_bits(),
20 }
21 }
22}
23
24pub struct CachedGlyph {
25 pub alloc_id: AllocId,
26 pub atlas_x: u32,
27 pub atlas_y: u32,
28 pub width: u32,
29 pub height: u32,
30 pub placement_left: i32,
31 pub placement_top: i32,
32 pub is_color: bool,
33 pub last_used: u64,
35}
36
37pub struct GlyphCache {
43 pub(crate) entries: HashMap<GlyphCacheKey, CachedGlyph>,
44 generation: u64,
45 last_eviction_generation: u64,
46}
47
48const MAX_IDLE_FRAMES: u64 = 120; impl Default for GlyphCache {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl GlyphCache {
58 pub fn new() -> Self {
59 Self {
60 entries: HashMap::new(),
61 generation: 0,
62 last_eviction_generation: 0,
63 }
64 }
65
66 pub fn advance_generation(&mut self) {
68 self.generation += 1;
69 }
70
71 pub fn generation(&self) -> u64 {
72 self.generation
73 }
74
75 pub fn get(&mut self, key: &GlyphCacheKey) -> Option<&CachedGlyph> {
77 if let Some(entry) = self.entries.get_mut(key) {
78 entry.last_used = self.generation;
79 Some(entry)
80 } else {
81 None
82 }
83 }
84
85 pub fn peek(&self, key: &GlyphCacheKey) -> Option<&CachedGlyph> {
87 self.entries.get(key)
88 }
89
90 pub fn insert(&mut self, key: GlyphCacheKey, mut glyph: CachedGlyph) {
91 glyph.last_used = self.generation;
92 self.entries.insert(key, glyph);
93 }
94
95 pub fn evict_unused(&mut self) -> Vec<AllocId> {
100 if self.generation - self.last_eviction_generation < 60 {
102 return Vec::new();
103 }
104 self.last_eviction_generation = self.generation;
105
106 let threshold = self.generation.saturating_sub(MAX_IDLE_FRAMES);
107 let mut evicted = Vec::new();
108
109 self.entries.retain(|_key, glyph| {
110 if glyph.last_used < threshold {
111 evicted.push(glyph.alloc_id);
112 false
113 } else {
114 true
115 }
116 });
117
118 evicted
119 }
120
121 pub fn len(&self) -> usize {
122 self.entries.len()
123 }
124
125 pub fn is_empty(&self) -> bool {
126 self.entries.is_empty()
127 }
128}