gioser

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Definitions

⎇ c5a232cf24788ab75a850ffe81eca5a7af50e2f8 llimphi-ui/examples/stress_memo.rs Rust · 186 lines · 5.7 KB History Who touched what raw

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_memo — Bloque 25 (memo). Como `stress_boundary`, pero el grid estático
2//! va en un `.memo(key, deps, builder)` en vez de `.repaint_boundary()`. Con
3//! `deps` constante, tras el primer frame el builder NO se llama: el `View` del
4//! grid no se reconstruye, no se re-monta y no se re-layoutea (se reusa el
5//! `Mounted` cacheado). Así se desploman `view`, `mount` Y `layout` para el
6//! subárbol — no sólo `paint` (que era lo del Bloque 23).
7//!
8//! ```bash
9//! LLIMPHI_PERF=20 cargo run -p llimphi-ui --example stress_grid --release -- --nodes 50000 --frames 0
10//! LLIMPHI_PERF=20 cargo run -p llimphi-ui --example stress_memo --release -- --nodes 50000 --frames 0
11//! ```
12//! En stress_memo, `view`/`mount`/`layout` deberían ser mucho más bajos (el grid
13//! sólo se construye una vez).
14
15use llimphi_ui::llimphi_layout::taffy::prelude::*;
16use llimphi_ui::llimphi_raster::peniko::Color;
17use llimphi_ui::{App, Handle, View};
18use std::time::Duration;
19
20#[derive(Clone)]
21enum Msg {
22    Tick,
23}
24
25struct Stress;
26
27struct Model {
28    frame: u64,
29    nodes: usize,
30    cols: usize,
31    quit_after: u64,
32    renders: std::cell::Cell<u64>,
33}
34
35fn arg_u64(name: &str, default: u64) -> u64 {
36    let mut args = std::env::args();
37    while let Some(a) = args.next() {
38        if a == name {
39            if let Some(v) = args.next() {
40                if let Ok(n) = v.parse::<u64>() {
41                    return n;
42                }
43            }
44        }
45    }
46    default
47}
48
49/// Grid estático (colores fijos por índice). Lo produce el builder del memo, que
50/// sólo se llama cuando `deps` cambia (aquí nunca) → se construye una sola vez.
51fn grid_estatico(nodes: usize, cols: usize) -> View<Msg> {
52    let rows = nodes.div_ceil(cols);
53    let mut row_views = Vec::with_capacity(rows);
54    let mut made = 0usize;
55    for _r in 0..rows {
56        let mut cells = Vec::with_capacity(cols);
57        for _c in 0..cols {
58            if made >= nodes {
59                break;
60            }
61            let hue = (made % 255) as u8;
62            cells.push(
63                View::new(Style {
64                    size: Size {
65                        width: length(6.0_f32),
66                        height: length(6.0_f32),
67                    },
68                    ..Default::default()
69                })
70                .fill(Color::from_rgba8(hue, 90, 200u8.wrapping_sub(hue), 255))
71                .radius(1.0),
72            );
73            made += 1;
74        }
75        row_views.push(
76            View::new(Style {
77                flex_direction: FlexDirection::Row,
78                gap: Size {
79                    width: length(1.0_f32),
80                    height: length(0.0_f32),
81                },
82                ..Default::default()
83            })
84            .children(cells),
85        );
86    }
87    View::new(Style {
88        flex_direction: FlexDirection::Column,
89        gap: Size {
90            width: length(0.0_f32),
91            height: length(1.0_f32),
92        },
93        ..Default::default()
94    })
95    .children(row_views)
96}
97
98impl App for Stress {
99    type Model = Model;
100    type Msg = Msg;
101
102    fn title() -> &'static str {
103        "llimphi · stress_memo"
104    }
105
106    fn init(handle: &Handle<Self::Msg>) -> Self::Model {
107        let nodes = arg_u64("--nodes", 5_000).max(1) as usize;
108        let cols = (nodes as f64).sqrt().ceil() as usize;
109        let quit_after = arg_u64("--frames", 240);
110        handle.spawn_periodic(Duration::from_millis(16), || Msg::Tick);
111        eprintln!(
112            "[stress_memo] nodes={nodes} cols={cols} — LLIMPHI_PERF=1; \
113             el grid va en un .memo (se construye UNA vez)"
114        );
115        Model {
116            frame: 0,
117            nodes,
118            cols,
119            quit_after,
120            renders: std::cell::Cell::new(0),
121        }
122    }
123
124    fn update(mut model: Self::Model, msg: Self::Msg, handle: &Handle<Self::Msg>) -> Self::Model {
125        match msg {
126            Msg::Tick => {
127                model.frame = model.frame.wrapping_add(1);
128                if model.quit_after != 0 && model.renders.get() >= model.quit_after {
129                    handle.quit();
130                }
131            }
132        }
133        model
134    }
135
136    fn view(model: &Self::Model) -> View<Self::Msg> {
137        model.renders.set(model.renders.get() + 1);
138        // Header que cambia cada frame (fuerza el repintado). Fuera del memo.
139        let header = View::new(Style {
140            size: Size {
141                width: percent(1.0_f32),
142                height: length(28.0_f32),
143            },
144            ..Default::default()
145        })
146        .fill(Color::from_rgba8(30, 34, 44, 255))
147        .text(
148            format!("frame {}", model.frame),
149            16.0,
150            Color::from_rgba8(210, 214, 222, 255),
151        );
152
153        let nodes = model.nodes;
154        let cols = model.cols;
155        // Contenedor del grid con tamaño DEFINIDO (contrato del memo) + `.memo`
156        // con deps constante (0) → el grid se construye una sola vez.
157        let grid = View::new(Style {
158            size: Size {
159                width: percent(1.0_f32),
160                height: length(520.0_f32),
161            },
162            flex_shrink: 0.0,
163            ..Default::default()
164        })
165        .memo(1, 0, move || grid_estatico(nodes, cols));
166
167        View::new(Style {
168            flex_direction: FlexDirection::Column,
169            size: Size {
170                width: percent(1.0_f32),
171                height: percent(1.0_f32),
172            },
173            gap: Size {
174                width: length(0.0_f32),
175                height: length(4.0_f32),
176            },
177            ..Default::default()
178        })
179        .fill(Color::from_rgba8(16, 18, 24, 255))
180        .children(vec![header, grid])
181    }
182}
183
184fn main() {
185    llimphi_ui::run::<Stress>();
186}