tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros
1// Guardarraíl del reciclado de buffers (Bloque 21, palanca 1).
2//
3// La invariante: **nadie asigna `last_render = None` a mano**. El único camino
4// sancionado es `RuntimeState::invalidar_render`, que invalida la caché de
5// hit-test PERO se queda con el `Vec<MountedNode>` en `mount_pool` para que el
6// mount siguiente escriba encima (a 200k nodos, 432 MB por frame).
7//
8// Existe porque el agujero se escapó DOS veces seguidas, y las dos veces fue
9// invisible: el reciclado compilaba, corría, y no reciclaba nada. La primera vez
10// porque todo `update` tiraba la caché; la segunda porque quedó un sitio sin
11// convertir — `dispatch_model`, que se llama con `prim.`, no con `state.`, y por
12// eso no cayó en el barrido. Es el camino de TODO Msg del canal: el más caliente
13// que hay.
14//
15// Rust no sabe expresar "este campo se lee desde cualquier lado pero se escribe
16// sólo acá" (no hay visibilidad por campo dentro del mismo módulo), así que la
17// invariante se sostiene leyendo la fuente. Feo, pero atrapa exactamente lo que
18// se escapó.
19
20use std::fs;
21use std::path::Path;
22
23/// Único uso legítimo: la caché de una ventana **secundaria**
24/// (`SecRenderCache`), que es otro tipo y no alimenta el pool de la primaria.
25const PERMITIDO: &str = "sec.last_render = None";
26
27fn recorrer(dir: &Path, hallazgos: &mut Vec<String>) {
28 for entrada in fs::read_dir(dir).expect("leer src/") {
29 let ruta = entrada.expect("entrada").path();
30 if ruta.is_dir() {
31 recorrer(&ruta, hallazgos);
32 } else if ruta.extension().is_some_and(|e| e == "rs") {
33 let fuente = fs::read_to_string(&ruta).expect("leer fuente");
34 for (i, linea) in fuente.lines().enumerate() {
35 let l = linea.trim();
36 // Sólo asignaciones reales: descarta comentarios y doc.
37 if l.starts_with("//") {
38 continue;
39 }
40 if l.contains("last_render = None") && !l.contains(PERMITIDO) {
41 hallazgos.push(format!("{}:{}: {l}", ruta.display(), i + 1));
42 }
43 }
44 }
45 }
46}
47
48#[test]
49fn nadie_tira_el_mounted_a_mano() {
50 let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
51 let mut hallazgos = Vec::new();
52 recorrer(&src, &mut hallazgos);
53 assert!(
54 hallazgos.is_empty(),
55 "asignación cruda de `last_render = None` — usá `invalidar_render()`, que \
56 conserva los buffers del árbol montado en `mount_pool`:\n {}",
57 hallazgos.join("\n ")
58 );
59}