tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 229TOML 101Markdown 82Shell 24052 Sutichaykuna

⎇ main llimphi-text/examples/bench_cache.rs Rust · 95 siq'ikuna · 3.9 KB Ñawpa willay Pi imata llamkarqan ch'uya

refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel ccab39f14 · Sergio ·

1//! Evidencia del caché de shaping: simula N redraws de una UI con texto
2//! mayormente estable (chrome + un párrafo) más una línea que cambia cada
3//! frame (un contador/caret tipeando). Reporta tiempo total y hit-rate con el
4//! caché vivo vs. el costo de re-shapear siempre (clave única por frame).
5//!
6//!   cargo run -p llimphi-text --example bench_cache --release
7
8use llimphi_text::{Alignment, Typesetter};
9use std::time::Instant;
10
11const FRAMES: usize = 600; // ~10 s a 60 fps
12
13// Un bloque de chrome típico: labels que NO cambian entre frames.
14const CHROME: &[&str] = &[
15    "Archivo", "Editar", "Ver", "Insertar", "Formato", "Herramientas", "Ayuda",
16    "Guardar", "Abrir", "Nuevo", "Buscar", "Reemplazar", "Deshacer", "Rehacer",
17];
18const PARRAFO: &str = "Un documento es un haz de cuerpos sobre el mismo material, \
19    alineados párrafo a párrafo por sus hebras; si la madre cambia, la hija queda stale.";
20
21fn pintar_frame(ts: &mut Typesetter, frame: usize, estatico: bool) {
22    // Chrome estable + párrafo estable: misma clave cada frame ⇒ hit con caché.
23    for label in CHROME {
24        let _ = ts.layout(label, 13.0, None, Alignment::Start, 1.2, false, None, 400.0, false, false, 0.0, 0.0);
25    }
26    let _ = ts.layout(PARRAFO, 15.0, Some(420.0), Alignment::Start, 1.4, false, None, 400.0, false, false, 0.0, 0.0);
27    // Una línea que cambia cada frame (caret/contador): siempre miss.
28    // Con `estatico=true` la forzamos constante para ver el techo del caché.
29    let dinamico = if estatico {
30        "estado: listo".to_string()
31    } else {
32        format!("línea {frame} · col {}", frame % 80)
33    };
34    let _ = ts.layout(&dinamico, 13.0, None, Alignment::Start, 1.2, false, None, 400.0, false, false, 0.0, 0.0);
35}
36
37fn corrida(nombre: &str, estatico: bool) {
38    let mut ts = Typesetter::new();
39    // Warmup: primera pasada llena el caché (no la medimos).
40    pintar_frame(&mut ts, 0, estatico);
41    let base = ts.cache_stats();
42    let t0 = Instant::now();
43    for f in 1..=FRAMES {
44        pintar_frame(&mut ts, f, estatico);
45    }
46    let dt = t0.elapsed();
47    let s = ts.cache_stats();
48    let hits = s.hits - base.hits;
49    let misses = s.misses - base.misses;
50    let total = hits + misses;
51    println!(
52        "{nombre:<28} {FRAMES} frames en {:>7.2?}  ({:>6.1} µs/frame)  hit-rate {:.1}% ({hits}/{total})  entradas vivas {}",
53        dt,
54        dt.as_micros() as f64 / FRAMES as f64,
55        100.0 * hits as f64 / total as f64,
56        s.entries,
57    );
58}
59
60/// Baseline sin caché para la MISMA carga: cada texto se hace único por frame
61/// (sufijo invisible) ⇒ 100% miss ⇒ shaping completo siempre. Es el costo que
62/// el caché evita en el chrome+párrafo estables.
63fn corrida_sin_cache() {
64    let mut ts = Typesetter::new();
65    let frame_texts = |f: usize| -> Vec<String> {
66        let mut v: Vec<String> = CHROME.iter().map(|l| format!("{l}\u{200b}{f}")).collect();
67        v.push(format!("{PARRAFO}\u{200b}{f}"));
68        v.push(format!("línea {f} · col {}", f % 80));
69        v
70    };
71    let _ = frame_texts(0); // simetría con el warmup de `corrida`
72    let t0 = Instant::now();
73    for f in 1..=FRAMES {
74        for t in frame_texts(f) {
75            let _ = ts.layout(&t, 13.0, Some(420.0), Alignment::Start, 1.2, false, None, 400.0, false, false, 0.0, 0.0);
76        }
77    }
78    let dt = t0.elapsed();
79    println!(
80        "{:<28} {FRAMES} frames en {:>7.2?}  ({:>6.1} µs/frame)  hit-rate   0.0% (todo re-shapeado)",
81        "Sin caché (baseline)",
82        dt,
83        dt.as_micros() as f64 / FRAMES as f64,
84    );
85}
86
87fn main() {
88    println!("Caché de shaping de llimphi-text — {FRAMES} frames\n");
89    // Baseline: la misma carga, re-shapeando todo cada frame.
90    corrida_sin_cache();
91    // Caso real: chrome+párrafo estable, 1 línea cambiante por frame.
92    corrida("UI típica (1 línea cambia)", false);
93    // Techo: todo estable (lo que pasa en idle/hover sin cambio de texto).
94    corrida("Todo estable (techo)", true);
95}