gioser

tawasuyu/llimphi

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

Rust 229TOML 101Markdown 82Shell 24052 Definiciones

⎇ main shared/grafo/examples/verbos.rs Rust · 124 líneas · 4.4 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//! `verbos` — uso canónico de [`grafo_nav`]: cómo un dominio implementa
2//! [`GrafoNavegable`] y resuelve los verbos (*cercenar*, *conciliar*) sobre su
3//! propio modelo. Headless a propósito — certifica la semántica en texto, sin
4//! UI (los adaptadores reales lo copian y le enchufan `cockpit_grafo_view`).
5//!
6//! Correr: `cargo run -p grafo-nav --example verbos`
7
8use grafo_nav::{GrafoNavegable, Nodo, NodoId, Rol, Verbo};
9
10/// Un dominio sintético: una "historia" de nodos encadenados. En un dominio
11/// real esto sería `minga::GrafoCommits`, `ayni::Conversacion`, etc.
12struct Historia {
13    nodos: Vec<(NodoId, String)>,
14    aristas: Vec<(NodoId, NodoId)>,
15    activo: Option<NodoId>,
16}
17
18impl GrafoNavegable for Historia {
19    fn nodos(&self) -> Vec<Nodo> {
20        self.nodos
21            .iter()
22            .map(|(id, et)| {
23                let rol = if self.activo == Some(*id) { Rol::Activo } else { Rol::Normal };
24                Nodo::new(*id, et.clone(), rol)
25            })
26            .collect()
27    }
28
29    fn aristas(&self) -> Vec<(NodoId, NodoId)> {
30        self.aristas.clone()
31    }
32
33    /// La política de verbos del dominio: saltar siempre; restablecer/cercenar
34    /// requieren selección; conciliar sólo si el grafo bifurcó.
35    fn acciones(&self, seleccion: Option<NodoId>) -> Vec<Verbo> {
36        let mut v = vec![Verbo::Saltar];
37        if seleccion.is_some() {
38            v.push(Verbo::Restablecer);
39            v.push(Verbo::Cercenar);
40        }
41        if self.bifurcado() {
42            v.push(Verbo::Conciliar);
43        }
44        v
45    }
46}
47
48impl Historia {
49    /// *Cercenar*: poda un nodo y todo su cono descendente. En minga sería el gc
50    /// de inalcanzables; en un notebook, borrar la celda con sus dependientes.
51    fn cercenar(&mut self, id: NodoId) {
52        let cono = self.cono_descendente(id); // ya ordenado, incluye `id`
53        self.nodos.retain(|(n, _)| !cono.contains(n));
54        self.aristas
55            .retain(|(f, t)| !cono.contains(f) && !cono.contains(t));
56        if self.activo.map(|a| cono.contains(&a)).unwrap_or(false) {
57            self.activo = None;
58        }
59    }
60
61    /// *Conciliar*: si hay bifurcación, crea un nodo-merge que apunta desde todas
62    /// las cabezas — la fusión de minga, el merge de cabezas de ayni.
63    fn conciliar(&mut self, nuevo_id: NodoId, etiqueta: &str) {
64        let mut cabezas = self.cabezas();
65        cabezas.sort_unstable();
66        if cabezas.len() < 2 {
67            return;
68        }
69        self.nodos.push((nuevo_id, etiqueta.to_string()));
70        for c in cabezas {
71            self.aristas.push((c, nuevo_id));
72        }
73        self.activo = Some(nuevo_id);
74    }
75}
76
77fn resumen(h: &Historia, titulo: &str) {
78    let mut cabezas = h.cabezas();
79    cabezas.sort_unstable();
80    println!("── {titulo}");
81    println!("   nodos={}  raíces={:?}  cabezas={:?}  bifurcado={}",
82        h.nodos.len(), h.raices(), cabezas, h.bifurcado());
83}
84
85fn main() {
86    // Diamante con una cabeza extra suelta:
87    //   1→2, 1→3, 2→4, 3→4  (diamante)   +   1→5  (cabeza suelta)
88    let mut h = Historia {
89        nodos: vec![
90            (1, "raíz".into()),
91            (2, "rama A".into()),
92            (3, "rama B".into()),
93            (4, "merge previo".into()),
94            (5, "experimento".into()),
95        ],
96        aristas: vec![(1, 2), (1, 3), (2, 4), (3, 4), (1, 5)],
97        activo: Some(4),
98    };
99
100    resumen(&h, "inicial");
101    println!("   acciones(sel=Some(5)) = {:?}",
102        h.acciones(Some(5)).iter().map(|v| v.etiqueta()).collect::<Vec<_>>());
103
104    // Cercenar el experimento (nodo 5): desaparece esa cabeza suelta.
105    println!("\n>> Cercenar(5)  — poda el nodo y su cono");
106    h.cercenar(5);
107    resumen(&h, "tras cercenar");
108    assert!(!h.nodos.iter().any(|(n, _)| *n == 5), "5 fue podado");
109    assert!(!h.bifurcado(), "ya no bifurca: única cabeza = 4");
110
111    // Forzar una bifurcación nueva y conciliarla.
112    println!("\n>> añadir una rama divergente y Conciliar");
113    h.nodos.push((6, "hotfix".into()));
114    h.aristas.push((1, 6)); // 6 cuelga de la raíz → segunda cabeza
115    resumen(&h, "con divergencia");
116    assert!(h.bifurcado(), "dos cabezas: 4 y 6");
117    h.conciliar(7, "conciliación");
118    resumen(&h, "tras conciliar");
119    assert!(!h.bifurcado(), "conciliado: única cabeza = 7");
120    assert_eq!(h.cabezas(), vec![7]);
121    assert_eq!(h.activo, Some(7));
122
123    println!("\nOK — verbos cercenar/conciliar coherentes con la estructura derivada.");
124}