tawasuyu

tawasuyu/llimphi

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

Rust 484Markdown 258TOML 131RON 38118 Definiciones

⎇ main llimphi-ui/src/censo.rs Rust · 119 líneas · 4.8 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0) b575c63f1 · Sergio ·

1//! Censo del reparto `View`/`ViewRare` contra el árbol de una app **real**,
2//! headless.
3//!
4//! Bloque 21, palanca 2b de `PLAN-NUCLEO-RETENIDO.md`. El ahorro del reparto
5//! depende de una sola cifra —la fracción de nodos que lleva alguna prop rara y
6//! paga la caja— y esa cifra la da el árbol de la app, no un benchmark: contra
7//! `stress_grid` daba −74 % y contra la galería real, −25,9 %. Toda la
8//! corrección del reparto salió de medir esto.
9//!
10//! No abre ventana ni toca la GPU: arma el Model con un `Handle` de tests, llama
11//! `App::view` una vez y recorre el árbol. Sale por stdout, es determinista, y
12//! cuesta lo que cuesta un `view()` — el modo barato de evidencia que pide la
13//! Regla 8 del repo.
14//!
15//! Enchufarlo en una app es una línea en su `main`:
16//!
17//! ```ignore
18//! if std::env::args().any(|a| a == "--census") {
19//!     llimphi_ui::censo_app::<MiApp>();
20//!     return;
21//! }
22//! ```
23
24use crate::{censo_rare, mascaras_rare, tam_rare, App, CensoRare, Handle, View, ViewRare};
25
26/// Corre el censo de `A` e imprime el informe. Ver el módulo.
27pub fn censo_app<A: App>() {
28    // `init` sólo recibe el handle para despachar; acá los Msg se tiran porque
29    // no hay bucle que los aplique.
30    let modelo = A::init(&Handle::<A::Msg>::for_test_with(|_| {}));
31    let vista: View<A::Msg> = A::view(&modelo);
32    informe::<A::Msg>(&vista, A::title());
33}
34
35/// El informe sobre un árbol ya construido — separado de [`censo_app`] para que
36/// una app con un Model caro de armar pueda pasar el suyo.
37pub fn informe<Msg>(vista: &View<Msg>, nombre: &str) {
38    let c = censo_rare(vista);
39    let (antes, ahora) = (c.bytes_antes(), c.bytes_ahora::<Msg>());
40    let base_view = core::mem::size_of::<View<Msg>>();
41    let base_rare = core::mem::size_of::<ViewRare<Msg>>();
42
43    println!("{nombre} — censo de props raras (árbol real, headless)");
44    println!("  nodos                 {}", c.nodos);
45    println!(
46        "  con caja ViewRare     {} ({:.1} %)",
47        c.con_rare,
48        100.0 * c.fraccion()
49    );
50    println!(
51        "  size_of View / Rare   {base_view} B / {base_rare} B  (antes del reparto: {} B por nodo)",
52        CensoRare::VIEW_ANTES,
53    );
54    println!(
55        "  bytes del árbol       {antes} → {ahora}  ({:+.1} %)",
56        100.0 * (ahora as f64 - antes as f64) / antes as f64
57    );
58    println!(
59        "  asignaciones extra    {} (una caja por nodo que usa alguna prop rara)",
60        c.con_rare
61    );
62    if c.con_rare > 0 {
63        println!("  qué prop hace rara a cada caja:");
64        for (campo, n) in crate::censo_campos(vista) {
65            println!("      {n:>5}  {campo}");
66        }
67    }
68
69    // ¿Convendría devolver alguna prop a `View`? La marginal por campo NO
70    // alcanza: lo que importa es cuántos nodos quedarían SIN ninguna prop rara
71    // (ésos dejan de pagar la caja entera), y eso depende del conjunto por nodo.
72    // Con la máscara se evalúa exacto; se elige por búsqueda voraz.
73    let mascaras = mascaras_rare(vista);
74    let tam = tam_rare::<Msg>();
75    let bytes_con = |s: u64| -> usize {
76        let crecio: usize = (0..tam.len()).filter(|k| s & (1 << k) != 0).map(|k| tam[k]).sum();
77        let cajas = mascaras.iter().filter(|m| *m & !s != 0).count();
78        c.nodos * (base_view + crecio) + cajas * (base_rare - crecio)
79    };
80    let mut s = 0u64;
81    let mut mejor = bytes_con(0);
82    loop {
83        let cand = (0..tam.len())
84            .filter(|k| s & (1 << k) == 0)
85            .map(|k| (bytes_con(s | (1 << k)), k))
86            .min();
87        match cand {
88            Some((b, k)) if b < mejor => {
89                mejor = b;
90                s |= 1 << k;
91            }
92            _ => break,
93        }
94    }
95    if s == 0 {
96        println!("  reparto óptimo para este árbol: el vigente (no conviene mover ningún campo)");
97        return;
98    }
99    let elegidas: Vec<&str> = (0..tam.len())
100        .filter(|k| s & (1 << k) != 0)
101        .map(|k| crate::NOMBRES_RARE[k])
102        .collect();
103    let crecio: usize = (0..tam.len()).filter(|k| s & (1 << k) != 0).map(|k| tam[k]).sum();
104    let cajas = mascaras.iter().filter(|m| *m & !s != 0).count();
105    println!("  este árbol preferiría OTRO reparto (búsqueda voraz sobre la máscara):");
106    println!("      volver inline    {elegidas:?}");
107    println!("      View             {base_view} B → {} B", base_view + crecio);
108    println!("      cajas            {} → {cajas}", c.con_rare);
109    println!(
110        "      bytes del árbol  {ahora} → {mejor}  ({:+.1} % contra el View viejo)",
111        100.0 * (mejor as f64 - antes as f64) / antes as f64
112    );
113    println!(
114        "      contracara: un árbol uniforme SIN props raras (tipo stress_grid)\n\
115         \x20                 pasaría de {base_view} a {} B por nodo, {:+.1} %",
116        base_view + crecio,
117        100.0 * crecio as f64 / base_view as f64
118    );
119}