tawasuyu

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Definiciones

⎇ c5a232cf24788ab75a850ffe81eca5a7af50e2f8 llimphi-ui/examples/stress_grid.rs Rust · 158 líneas · 5.3 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo — 27 crates nuevos (anim, lottie, svg, mesh, image, layer, test, wire-view, widgets rag-sidebar/router/lazy-list/rive-button) y sync de los 95 existentes 93253cc22 · Sergio ·

1//! stress_grid — Bloque 21 (PLAN-NUCLEO-RETENIDO.md): app sintética de N nodos
2//! para medir el camino completo de redraw con el perfilador `LLIMPHI_PERF`.
3//!
4//! Cada tick (~16 ms) muta el modelo → invalida `last_render` → fuerza un frame
5//! de camino COMPLETO (mount + layout + paint), que es justo lo que el
6//! perfilador muestrea. El color de cada celda depende del frame, así el árbol
7//! cambia de verdad cada cuadro (nada de cache-hit).
8//!
9//! Uso (baseline del Bloque 21):
10//! ```bash
11//! LLIMPHI_PERF=1 cargo run -p llimphi-ui --example stress_grid --release -- --nodes 5000
12//! LLIMPHI_PERF=1 cargo run -p llimphi-ui --example stress_grid --release -- --nodes 50000
13//! LLIMPHI_PERF=1 cargo run -p llimphi-ui --example stress_grid --release -- --nodes 200000
14//! ```
15
16use llimphi_ui::llimphi_layout::taffy::prelude::*;
17use llimphi_ui::llimphi_raster::peniko::Color;
18use llimphi_ui::{App, Handle, View};
19use std::time::Duration;
20
21#[derive(Clone)]
22enum Msg {
23    Tick,
24}
25
26struct Stress;
27
28struct Model {
29    frame: u64,
30    nodes: usize,
31    cols: usize,
32    /// Se cierra solo tras estos frames RENDERIZADOS (para no imprimir
33    /// infinito). `0` = nunca (corre hasta cerrar la ventana). Ver `--frames`.
34    quit_after: u64,
35    /// Frames realmente renderizados (incrementado en `view`). El auto-quit se
36    /// basa en esto, no en los Ticks: a muchos nodos el render es más lento que
37    /// el tick de 16 ms y contar Ticks cerraría antes de que el perfilador
38    /// (reporta cada N renders) imprima una línea.
39    renders: std::cell::Cell<u64>,
40}
41
42/// Lee un flag `--<name> <valor>` de la línea de comando.
43fn arg_u64(name: &str, default: u64) -> u64 {
44    let mut args = std::env::args();
45    while let Some(a) = args.next() {
46        if a == name {
47            if let Some(v) = args.next() {
48                if let Ok(n) = v.parse::<u64>() {
49                    return n;
50                }
51            }
52        }
53    }
54    default
55}
56
57impl App for Stress {
58    type Model = Model;
59    type Msg = Msg;
60
61    fn title() -> &'static str {
62        "llimphi · stress_grid"
63    }
64
65    fn init(handle: &Handle<Self::Msg>) -> Self::Model {
66        let nodes = arg_u64("--nodes", 5_000).max(1) as usize;
67        let cols = (nodes as f64).sqrt().ceil() as usize;
68        // Default: se cierra solo tras 240 frames (~4 reportes de 60). `--frames 0`
69        // corre indefinido (cierras la ventana a mano).
70        let quit_after = arg_u64("--frames", 240);
71        // Feed continuo: garantiza frames de camino completo para muestrear.
72        handle.spawn_periodic(Duration::from_millis(16), || Msg::Tick);
73        eprintln!(
74            "[stress_grid] nodes={nodes} cols={cols} — LLIMPHI_PERF=1; \
75             sale solo a los {quit_after} frames (--frames 0 = infinito)"
76        );
77        Model {
78            frame: 0,
79            nodes,
80            cols,
81            quit_after,
82            renders: std::cell::Cell::new(0),
83        }
84    }
85
86    fn update(mut model: Self::Model, msg: Self::Msg, handle: &Handle<Self::Msg>) -> Self::Model {
87        match msg {
88            Msg::Tick => {
89                model.frame = model.frame.wrapping_add(1);
90                if model.quit_after != 0 && model.renders.get() >= model.quit_after {
91                    handle.quit();
92                }
93            }
94        }
95        model
96    }
97
98    fn view(model: &Self::Model) -> View<Self::Msg> {
99        model.renders.set(model.renders.get() + 1);
100        let cols = model.cols.max(1);
101        let rows = model.nodes.div_ceil(cols);
102        let f = model.frame;
103        let mut row_views = Vec::with_capacity(rows);
104        let mut made = 0usize;
105        for _r in 0..rows {
106            let mut cells = Vec::with_capacity(cols);
107            for _c in 0..cols {
108                if made >= model.nodes {
109                    break;
110                }
111                let i = made as u64;
112                // Color animado por frame → el árbol cambia de verdad cada cuadro.
113                let hue = ((i.wrapping_add(f)) % 255) as u8;
114                cells.push(
115                    View::new(Style {
116                        size: Size {
117                            width: length(6.0_f32),
118                            height: length(6.0_f32),
119                        },
120                        ..Default::default()
121                    })
122                    .fill(Color::from_rgba8(hue, 90, 200u8.wrapping_sub(hue), 255))
123                    .radius(1.0),
124                );
125                made += 1;
126            }
127            row_views.push(
128                View::new(Style {
129                    flex_direction: FlexDirection::Row,
130                    gap: Size {
131                        width: length(1.0_f32),
132                        height: length(0.0_f32),
133                    },
134                    ..Default::default()
135                })
136                .children(cells),
137            );
138        }
139        View::new(Style {
140            flex_direction: FlexDirection::Column,
141            size: Size {
142                width: percent(1.0_f32),
143                height: percent(1.0_f32),
144            },
145            gap: Size {
146                width: length(0.0_f32),
147                height: length(1.0_f32),
148            },
149            ..Default::default()
150        })
151        .fill(Color::from_rgba8(16, 18, 24, 255))
152        .children(row_views)
153    }
154}
155
156fn main() {
157    llimphi_ui::run::<Stress>();
158}