Skip to main content

extract_defaults/
extract_defaults.rs

1use brdb::{AsBrdbValue, Brdb, IntoReader};
2use brdb::schema::{BrdbSchema, BrdbSchemaStructProperty, WireVariant};
3use std::collections::{BTreeMap, BTreeSet};
4use std::path::PathBuf;
5
6fn format_value(
7    schema: &BrdbSchema,
8    ty_str: &str,
9    val: &dyn AsBrdbValue,
10) -> Option<String> {
11    let b = |s: String| format!("Box::new({s}) as Box<dyn AsBrdbValue>");
12    match ty_str {
13        "bool" => Some(b(format!("{}", val.as_brdb_bool().ok()?))),
14        "u8" => Some(b(format!("{}u8", val.as_brdb_u8().ok()?))),
15        "u16" => Some(b(format!("{}u16", val.as_brdb_u16().ok()?))),
16        "u32" => Some(b(format!("{}u32", val.as_brdb_u32().ok()?))),
17        "u64" => Some(b(format!("{}u64", val.as_brdb_u64().ok()?))),
18        "i8" => Some(b(format!("{}i8", val.as_brdb_i8().ok()?))),
19        "i16" => Some(b(format!("{}i16", val.as_brdb_i16().ok()?))),
20        "i32" => Some(b(format!("{}i32", val.as_brdb_i32().ok()?))),
21        "i64" => Some(b(format!("{}i64", val.as_brdb_i64().ok()?))),
22        "f32" => Some(b(format!("{:?}f32", val.as_brdb_f32().ok()?))),
23        "f64" => Some(b(format!("{:?}f64", val.as_brdb_f64().ok()?))),
24        "str" => Some(b(format!("String::from({:?})", val.as_brdb_str().ok()?))),
25        "bundle_path_ref" => Some(b(format!("String::from({:?})", val.as_brdb_str().unwrap_or("")))),
26        "wire_graph_variant" | "wire_graph_prim_math_variant" => {
27            let wv = match val.as_brdb_wire_variant().ok()? {
28                WireVariant::Number(n) => format!("WireVariant::Number({n:?})"),
29                WireVariant::Int(n) => format!("WireVariant::Int({n})"),
30                WireVariant::Bool(b) => format!("WireVariant::Bool({b})"),
31                WireVariant::Object(o) => format!("WireVariant::Object({o:?})"),
32                WireVariant::Exec => "WireVariant::Exec".into(),
33                WireVariant::Vector(v) => format!(
34                    "WireVariant::Vector(Vector3f {{ x: {:?}, y: {:?}, z: {:?} }})",
35                    v.x, v.y, v.z
36                ),
37                WireVariant::Str(s) => format!("WireVariant::Str({s:?}.into())"),
38                WireVariant::Rotator { pitch, yaw, roll } => format!(
39                    "WireVariant::Rotator {{ pitch: {pitch:?}, yaw: {yaw:?}, roll: {roll:?} }}"
40                ),
41                WireVariant::Quat { x, y, z, w } => format!(
42                    "WireVariant::Quat {{ x: {x:?}, y: {y:?}, z: {z:?}, w: {w:?} }}"
43                ),
44                WireVariant::LinearColor { r, g, b, a } => format!(
45                    "WireVariant::LinearColor {{ r: {r:?}f32, g: {g:?}f32, b: {b:?}f32, a: {a:?}f32 }}"
46                ),
47            };
48            Some(b(wv))
49        }
50        "class" | "object" => None, // asset refs are context-dependent
51        other => {
52            if schema.get_enum(other).is_some() {
53                Some(b(format!("{}u8", val.as_brdb_u8().unwrap_or(0))))
54            } else if other == "Color" {
55                let s_id = schema.intern.get(other)?;
56                let get_u8 = |field: &str| -> u8 {
57                    let fid = schema.intern.get(field).unwrap();
58                    val.as_brdb_struct_prop_value(schema, s_id, fid)
59                        .ok()
60                        .and_then(|v| v.as_brdb_u8().ok())
61                        .unwrap_or(0)
62                };
63                let r = get_u8("R"); let g = get_u8("G"); let b_ = get_u8("B"); let a = get_u8("A");
64                Some(b(format!("SavedBrickColor {{ r: {r}, g: {g}, b: {b_}, a: {a} }}")))
65            } else if let Some(s_ty) = schema.get_struct(other) {
66                // Generic struct — recurse into fields and emit a NestedStructDefault so nested
67                // struct values (Vector2D {X,Y}, LinearColor {R,G,B,A}, ...) survive into
68                // STRUCT_DEFAULTS instead of being written as zeros for unset fields.
69                let s_id = schema.intern.get(other)?;
70                let mut parts = Vec::new();
71                for (field_id, prop_ty) in s_ty {
72                    let field_name = field_id.get(schema)?;
73                    let inner_ty = match prop_ty {
74                        BrdbSchemaStructProperty::Type(t) => schema.intern.lookup_ref(*t)?,
75                        _ => continue,
76                    };
77                    let prop_val = val.as_brdb_struct_prop_value(schema, s_id, *field_id).ok()?;
78                    // Skip a sub-field we can't format (e.g. an object/class ref) but keep the
79                    // rest — a missing field falls back to zero at write time.
80                    let Some(formatted) = format_value(schema, &inner_ty, prop_val) else {
81                        continue;
82                    };
83                    // First field keeps the `as Box<dyn AsBrdbValue>` cast to fix the vec's
84                    // element type; the rest coerce to it.
85                    if parts.is_empty() {
86                        parts.push(format!("(\"{field_name}\", {formatted})"));
87                    } else {
88                        let short = formatted.trim_end_matches(" as Box<dyn AsBrdbValue>");
89                        parts.push(format!("(\"{field_name}\", {short})"));
90                    }
91                }
92                if parts.is_empty() {
93                    None
94                } else {
95                    Some(b(format!("NestedStructDefault(vec![{}])", parts.join(", "))))
96                }
97            } else {
98                None
99            }
100        }
101    }
102}
103
104fn main() -> Result<(), Box<dyn std::error::Error>> {
105    let args: Vec<String> = std::env::args().collect();
106    let path = PathBuf::from(args.get(1).expect("usage: extract_defaults <dump.brdb> [output.rs]"));
107    let output_path = args.get(2).map(PathBuf::from);
108    let db = Brdb::open(path)?.into_reader();
109    let global_data = db.global_data()?;
110    let schema = db.components_schema()?;
111
112    let mut type_to_struct: BTreeMap<String, String> = BTreeMap::new();
113    let mut wire_ports: BTreeSet<String> = BTreeSet::new();
114    let mut struct_defaults: BTreeMap<String, Vec<(String, String, String)>> = BTreeMap::new();
115
116    for (i, type_name) in global_data.component_type_names.iter().enumerate() {
117        if let Some(struct_name) = global_data.component_data_struct_names.get(i) {
118            if struct_name != "None" {
119                type_to_struct.insert(type_name.clone(), struct_name.clone());
120            }
121        }
122    }
123
124    for port in &global_data.component_wire_port_names {
125        wire_ports.insert(port.clone());
126    }
127
128    for chunk in db.brick_chunk_index(1)? {
129        if chunk.num_components == 0 {
130            continue;
131        }
132        let Ok((_soa, components)) = db.component_chunk(1, *chunk) else {
133            continue;
134        };
135        for s in components {
136            let name = s.get_name().to_owned();
137            if struct_defaults.contains_key(&name) {
138                continue;
139            }
140
141            let Some(struct_def) = schema.get_struct(&name) else {
142                continue;
143            };
144            let s_id = match schema.intern.get(&name) {
145                Some(id) => id,
146                None => continue,
147            };
148
149            let mut fields = Vec::new();
150            for (field_id, prop_ty) in struct_def {
151                let field_name = match field_id.get(&schema) {
152                    Some(n) => n.to_owned(),
153                    None => continue,
154                };
155                let ty_str = match prop_ty {
156                    BrdbSchemaStructProperty::Type(t) => {
157                        match schema.intern.lookup_ref(*t) {
158                            Some(s) => s.to_owned(),
159                            None => continue,
160                        }
161                    }
162                    _ => continue,
163                };
164                let val = match s.as_brdb_struct_prop_value(&schema, s_id, *field_id) {
165                    Ok(v) => v,
166                    Err(_) => continue,
167                };
168                let formatted = match format_value(&schema, &ty_str, val) {
169                    Some(f) => f,
170                    None => continue,
171                };
172                fields.push((field_name, ty_str, formatted));
173            }
174            struct_defaults.insert(name, fields);
175        }
176    }
177
178    use std::fmt::Write;
179    let mut out = String::new();
180    macro_rules! w { ($($t:tt)*) => { writeln!(out, $($t)*).unwrap() } }
181
182    w!("// Autogenerated from: cargo run --example extract_defaults -- path/to/dump.brdb");
183    w!();
184
185    w!("pub static COMPONENT_TYPE_STRUCT_PAIRS: &[(&str, &str)] = &[");
186    for (type_name, struct_name) in &type_to_struct {
187        w!("    (\"{type_name}\", \"{struct_name}\"),");
188    }
189    w!("];");
190    w!();
191
192    w!("pub static WIRE_PORT_NAMES: &[&str] = &[");
193    for port in &wire_ports {
194        w!("    \"{port}\",");
195    }
196    w!("];");
197    w!();
198
199    w!("pub static ENTITY_TYPE_STRUCT_PAIRS: &[(&str, &str)] = &[");
200    for (i, type_name) in global_data.entity_type_names.iter().enumerate() {
201        if let Some(class_name) = global_data.entity_data_class_names.get_index(i) {
202            w!("    (\"{type_name}\", \"{class_name}\"),");
203        }
204    }
205    w!("];");
206    w!();
207
208    w!("use std::sync::LazyLock;");
209    // WireVariant is only referenced when a dump carries wire-variant defaults;
210    // allow it to be unused so a dump without any still compiles clean.
211    w!("#[allow(unused_imports)]");
212    w!("use crate::schema::WireVariant;");
213    w!("use crate::schema::as_brdb::AsBrdbValue;");
214    // NestedStructDefault carries struct-typed defaults (Vector2D/LinearColor/...); allow it
215    // to be unused so a dump without any nested-struct defaults still compiles clean.
216    w!("#[allow(unused_imports)]");
217    w!("use crate::schema::as_brdb::NestedStructDefault;");
218    w!("use crate::SavedBrickColor;");
219    w!();
220    w!("/// Default field values for every component data struct.");
221    w!("pub static STRUCT_DEFAULTS: LazyLock<Vec<(&'static str, Vec<(&'static str, Box<dyn AsBrdbValue>)>)>> =");
222    w!("    LazyLock::new(|| vec![");
223    let mut first_entry = true;
224    for (name, fields) in &struct_defaults {
225        if fields.is_empty() {
226            continue;
227        }
228        w!("        (\"{name}\", vec![");
229        for (field_name, _ty, val) in fields {
230            if first_entry {
231                w!("            (\"{field_name}\", {val}),");
232                first_entry = false;
233            } else {
234                let short = val.trim_end_matches(" as Box<dyn AsBrdbValue>");
235                w!("            (\"{field_name}\", {short}),");
236            }
237        }
238        w!("        ]),");
239    }
240    w!("    ]);");
241
242    if let Some(ref p) = output_path {
243        std::fs::write(p, &out)?;
244        eprintln!("Wrote {}", p.display());
245    } else {
246        print!("{out}");
247    }
248
249    eprintln!(
250        "Extracted: {} type mappings, {} wire ports, {} struct defaults, {} entity types",
251        type_to_struct.len(),
252        wire_ports.len(),
253        struct_defaults.len(),
254        global_data.entity_type_names.len(),
255    );
256
257    Ok(())
258}