Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-demo/src/gen_build.rs

//! **The [`BuildFn`](crate::graphgen::BuildFn)s** — how each graph tab's component is
//! made from a generated graph ([`crate::graphgen::GenGraph`]). One function per tab,
//! each building the SAME component the tab shows on its demo data, with the GPU lane
//! it carries switched on (it self-disables with no installed renderer and says so in
//! `state_json`).
//!
//! The builders of LIBRARY components (`lineage`, `decograph`, `graphpan`, `graph3d`,
//! `graph`) live in `facett_graphgen::build` (lane F, 2026-10-02) — facet-wrapped's
//! `{"generate": …}` seam calls the same ones — and are re-exported here. The builders of
//! the demo's own tab types stay below.

pub use facett_graphgen::build::{FIT_VIEWPORT, decograph, graph, graph3d, graphpan, lineage};

use crate::graphgen::{self, GenGraph};

/// `Graphs` — the graph navigator + its node table + its 3-D cloud.
pub fn graphs(title: &'static str, gg: &GenGraph) -> crate::vg_lab_tab::VgLabTab {
    crate::vg_lab_tab::VgLabTab::from_generated(title, gg)
}

/// `falkor` — the generated graph as the answer to a Cypher query: a
/// [`CypherResult`](facett_graphview::CypherResult) on the browser's in-memory runner,
/// laid out by the browser's own query path (`FalkorBrowser::run_query`: a GPU layout
/// job on the host's device — all-pairs below `GRID_MIN_NODES`, the grid above — or,
/// with no device, the CPU lane up to `CPU_LAYOUT_MAX` and "no GPU" above), framed,
/// painted on the resident cloud above `RESIDENT_ABOVE`.
pub fn falkor(title: &'static str, gg: &GenGraph) -> crate::falkor_tab::FalkorTab {
    let query = format!("MATCH (n)-[r]->(m) RETURN n, r, m LIMIT {}", gg.g.edges.len().max(1));
    crate::falkor_tab::FalkorTab::with_result(title, &query, graphgen::to_cypher(gg), FIT_VIEWPORT)
}

/// `World coderoom` — the generated graph through the coderoom DATA SEAM
/// ([`GenCoderoomData`]): every node a `crate@version`, every edge a semver compat edge.
///
/// The seam's layered layout is O(V + E) (`DepGraphLayout::build`), the pane paints on
/// the resident GPU cloud above `RESIDENT_ABOVE` crates, framed once (the camera then
/// survives every what-breaks rebuild).
pub fn coderoom(title: &'static str, gg: &GenGraph) -> facett_coderoom_view::CoderoomPane {
    let mut p = facett_coderoom_view::CoderoomPane::new(Box::new(GenCoderoomData::from_generated(gg)))
        .with_title(title)
        .with_gpu(true);
    p.fit_graph(FIT_VIEWPORT, 16.0);
    p
}

/// `graphview` — the generated model through the L0 render kernel (above
/// `RESIDENT_ABOVE` nodes, with a renderer installed: the resident GPU cloud).
pub fn graphview(title: &'static str, gg: &GenGraph) -> crate::graphview_tab::GraphviewTab {
    crate::graphview_tab::GraphviewTab::from_model(title, graphgen::to_view_model(gg))
}

/// **A generated world coderoom** behind the pane's data seam. Node `i` is crate
/// `<label>-<i>@0.<group>.0` with a symbol count from its degree; edge `a → b` says
/// consumer `a` references producer `b` (oriented newer → older, so the crate graph is a
/// DAG as Cargo's is), and one edge in six (seeded) is BREAKING — `b`'s
/// pending change removes the symbol `a` uses. `what_breaks(b)` answers with exactly
/// those consumers, so the pane's impact set comes from the seam, as with a warehouse.
pub struct GenCoderoomData {
    crates: Vec<facett_coderoom_view::CrateNode>,
    edges: Vec<facett_coderoom_view::CompatEdge>,
}

impl GenCoderoomData {
    #[must_use]
    pub fn from_generated(gg: &GenGraph) -> Self {
        use facett_coderoom_view::{CompatEdge, CrateNode};
        let n = gg.g.n;
        let mut degree = vec![0u64; n];
        for &(a, b) in &gg.g.edges {
            degree[a as usize] += 1;
            degree[b as usize] += 1;
        }
        let crates: Vec<CrateNode> = (0..n)
            .map(|i| {
                let name = format!("{}-{i}", gg.label(i).to_lowercase());
                CrateNode::new(name, format!("0.{}.0", gg.g.group[i] % 10), 4 + degree[i] * 3)
            })
            .collect();
        let mut rng = facett_graphview::generate::SplitMix::new(gg.g.seed ^ 0x636f_6465);
        let edges = gg
            .g
            .edges
            .iter()
            .filter(|&&(a, b)| a != b)
            .map(|&(a, b)| {
                // Cargo forbids dependency cycles: a crate depends only on crates that
                // existed before it. The newer crate (the higher index) is the consumer,
                // so EVERY generator becomes a DAG — and the seam's layered layout gets
                // real layers (an SBM's random cycles stacked 100k crates into one
                // screen-wide band of ranks: 22-34 fps on the t14s, fill-bound).
                let (a, b) = (a.max(b), a.min(b));
                let (from, to) = (crates[a as usize].id(), crates[b as usize].id());
                let sym = format!("sym_{b}");
                if rng.unit() < 1.0 / 6.0 {
                    CompatEdge::breaking(from, to, "major", &[sym.as_str()])
                } else {
                    CompatEdge::compatible(from, to, "minor", &[sym.as_str()])
                }
            })
            .collect();
        Self { crates, edges }
    }
}

impl facett_coderoom_view::CoderoomDataSeam for GenCoderoomData {
    fn source(&self) -> &str {
        "generated (facett-demo::graphgen)"
    }
    fn crates(&self) -> Vec<facett_coderoom_view::CrateNode> {
        self.crates.clone()
    }
    fn compat_edges(&self) -> Vec<facett_coderoom_view::CompatEdge> {
        self.edges.clone()
    }
    fn what_breaks(&self, target: &str) -> Option<facett_coderoom_view::WhatBreaks> {
        use facett_coderoom_view::{Impact, WhatBreaks};
        let impacted: Vec<Impact> = self
            .edges
            .iter()
            .filter(|e| e.to == target && e.compat.is_breaking())
            .map(|e| Impact::new(e.from.clone(), e.via.first().cloned().unwrap_or_default()))
            .collect();
        if impacted.is_empty() {
            return None;
        }
        let mut removed: Vec<String> = impacted.iter().map(|i| i.symbol.clone()).collect();
        removed.sort();
        removed.dedup();
        Some(WhatBreaks { target: target.to_string(), removed_symbols: removed, impacted })
    }
}