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)?;
69 let brick_indices = csoa.component_brick_indices;
70 let type_indices = csoa
73 .component_type_counters
74 .iter()
75 .flat_map(|v| {
76 let ti = v.type_index as u16;
77 (0..v.num_instances).map(move |_| ti)
78 })
79 .collect::<Vec<_>>();
80 for i in 0..type_indices.len() {
81 let comp_ty = type_indices[i];
82 inputs.entry(comp_ty).or_default();
84 outputs.entry(comp_ty).or_default();
85 let brick_index = brick_indices[i].as_brdb_u32()? as usize;
86 if let Some(&bt) = brick_types.get(brick_index) {
87 let host = if bt < pb_start {
91 data.basic_brick_asset_names.get_index(bt as usize).cloned()
92 } else {
93 proc_asset_by_size
94 .get((bt - pb_start) as usize)
95 .and_then(|&ai| {
96 data.procedural_brick_asset_names.get_index(ai as usize).cloned()
97 })
98 };
99 if let Some(host) = host {
100 if host != standalone_host {
101 bricks.entry(comp_ty).or_default().insert(host);
102 }
103 }
104 }
105 }
106 }
107
108 if chunk.num_wires > 0 {
109 let soa = db.wire_chunk_soa(gid, chunk.index)?.to_value();
110 let soa: WireChunkSoA = (&soa).try_into()?;
111 for p in &soa.local_wire_sources {
114 outputs
115 .entry(p.component_type_index)
116 .or_default()
117 .insert(p.port_index);
118 }
119 for p in &soa.remote_wire_sources {
120 outputs
121 .entry(p.component_type_index)
122 .or_default()
123 .insert(p.port_index);
124 }
125 for p in &soa.local_wire_targets {
126 inputs
127 .entry(p.component_type_index)
128 .or_default()
129 .insert(p.port_index);
130 }
131 for p in &soa.remote_wire_targets {
132 inputs
133 .entry(p.component_type_index)
134 .or_default()
135 .insert(p.port_index);
136 }
137 }
138 }
139 }
140
141 let name_of = |idx: u16| data.component_type_names.get_index(idx as usize).cloned();
143 let port_of = |idx: u16| data.component_wire_port_names.get_index(idx as usize).cloned();
144
145 let all: BTreeSet<u16> = bricks
146 .keys()
147 .chain(inputs.keys())
148 .chain(outputs.keys())
149 .copied()
150 .collect();
151
152 let mut rows: Vec<(String, Vec<String>, Vec<String>, Vec<String>)> = Vec::new();
154 for ty in all {
155 let Some(name) = name_of(ty) else { continue };
156 let mut bs: Vec<String> = bricks
157 .get(&ty)
158 .map(|s| s.iter().cloned().collect())
159 .unwrap_or_default();
160 bs.sort();
161 bs.dedup();
162 let mut ins: Vec<String> = inputs
163 .get(&ty)
164 .map(|s| s.iter().filter_map(|&p| port_of(p)).collect())
165 .unwrap_or_default();
166 ins.sort();
167 ins.dedup();
168 let mut outs: Vec<String> = outputs
169 .get(&ty)
170 .map(|s| s.iter().filter_map(|&p| port_of(p)).collect())
171 .unwrap_or_default();
172 outs.sort();
173 outs.dedup();
174 rows.push((name, bs, ins, outs));
175 }
176 rows.sort_by(|a, b| a.0.cmp(&b.0));
177
178 let mut out = String::new();
179 macro_rules! w {
180 ($($t:tt)*) => { writeln!(out, $($t)*).unwrap() };
181 }
182 let slice = |v: &[String]| {
183 v.iter()
184 .map(|s| format!("{s:?}"))
185 .collect::<Vec<_>>()
186 .join(", ")
187 };
188
189 w!("// Autogenerated from a zoo save:");
190 w!("// cargo run --example extract_components -- <zoo.brdb> src/assets/component_catalog.rs");
191 w!("// Do not edit by hand.");
192 w!();
193 w!("/// Per-component catalog entry: the full component type name, its host");
194 w!("/// brick asset(s), and its wire input/output port names. Extracted from a");
195 w!("/// fully-placed, fully-wired \"zoo\" save (mirrors brs-js's COMPONENTS).");
196 w!("#[derive(Debug, Clone, Copy, PartialEq, Eq)]");
197 w!("pub struct ComponentInfo {{");
198 w!(" /// e.g. \"BrickComponentType_WireGraph_Exec_Branch\".");
199 w!(" pub name: &'static str,");
200 w!(" /// Host brick asset name(s) that carry this component.");
201 w!(" pub bricks: &'static [&'static str],");
202 w!(" /// Wire input port names.");
203 w!(" pub inputs: &'static [&'static str],");
204 w!(" /// Wire output port names.");
205 w!(" pub outputs: &'static [&'static str],");
206 w!("}}");
207 w!();
208 w!("impl ComponentInfo {{");
209 w!(" /// The primary host brick asset (first, if any).");
210 w!(" pub const fn brick(&self) -> Option<&'static str> {{");
211 w!(" self.bricks.first().copied()");
212 w!(" }}");
213 w!("}}");
214 w!();
215 w!(
216 "/// Every component present in the zoo, sorted by `name` (binary-searchable)."
217 );
218 w!("pub static COMPONENTS: &[ComponentInfo] = &[");
219 for (name, bs, ins, outs) in &rows {
220 w!(
221 " ComponentInfo {{ name: {name:?}, bricks: &[{}], inputs: &[{}], outputs: &[{}] }},",
222 slice(bs),
223 slice(ins),
224 slice(outs),
225 );
226 }
227 w!("];");
228 w!();
229 w!("/// Look up a component by its full type name.");
230 w!("pub fn component(name: &str) -> Option<&'static ComponentInfo> {{");
231 w!(" COMPONENTS");
232 w!(" .binary_search_by(|c| c.name.cmp(name))");
233 w!(" .ok()");
234 w!(" .map(|i| &COMPONENTS[i])");
235 w!("}}");
236
237 if let Some(ref p) = out_path {
238 std::fs::write(p, &out)?;
239 eprintln!("Wrote {}", p.display());
240 } else {
241 print!("{out}");
242 }
243 eprintln!(
244 "Extracted {} components ({} with host bricks)",
245 rows.len(),
246 rows.iter().filter(|r| !r.1.is_empty()).count(),
247 );
248 Ok(())
249}