//! stress_memo — Bloque 25 (memo). Como `stress_boundary`, pero el grid estático //! va en un `.memo(key, deps, builder)` en vez de `.repaint_boundary()`. Con //! `deps` constante, tras el primer frame el builder NO se llama: el `View` del //! grid no se reconstruye, no se re-monta y no se re-layoutea (se reusa el //! `Mounted` cacheado). Así se desploman `view`, `mount` Y `layout` para el //! subárbol — no sólo `paint` (que era lo del Bloque 23). //! //! ```bash //! LLIMPHI_PERF=20 cargo run -p llimphi-ui --example stress_grid --release -- --nodes 50000 --frames 0 //! LLIMPHI_PERF=20 cargo run -p llimphi-ui --example stress_memo --release -- --nodes 50000 --frames 0 //! ``` //! En stress_memo, `view`/`mount`/`layout` deberían ser mucho más bajos (el grid //! sólo se construye una vez). use llimphi_ui::llimphi_layout::taffy::prelude::*; use llimphi_ui::llimphi_raster::peniko::Color; use llimphi_ui::{App, Handle, View}; use std::time::Duration; #[derive(Clone)] enum Msg { Tick, } struct Stress; struct Model { frame: u64, nodes: usize, cols: usize, quit_after: u64, renders: std::cell::Cell, } fn arg_u64(name: &str, default: u64) -> u64 { let mut args = std::env::args(); while let Some(a) = args.next() { if a == name { if let Some(v) = args.next() { if let Ok(n) = v.parse::() { return n; } } } } default } /// Grid estático (colores fijos por índice). Lo produce el builder del memo, que /// sólo se llama cuando `deps` cambia (aquí nunca) → se construye una sola vez. fn grid_estatico(nodes: usize, cols: usize) -> View { let rows = nodes.div_ceil(cols); let mut row_views = Vec::with_capacity(rows); let mut made = 0usize; for _r in 0..rows { let mut cells = Vec::with_capacity(cols); for _c in 0..cols { if made >= nodes { break; } let hue = (made % 255) as u8; cells.push( View::new(Style { size: Size { width: length(6.0_f32), height: length(6.0_f32), }, ..Default::default() }) .fill(Color::from_rgba8(hue, 90, 200u8.wrapping_sub(hue), 255)) .radius(1.0), ); made += 1; } row_views.push( View::new(Style { flex_direction: FlexDirection::Row, gap: Size { width: length(1.0_f32), height: length(0.0_f32), }, ..Default::default() }) .children(cells), ); } View::new(Style { flex_direction: FlexDirection::Column, gap: Size { width: length(0.0_f32), height: length(1.0_f32), }, ..Default::default() }) .children(row_views) } impl App for Stress { type Model = Model; type Msg = Msg; fn title() -> &'static str { "llimphi · stress_memo" } fn init(handle: &Handle) -> Self::Model { let nodes = arg_u64("--nodes", 5_000).max(1) as usize; let cols = (nodes as f64).sqrt().ceil() as usize; let quit_after = arg_u64("--frames", 240); handle.spawn_periodic(Duration::from_millis(16), || Msg::Tick); eprintln!( "[stress_memo] nodes={nodes} cols={cols} — LLIMPHI_PERF=1; \ el grid va en un .memo (se construye UNA vez)" ); Model { frame: 0, nodes, cols, quit_after, renders: std::cell::Cell::new(0), } } fn update(mut model: Self::Model, msg: Self::Msg, handle: &Handle) -> Self::Model { match msg { Msg::Tick => { model.frame = model.frame.wrapping_add(1); if model.quit_after != 0 && model.renders.get() >= model.quit_after { handle.quit(); } } } model } fn view(model: &Self::Model) -> View { model.renders.set(model.renders.get() + 1); // Header que cambia cada frame (fuerza el repintado). Fuera del memo. let header = View::new(Style { size: Size { width: percent(1.0_f32), height: length(28.0_f32), }, ..Default::default() }) .fill(Color::from_rgba8(30, 34, 44, 255)) .text( format!("frame {}", model.frame), 16.0, Color::from_rgba8(210, 214, 222, 255), ); let nodes = model.nodes; let cols = model.cols; // Contenedor del grid con tamaño DEFINIDO (contrato del memo) + `.memo` // con deps constante (0) → el grid se construye una sola vez. let grid = View::new(Style { size: Size { width: percent(1.0_f32), height: length(520.0_f32), }, flex_shrink: 0.0, ..Default::default() }) .memo(1, 0, move || grid_estatico(nodes, cols)); View::new(Style { flex_direction: FlexDirection::Column, size: Size { width: percent(1.0_f32), height: percent(1.0_f32), }, gap: Size { width: length(0.0_f32), height: length(4.0_f32), }, ..Default::default() }) .fill(Color::from_rgba8(16, 18, 24, 255)) .children(vec![header, grid]) } } fn main() { llimphi_ui::run::(); }