ass_renderer/cache/
mod.rs1use crate::pipeline::shaping::ShapedText;
4use tiny_skia::Path;
5
6#[cfg(not(feature = "nostd"))]
7use std::collections::HashMap;
8#[cfg(not(feature = "nostd"))]
9use std::sync::Arc;
10
11#[cfg(feature = "nostd")]
12use alloc::collections::BTreeMap as HashMap;
13#[cfg(feature = "nostd")]
14use alloc::{string::String, sync::Arc};
15
16#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
18pub struct TextCacheKey {
19 pub text: String,
21 pub font_family: String,
23 pub font_size: u32,
25 pub bold: bool,
27 pub italic: bool,
29}
30
31#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
33pub struct DrawingCacheKey {
34 pub commands: String,
36}
37
38pub struct RenderCache {
40 shaped_text_cache: HashMap<TextCacheKey, Arc<ShapedText>>,
42 max_shaped_entries: usize,
43
44 drawing_path_cache: HashMap<DrawingCacheKey, Option<Path>>,
46 max_drawing_entries: usize,
47
48 pub stats: CacheStats,
50}
51
52#[derive(Debug, Default, Clone)]
54pub struct CacheStats {
55 pub text_hits: usize,
57 pub text_misses: usize,
59 pub drawing_hits: usize,
61 pub drawing_misses: usize,
63 pub evictions: usize,
65}
66
67impl RenderCache {
68 pub fn new() -> Self {
70 Self {
71 shaped_text_cache: HashMap::new(),
72 max_shaped_entries: 1000,
73 drawing_path_cache: HashMap::new(),
74 max_drawing_entries: 500,
75 stats: CacheStats::default(),
76 }
77 }
78
79 pub fn with_limits(max_shaped: usize, max_drawing: usize) -> Self {
81 Self {
82 shaped_text_cache: HashMap::new(),
83 max_shaped_entries: max_shaped,
84 drawing_path_cache: HashMap::new(),
85 max_drawing_entries: max_drawing,
86 stats: CacheStats::default(),
87 }
88 }
89
90 pub fn get_shaped_text(&mut self, key: &TextCacheKey) -> Option<Arc<ShapedText>> {
92 if let Some(shaped) = self.shaped_text_cache.get(key) {
93 self.stats.text_hits += 1;
94 Some(Arc::clone(shaped))
95 } else {
96 self.stats.text_misses += 1;
97 None
98 }
99 }
100
101 pub fn store_shaped_text(&mut self, key: TextCacheKey, shaped: ShapedText) -> Arc<ShapedText> {
103 if self.shaped_text_cache.len() >= self.max_shaped_entries {
105 if let Some(first_key) = self.shaped_text_cache.keys().next().cloned() {
107 self.shaped_text_cache.remove(&first_key);
108 self.stats.evictions += 1;
109 }
110 }
111
112 let arc_shaped = Arc::new(shaped);
113 self.shaped_text_cache.insert(key, Arc::clone(&arc_shaped));
114 arc_shaped
115 }
116
117 pub fn get_drawing_path(&mut self, key: &DrawingCacheKey) -> Option<Option<Path>> {
119 if let Some(path) = self.drawing_path_cache.get(key) {
120 self.stats.drawing_hits += 1;
121 Some(path.clone())
122 } else {
123 self.stats.drawing_misses += 1;
124 None
125 }
126 }
127
128 pub fn store_drawing_path(&mut self, key: DrawingCacheKey, path: Option<Path>) {
130 if self.drawing_path_cache.len() >= self.max_drawing_entries {
132 if let Some(first_key) = self.drawing_path_cache.keys().next().cloned() {
133 self.drawing_path_cache.remove(&first_key);
134 self.stats.evictions += 1;
135 }
136 }
137
138 self.drawing_path_cache.insert(key, path);
139 }
140
141 pub fn clear(&mut self) {
143 self.shaped_text_cache.clear();
144 self.drawing_path_cache.clear();
145 self.stats = CacheStats::default();
146 }
147
148 pub fn stats(&self) -> &CacheStats {
150 &self.stats
151 }
152
153 pub fn print_stats(&self) {
155 #[cfg(not(feature = "nostd"))]
156 let text_ratio = if self.stats.text_hits + self.stats.text_misses > 0 {
157 self.stats.text_hits as f64 / (self.stats.text_hits + self.stats.text_misses) as f64
158 } else {
159 0.0
160 };
161
162 #[cfg(not(feature = "nostd"))]
163 let drawing_ratio = if self.stats.drawing_hits + self.stats.drawing_misses > 0 {
164 self.stats.drawing_hits as f64
165 / (self.stats.drawing_hits + self.stats.drawing_misses) as f64
166 } else {
167 0.0
168 };
169
170 #[cfg(not(feature = "nostd"))]
171 eprintln!("=== Cache Statistics ===");
172 #[cfg(not(feature = "nostd"))]
173 eprintln!(
174 "Text Cache: {} entries, {:.1}% hit rate ({}/{} hits)",
175 self.shaped_text_cache.len(),
176 text_ratio * 100.0,
177 self.stats.text_hits,
178 self.stats.text_hits + self.stats.text_misses
179 );
180 #[cfg(not(feature = "nostd"))]
181 eprintln!(
182 "Drawing Cache: {} entries, {:.1}% hit rate ({}/{} hits)",
183 self.drawing_path_cache.len(),
184 drawing_ratio * 100.0,
185 self.stats.drawing_hits,
186 self.stats.drawing_hits + self.stats.drawing_misses
187 );
188 #[cfg(not(feature = "nostd"))]
189 eprintln!("Total Evictions: {}", self.stats.evictions);
190 }
191}
192
193impl Default for RenderCache {
194 fn default() -> Self {
195 Self::new()
196 }
197}