extract_components/
extract_components.rs1use brdb::{AsBrdbValue, Brdb, IntoReader, WireChunkSoA};
14use std::collections::{BTreeMap, BTreeSet};
15use std::fmt::Write;
16use std::path::PathBuf;
17
18fn main() -> Result<(), Box<dyn std::error::Error>> {
19 let args: Vec<String> = std::env::args().collect();
20 let path = PathBuf::from(
21 args.get(1)
22 .expect("usage: extract_components <zoo.brdb> [out.rs]"),
23 );
24 let out_path = args.get(2).map(PathBuf::from);
25 let db = Brdb::open(path)?.into_reader();
26 let data = db.global_data()?;
27
28 let standalone_host_brick = brdb::assets::bricks::B_1X1F_ROUND;
31 let standalone_host: &str = standalone_host_brick.asset().as_ref();
32
33 let mut bricks: BTreeMap<u16, BTreeSet<String>> = BTreeMap::new();
37 let mut inputs: BTreeMap<u16, BTreeSet<u16>> = BTreeMap::new();
38 let mut outputs: BTreeMap<u16, BTreeSet<u16>> = BTreeMap::new();
39
40 for gid in 1..64 {
43 let chunks = match db.brick_chunk_index(gid) {
44 Ok(c) => c,
45 Err(_) => break,
46 };
47 for chunk in &chunks {
48 let bsoa = db.brick_chunk_soa(gid, chunk.index)?;
51 let pb_start = bsoa.procedural_brick_starting_index;
52 let proc_asset_by_size: Vec<u32> = bsoa
55 .brick_size_counters
56 .iter()
57 .flat_map(|c| std::iter::repeat(c.asset_index).take(c.num_sizes as usize))
58 .collect();
59 let brick_types = bsoa.brick_type_indices;
60
61 if chunk.num_components > 0 {
62 let (csoa, components) = db.component_chunk_soa(gid, chunk.index)?;
63 let brick_indices = csoa.component_brick_indices;
64 let type_indices = csoa
67 .component_type_counters
68 .iter()
69 .flat_map(|v| {
70 let ti = v.type_index as u16;
71 (0..v.num_instances).map(move |_| ti)
72 })
73 .collect::<Vec<_>>();
74 for i in 0..components.len() {
75 let comp_ty = type_indices[i];
76 inputs.entry(comp_ty).or_default();
78 outputs.entry(comp_ty).or_default();
79 let brick_index = brick_indices[i].as_brdb_u32()? as usize;
80 if let Some(&bt) = brick_types.get(brick_index) {
81 let host = if bt < pb_start {
85 data.basic_brick_asset_names.get_index(bt as usize).cloned()
86 } else {
87 proc_asset_by_size
88 .get((bt - pb_start) as usize)
89 .and_then(|&ai| {
90 data.procedural_brick_asset_names.get_index(ai as usize).cloned()
91 })
92 };
93 if let Some(host) = host {
94 if host != standalone_host {
95 bricks.entry(comp_ty).or_default().insert(host);
96 }
97 }
98 }
99 }
100 }
101
102 if chunk.num_wires > 0 {
103 let soa = db.wire_chunk_soa(gid, chunk.index)?.to_value();
104 let soa: WireChunkSoA = (&soa).try_into()?;
105 for p in &soa.local_wire_sources {
108 outputs
109 .entry(p.component_type_index)
110 .or_default()
111 .insert(p.port_index);
112 }
113 for p in &soa.remote_wire_sources {
114 outputs
115 .entry(p.component_type_index)
116 .or_default()
117 .insert(p.port_index);
118 }
119 for p in &soa.local_wire_targets {
120 inputs
121 .entry(p.component_type_index)
122 .or_default()
123 .insert(p.port_index);
124 }
125 for p in &soa.remote_wire_targets {
126 inputs
127 .entry(p.component_type_index)
128 .or_default()
129 .insert(p.port_index);
130 }
131 }
132 }
133 }
134
135 let name_of = |idx: u16| data.component_type_names.get_index(idx as usize).cloned();
137 let port_of = |idx: u16| data.component_wire_port_names.get_index(idx as usize).cloned();
138
139 let all: BTreeSet<u16> = bricks
140 .keys()
141 .chain(inputs.keys())
142 .chain(outputs.keys())
143 .copied()
144 .collect();
145
146 let mut rows: Vec<(String, Vec<String>, Vec<String>, Vec<String>)> = Vec::new();
148 for ty in all {
149 let Some(name) = name_of(ty) else { continue };
150 let mut bs: Vec<String> = bricks
151 .get(&ty)
152 .map(|s| s.iter().cloned().collect())
153 .unwrap_or_default();
154 bs.sort();
155 bs.dedup();
156 let mut ins: Vec<String> = inputs
157 .get(&ty)
158 .map(|s| s.iter().filter_map(|&p| port_of(p)).collect())
159 .unwrap_or_default();
160 ins.sort();
161 ins.dedup();
162 let mut outs: Vec<String> = outputs
163 .get(&ty)
164 .map(|s| s.iter().filter_map(|&p| port_of(p)).collect())
165 .unwrap_or_default();
166 outs.sort();
167 outs.dedup();
168 rows.push((name, bs, ins, outs));
169 }
170 rows.sort_by(|a, b| a.0.cmp(&b.0));
171
172 let mut out = String::new();
173 macro_rules! w {
174 ($($t:tt)*) => { writeln!(out, $($t)*).unwrap() };
175 }
176 let slice = |v: &[String]| {
177 v.iter()
178 .map(|s| format!("{s:?}"))
179 .collect::<Vec<_>>()
180 .join(", ")
181 };
182
183 w!("// Autogenerated from a zoo save:");
184 w!("// cargo run --example extract_components -- <zoo.brdb> src/assets/component_catalog.rs");
185 w!("// Do not edit by hand.");
186 w!();
187 w!("/// Per-component catalog entry: the full component type name, its host");
188 w!("/// brick asset(s), and its wire input/output port names. Extracted from a");
189 w!("/// fully-placed, fully-wired \"zoo\" save (mirrors brs-js's COMPONENTS).");
190 w!("#[derive(Debug, Clone, Copy, PartialEq, Eq)]");
191 w!("pub struct ComponentInfo {{");
192 w!(" /// e.g. \"BrickComponentType_WireGraph_Exec_Branch\".");
193 w!(" pub name: &'static str,");
194 w!(" /// Host brick asset name(s) that carry this component.");
195 w!(" pub bricks: &'static [&'static str],");
196 w!(" /// Wire input port names.");
197 w!(" pub inputs: &'static [&'static str],");
198 w!(" /// Wire output port names.");
199 w!(" pub outputs: &'static [&'static str],");
200 w!("}}");
201 w!();
202 w!("impl ComponentInfo {{");
203 w!(" /// The primary host brick asset (first, if any).");
204 w!(" pub const fn brick(&self) -> Option<&'static str> {{");
205 w!(" self.bricks.first().copied()");
206 w!(" }}");
207 w!("}}");
208 w!();
209 w!(
210 "/// Every component present in the zoo, sorted by `name` (binary-searchable)."
211 );
212 w!("pub static COMPONENTS: &[ComponentInfo] = &[");
213 for (name, bs, ins, outs) in &rows {
214 w!(
215 " ComponentInfo {{ name: {name:?}, bricks: &[{}], inputs: &[{}], outputs: &[{}] }},",
216 slice(bs),
217 slice(ins),
218 slice(outs),
219 );
220 }
221 w!("];");
222 w!();
223 w!("/// Look up a component by its full type name.");
224 w!("pub fn component(name: &str) -> Option<&'static ComponentInfo> {{");
225 w!(" COMPONENTS");
226 w!(" .binary_search_by(|c| c.name.cmp(name))");
227 w!(" .ok()");
228 w!(" .map(|i| &COMPONENTS[i])");
229 w!("}}");
230
231 if let Some(ref p) = out_path {
232 std::fs::write(p, &out)?;
233 eprintln!("Wrote {}", p.display());
234 } else {
235 print!("{out}");
236 }
237 eprintln!(
238 "Extracted {} components ({} with host bricks)",
239 rows.len(),
240 rows.iter().filter(|r| !r.1.is_empty()).count(),
241 );
242 Ok(())
243}