use super::*; // ───────────────────────────────────────────────────────────────────────── // Bloque 25 — Memo de view() (enfoque A: clone-splice). Un `View::memo(key, // deps, builder)` es un subárbol cuyo `View` NO se reconstruye si `deps` (ni el // tamaño del slot) cambiaron: el builder no se llama, y el `Mounted` cacheado se // splica en el árbol principal. El subárbol vive en un `LayoutTree` AISLADO que // taffy cachea → 0 re-solve. Ahorra view-build + mount + re-solve del subárbol; // paga un `clone` liviano (los handlers son `Arc`). Paint/hit-test quedan // INTACTOS (el splice produce un `Mounted` plano normal). Correctitud probada // por el arnés (Mounted memoizado == fresco). // ───────────────────────────────────────────────────────────────────────── /// Registro de subárboles memoizados (Bloque 25). Vive en el runtime entre /// frames. Por key: `deps` + tamaño del slot con que se computó + el /// `LayoutTree` aislado (cacheado por taffy) + el `Mounted` local + sus rects /// locales. pub struct MemoRegistry { entries: hashbrown::HashMap>, seen: hashbrown::HashSet, } pub(crate) struct MemoEntry { deps: u64, avail: (f32, f32), layout: LayoutTree, nodes: Vec>, rects: Vec, } impl Default for MemoRegistry { fn default() -> Self { Self { entries: hashbrown::HashMap::new(), seen: hashbrown::HashSet::new(), } } } impl MemoRegistry { pub fn new() -> Self { Self::default() } /// Subárboles memoizados vivos. pub fn len(&self) -> usize { self.entries.len() } pub fn is_empty(&self) -> bool { self.entries.is_empty() } } /// `true` si el árbol tiene algún nodo `memo` (fast-path para el runtime). pub fn has_memo(view: &View) -> bool { view.rare.as_ref().is_some_and(|r| r.memo.is_some()) || view.children.iter().any(has_memo) } /// Extrae los builders de los nodos `memo` del `view` (dejándolos como /// marcadores `builder: None`), en pre-orden. El runtime los pasa a /// [`splice_memos`] tras el layout. Debe correr ANTES de `mount`. pub fn take_memo_builders( view: &mut View, out: &mut Vec<(u64, u64, MemoBuilderFn, crate::Style)>, ) { if view .rare .as_ref() .and_then(|r| r.memo.as_ref()) .is_some_and(|m| m.builder.is_some()) { // Capturamos el `Style` del nodo memo: el subárbol aislado se computa // como `container(style) -> content`, igual que el fresco, para que el // contenido se estire dentro del box del memo (no se dimensione como raíz). let style = view.style.clone(); let m = view.rare.as_mut().and_then(|r| r.memo.as_mut()).expect("memo"); let b = m.builder.take().expect("builder"); out.push((m.key, m.deps, b, style)); } for c in &mut view.children { take_memo_builders(c, out); } } /// Splica los subárboles memoizados en el `Mounted`/`ComputedLayout` ya /// computados: para cada memo, si `deps` y el tamaño del slot no cambiaron reusa /// el `Mounted` cacheado (sin llamar el builder); si cambiaron, llama el builder, /// monta+computa el subárbol en su `LayoutTree` aislado y lo cachea. Luego clona /// los nodos cacheados con `NodeId` sintéticos (rango alto, colisión-proof), /// offsetea sus rects a la posición del marcador, y los inserta como hijos del /// marcador. `measure` mide una hoja de texto (misma que el compute principal). pub fn splice_memos( mounted: &mut Mounted, computed: &mut ComputedLayout, pending: &[(u64, u64, MemoBuilderFn, crate::Style)], reg: &mut MemoRegistry, mut measure: F, ) where Msg: Clone, F: FnMut( &TextMeasure, llimphi_layout::taffy::Size>, llimphi_layout::taffy::Size, ) -> llimphi_layout::taffy::Size, { reg.seen.clear(); let mut synth: u64 = u64::MAX; for (key, deps, builder, style) in pending { reg.seen.insert(*key); // Re-scan del marcador: los índices se corren tras cada splice. let Some(mi) = mounted.nodes.iter().position(|n| n.memo_ref == Some(*key)) else { continue; }; let Some(slot) = computed.get(mounted.nodes[mi].id) else { continue; }; let abs = (slot.x, slot.y); let avail = (slot.w, slot.h); // Reuso si deps y tamaño del slot coinciden; si no, rebuild. let hit = reg .entries .get(key) .is_some_and(|e| e.deps == *deps && e.avail == avail); if !hit { // El aislado replica EXACTAMENTE el fresco: `container(style) -> // content`. El `container` (índice 0) == el marcador; se computa con // el mismo slot que el marcador, así el `content` se estira igual. let root_view = View::new(style.clone()).children(vec![builder()]); let mut layout = reg .entries .remove(key) .map(|e| e.layout) .unwrap_or_default(); layout.clear(); let sub_mounted = mount(&mut layout, root_view); let sub_computed = { let tm = &sub_mounted.text_measures; layout .compute_with_measure(sub_mounted.root, avail, |nid, k, a| match tm.get(&nid) { Some(t) => measure(t, k, a), None => llimphi_layout::taffy::Size { width: 0.0, height: 0.0, }, }) .expect("layout memo") }; // Origen del container (índice 0) para hacer los rects RELATIVOS a él // (luego se offsetean al `abs` del marcador). let origin = sub_computed .get(sub_mounted.nodes[0].id) .map(|r| (r.x, r.y)) .unwrap_or((0.0, 0.0)); let rects = sub_mounted .nodes .iter() .map(|n| { let r = sub_computed.get(n.id).unwrap_or(llimphi_layout::Rect { x: 0.0, y: 0.0, w: 0.0, h: 0.0, }); llimphi_layout::Rect { x: r.x - origin.0, y: r.y - origin.1, w: r.w, h: r.h, } }) .collect(); reg.entries.insert( *key, MemoEntry { deps: *deps, avail, layout, nodes: sub_mounted.nodes, rects, }, ); } let e = reg.entries.get(key).expect("insertado o reusado"); // Salteamos el índice 0 (el container == el marcador, ya en el árbol // principal): sólo splicamos su CONTENIDO (`[1..]`). let sub_count = e.nodes.len().saturating_sub(1); if sub_count == 0 { continue; } let base = mi + 1; let mut cloned: Vec> = Vec::with_capacity(sub_count); for (i, n) in e.nodes.iter().enumerate().skip(1) { let mut c = n.clone(); let sid = NodeId::from(synth); synth -= 1; c.id = sid; // Índice local `i` (≥1) → absoluto `mi + i`; subtree_end local + mi. c.subtree_end = n.subtree_end + mi; let r = e.rects[i]; computed.rects.insert( sid, llimphi_layout::Rect { x: r.x + abs.0, y: r.y + abs.1, w: r.w, h: r.h, }, ); cloned.push(c); } // Corre los subtree_end existentes que abarcaban el marcador (>mi). for n in mounted.nodes.iter_mut() { if n.subtree_end > mi { n.subtree_end += sub_count; } } mounted.nodes.splice(base..base, cloned); } // GC de las keys que no aparecieron este frame. let seen = ®.seen; reg.entries.retain(|k, _| seen.contains(k)); }