use brdb::{AsBrdbValue, Brdb, IntoReader, WireChunkSoA};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write;
use std::path::PathBuf;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
let path = PathBuf::from(
args.get(1)
.expect("usage: extract_components <zoo.brdb> [out.rs]"),
);
let out_path = args.get(2).map(PathBuf::from);
let db = Brdb::open(path)?.into_reader();
let data = db.global_data()?;
let standalone_host_brick = brdb::assets::bricks::B_1X1F_ROUND;
let standalone_host: &str = standalone_host_brick.asset().as_ref();
let mut bricks: BTreeMap<u16, BTreeSet<String>> = BTreeMap::new();
let mut inputs: BTreeMap<u16, BTreeSet<u16>> = BTreeMap::new();
let mut outputs: BTreeMap<u16, BTreeSet<u16>> = BTreeMap::new();
for gid in 1..64 {
let chunks = match db.brick_chunk_index(gid) {
Ok(c) => c,
Err(_) => break,
};
for chunk in &chunks {
let bsoa = db.brick_chunk_soa(gid, chunk.index)?;
let pb_start = bsoa.procedural_brick_starting_index;
let proc_asset_by_size: Vec<u32> = bsoa
.brick_size_counters
.iter()
.flat_map(|c| std::iter::repeat(c.asset_index).take(c.num_sizes as usize))
.collect();
let brick_types = bsoa.brick_type_indices;
if chunk.num_components > 0 {
let (csoa, components) = db.component_chunk_soa(gid, chunk.index)?;
let brick_indices = csoa.component_brick_indices;
let type_indices = csoa
.component_type_counters
.iter()
.flat_map(|v| {
let ti = v.type_index as u16;
(0..v.num_instances).map(move |_| ti)
})
.collect::<Vec<_>>();
for i in 0..components.len() {
let comp_ty = type_indices[i];
inputs.entry(comp_ty).or_default();
outputs.entry(comp_ty).or_default();
let brick_index = brick_indices[i].as_brdb_u32()? as usize;
if let Some(&bt) = brick_types.get(brick_index) {
let host = if bt < pb_start {
data.basic_brick_asset_names.get_index(bt as usize).cloned()
} else {
proc_asset_by_size
.get((bt - pb_start) as usize)
.and_then(|&ai| {
data.procedural_brick_asset_names.get_index(ai as usize).cloned()
})
};
if let Some(host) = host {
if host != standalone_host {
bricks.entry(comp_ty).or_default().insert(host);
}
}
}
}
}
if chunk.num_wires > 0 {
let soa = db.wire_chunk_soa(gid, chunk.index)?.to_value();
let soa: WireChunkSoA = (&soa).try_into()?;
for p in &soa.local_wire_sources {
outputs
.entry(p.component_type_index)
.or_default()
.insert(p.port_index);
}
for p in &soa.remote_wire_sources {
outputs
.entry(p.component_type_index)
.or_default()
.insert(p.port_index);
}
for p in &soa.local_wire_targets {
inputs
.entry(p.component_type_index)
.or_default()
.insert(p.port_index);
}
for p in &soa.remote_wire_targets {
inputs
.entry(p.component_type_index)
.or_default()
.insert(p.port_index);
}
}
}
}
let name_of = |idx: u16| data.component_type_names.get_index(idx as usize).cloned();
let port_of = |idx: u16| data.component_wire_port_names.get_index(idx as usize).cloned();
let all: BTreeSet<u16> = bricks
.keys()
.chain(inputs.keys())
.chain(outputs.keys())
.copied()
.collect();
let mut rows: Vec<(String, Vec<String>, Vec<String>, Vec<String>)> = Vec::new();
for ty in all {
let Some(name) = name_of(ty) else { continue };
let mut bs: Vec<String> = bricks
.get(&ty)
.map(|s| s.iter().cloned().collect())
.unwrap_or_default();
bs.sort();
bs.dedup();
let mut ins: Vec<String> = inputs
.get(&ty)
.map(|s| s.iter().filter_map(|&p| port_of(p)).collect())
.unwrap_or_default();
ins.sort();
ins.dedup();
let mut outs: Vec<String> = outputs
.get(&ty)
.map(|s| s.iter().filter_map(|&p| port_of(p)).collect())
.unwrap_or_default();
outs.sort();
outs.dedup();
rows.push((name, bs, ins, outs));
}
rows.sort_by(|a, b| a.0.cmp(&b.0));
let mut out = String::new();
macro_rules! w {
($($t:tt)*) => { writeln!(out, $($t)*).unwrap() };
}
let slice = |v: &[String]| {
v.iter()
.map(|s| format!("{s:?}"))
.collect::<Vec<_>>()
.join(", ")
};
w!("// Autogenerated from a zoo save:");
w!("// cargo run --example extract_components -- <zoo.brdb> src/assets/component_catalog.rs");
w!("// Do not edit by hand.");
w!();
w!("/// Per-component catalog entry: the full component type name, its host");
w!("/// brick asset(s), and its wire input/output port names. Extracted from a");
w!("/// fully-placed, fully-wired \"zoo\" save (mirrors brs-js's COMPONENTS).");
w!("#[derive(Debug, Clone, Copy, PartialEq, Eq)]");
w!("pub struct ComponentInfo {{");
w!(" /// e.g. \"BrickComponentType_WireGraph_Exec_Branch\".");
w!(" pub name: &'static str,");
w!(" /// Host brick asset name(s) that carry this component.");
w!(" pub bricks: &'static [&'static str],");
w!(" /// Wire input port names.");
w!(" pub inputs: &'static [&'static str],");
w!(" /// Wire output port names.");
w!(" pub outputs: &'static [&'static str],");
w!("}}");
w!();
w!("impl ComponentInfo {{");
w!(" /// The primary host brick asset (first, if any).");
w!(" pub const fn brick(&self) -> Option<&'static str> {{");
w!(" self.bricks.first().copied()");
w!(" }}");
w!("}}");
w!();
w!(
"/// Every component present in the zoo, sorted by `name` (binary-searchable)."
);
w!("pub static COMPONENTS: &[ComponentInfo] = &[");
for (name, bs, ins, outs) in &rows {
w!(
" ComponentInfo {{ name: {name:?}, bricks: &[{}], inputs: &[{}], outputs: &[{}] }},",
slice(bs),
slice(ins),
slice(outs),
);
}
w!("];");
w!();
w!("/// Look up a component by its full type name.");
w!("pub fn component(name: &str) -> Option<&'static ComponentInfo> {{");
w!(" COMPONENTS");
w!(" .binary_search_by(|c| c.name.cmp(name))");
w!(" .ok()");
w!(" .map(|i| &COMPONENTS[i])");
w!("}}");
if let Some(ref p) = out_path {
std::fs::write(p, &out)?;
eprintln!("Wrote {}", p.display());
} else {
print!("{out}");
}
eprintln!(
"Extracted {} components ({} with host bricks)",
rows.len(),
rows.iter().filter(|r| !r.1.is_empty()).count(),
);
Ok(())
}