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 })
}
}