use crate::font::text_run_cache::{
create_cached_text_run, create_shaping_key, create_text_run_key, CacheHitType,
ShapedGlyph, TextDirection, TextRunCache,
};
use std::sync::Arc;
use tracing::debug;
const COMMON_TERMINAL_PATTERNS: &[&str] = &[
" ", " ", " ", " ", "\t", "\t\t", "\t\t\t", "const ",
"let ",
"var ",
"function ",
"class ",
"import ",
"export ",
"return ",
"if ",
"else ",
"for ",
"while ",
"switch ",
"case ",
"break ",
"continue ",
"try ",
"catch ",
"finally ",
"async ",
"await ",
"public ",
"private ",
"protected ",
"static ",
"void ",
"int ",
"string ",
"bool ",
"true",
"false",
"null",
"undefined",
" = ",
" == ",
" === ",
" != ",
" !== ",
" <= ",
" >= ",
" && ",
" || ",
" => ",
" -> ",
"();",
"{}",
"[]",
"();",
"{};",
"[];",
"$ ",
"# ",
"> ",
"~ ",
"├── ",
"└── ",
"│ ",
"ls ",
"cd ",
"pwd",
"cat ",
"grep ",
"find ",
"git ",
"npm ",
"cargo ",
"make ",
"sudo ",
".js",
".ts",
".rs",
".py",
".go",
".cpp",
".c",
".h",
".json",
".xml",
".html",
".css",
".md",
".txt",
".log",
"/usr/",
"/home/",
"/var/",
"/etc/",
"./",
"../",
"Error: ",
"Warning: ",
"Info: ",
"Debug: ",
"[ERROR]",
"[WARN]",
"[INFO]",
"[DEBUG]",
"FAILED",
"SUCCESS",
"OK",
"0",
"1",
"2",
"3",
"4",
"5",
"10",
"100",
"1000",
"0.0",
"1.0",
"-1",
"...",
"---",
"===",
"***",
"+++",
">>>",
"<<<",
];
pub struct TextRunManager {
unified_cache: TextRunCache,
total_requests: u64,
full_render_hits: u64,
shaping_hits: u64,
glyph_hits: u64,
cache_misses: u64,
}
impl TextRunManager {
pub fn new() -> Self {
Self {
unified_cache: TextRunCache::new(),
total_requests: 0,
full_render_hits: 0,
shaping_hits: 0,
glyph_hits: 0,
cache_misses: 0,
}
}
#[allow(clippy::too_many_arguments)]
pub fn warm_cache(
&mut self,
font_id: usize,
font_size: f32,
font_weight: u16,
font_style: u8,
font_stretch: u8,
default_color: [f32; 4],
) {
debug!(
"Warming text run cache with {} common patterns",
COMMON_TERMINAL_PATTERNS.len()
);
let mut warmed_count = 0;
for &pattern in COMMON_TERMINAL_PATTERNS {
let key = create_text_run_key(
pattern,
font_weight,
font_style,
font_stretch,
font_size,
0, TextDirection::LeftToRight,
Some(default_color),
);
let cached_run = create_cached_text_run(
vec![], font_id,
font_size,
false, None, None, None, Some(default_color),
);
self.unified_cache.insert(key, cached_run);
warmed_count += 1;
}
debug!(
"Cache warming completed: {} patterns pre-cached",
warmed_count
);
}
#[allow(clippy::too_many_arguments)]
pub fn get_cached_data(
&mut self,
text: &str,
_font_id: usize,
font_size: f32,
font_weight: u16,
font_style: u8,
font_stretch: u8,
color: Option<[f32; 4]>,
) -> CacheResult {
self.total_requests += 1;
let key = create_text_run_key(
text,
font_weight,
font_style,
font_stretch,
font_size,
0, TextDirection::LeftToRight,
color,
);
match self.unified_cache.get(&key) {
Some(CacheHitType::FullRender(cached_run)) => {
self.full_render_hits += 1;
CacheResult::FullRender {
glyphs: cached_run.glyphs.clone(),
vertices: cached_run.vertices.clone().unwrap(),
base_position: cached_run.base_position.unwrap(),
advance_width: cached_run.advance_width,
has_emoji: cached_run.has_emoji,
font_id: cached_run.font_id,
}
}
Some(CacheHitType::ShapingOnly(cached_run)) => {
self.shaping_hits += 1;
CacheResult::ShapingOnly {
glyphs: cached_run.glyphs.clone(),
shaping_features: cached_run.shaping_features.clone(),
advance_width: cached_run.advance_width,
has_emoji: cached_run.has_emoji,
font_id: cached_run.font_id,
}
}
Some(CacheHitType::GlyphsOnly(cached_run)) => {
self.glyph_hits += 1;
CacheResult::GlyphsOnly {
glyphs: cached_run.glyphs.clone(),
advance_width: cached_run.advance_width,
has_emoji: cached_run.has_emoji,
font_id: cached_run.font_id,
}
}
None => {
self.cache_misses += 1;
CacheResult::Miss
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn cache_shaping_data(
&mut self,
text: &str,
font_id: usize,
font_size: f32,
font_weight: u16,
font_style: u8,
font_stretch: u8,
glyphs: Vec<ShapedGlyph>,
has_emoji: bool,
shaping_features: Option<Vec<u8>>,
) {
let key = create_shaping_key(
text,
font_weight,
font_style,
font_stretch,
font_size,
0, TextDirection::LeftToRight,
);
let cached_run = create_cached_text_run(
glyphs,
font_id,
font_size,
has_emoji,
shaping_features,
None, None, None, );
self.unified_cache.insert(key, cached_run);
}
pub fn apply_cached_vertices(
vertices_data: &[u8],
base_position: (f32, f32),
new_position: (f32, f32),
output_vertices: &mut Vec<u8>,
) {
let dx = new_position.0 - base_position.0;
let dy = new_position.1 - base_position.1;
output_vertices.extend_from_slice(vertices_data);
debug!("Applied cached vertices with offset ({}, {})", dx, dy);
}
pub fn clear_all(&mut self) {
self.unified_cache.clear();
debug!("TextRunManager: Cleared unified cache due to font change");
}
pub fn stats(&self) -> TextRunManagerStats {
let (
items,
total_hits,
total_misses,
hit_rate,
vertex_hits,
vertex_misses,
shaping_hits,
shaping_misses,
) = self.unified_cache.stats();
TextRunManagerStats {
total_requests: self.total_requests,
cache_items: items,
total_hits,
total_misses,
overall_hit_rate: hit_rate,
full_render_hits: self.full_render_hits,
shaping_hits: self.shaping_hits,
glyph_hits: self.glyph_hits,
cache_misses: self.cache_misses,
vertex_cache_hits: vertex_hits,
vertex_cache_misses: vertex_misses,
shaping_cache_hits: shaping_hits,
shaping_cache_misses: shaping_misses,
}
}
pub fn maintenance(&mut self) {
if self.unified_cache.needs_cleanup() {
self.unified_cache.cleanup();
}
if self.total_requests % 1000 == 0 && self.total_requests > 0 {
let stats = self.stats();
debug!(
"UnifiedTextRunManager stats: {:.1}% hit rate ({} requests), Full: {}, Shaping: {}, Glyphs: {}, Miss: {}, {} items",
stats.overall_hit_rate, stats.total_requests, stats.full_render_hits,
stats.shaping_hits, stats.glyph_hits, stats.cache_misses, stats.cache_items
);
}
}
pub fn needs_cleanup(&self) -> bool {
self.unified_cache.needs_cleanup()
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub enum CacheResult {
FullRender {
glyphs: Arc<Vec<ShapedGlyph>>,
vertices: Arc<Vec<u8>>,
base_position: (f32, f32),
advance_width: f32,
has_emoji: bool,
font_id: usize,
},
ShapingOnly {
glyphs: Arc<Vec<ShapedGlyph>>,
shaping_features: Option<Arc<Vec<u8>>>,
advance_width: f32,
has_emoji: bool,
font_id: usize,
},
GlyphsOnly {
glyphs: Arc<Vec<ShapedGlyph>>,
advance_width: f32,
has_emoji: bool,
font_id: usize,
},
Miss,
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct TextRunManagerStats {
pub total_requests: u64,
pub cache_items: usize,
pub total_hits: u64,
pub total_misses: u64,
pub overall_hit_rate: f64,
pub full_render_hits: u64,
pub shaping_hits: u64,
pub glyph_hits: u64,
pub cache_misses: u64,
pub vertex_cache_hits: u64,
pub vertex_cache_misses: u64,
pub shaping_cache_hits: u64,
pub shaping_cache_misses: u64,
}
impl Default for TextRunManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vertex_positioning() {
let vertices = vec![1, 2, 3, 4]; let mut output_vertices = Vec::new();
TextRunManager::apply_cached_vertices(
&vertices,
(100.0, 200.0),
(150.0, 250.0),
&mut output_vertices,
);
assert_eq!(output_vertices, vertices);
}
}