Skip to main content

brdb/schema/
value.rs

1use std::{collections::HashMap, fmt::Display, hash::Hash, sync::Arc};
2
3use indexmap::IndexMap;
4
5use crate::{
6    errors::BrdbSchemaError,
7    schema::{BrdbInterned, BrdbSchema, as_brdb::AsBrdbValue},
8    wrapper::Vector3f,
9};
10
11#[derive(Clone, Debug)]
12pub struct BrdbEnum {
13    pub(crate) schema: Arc<BrdbSchema>,
14    pub name: BrdbInterned,
15    pub value: u64,
16}
17
18#[derive(Clone, Debug)]
19pub struct BrdbStruct {
20    pub(crate) schema: Arc<BrdbSchema>,
21    pub name: BrdbInterned,
22    pub properties: HashMap<BrdbInterned, BrdbValue>,
23}
24
25impl BrdbStruct {
26    pub fn get(&self, prop: impl AsRef<str>) -> Option<&BrdbValue> {
27        let key = self.schema.intern.get(prop.as_ref())?;
28        self.properties.get(&key)
29    }
30
31    pub fn contains_key(&self, prop: impl AsRef<str>) -> bool {
32        let Some(key) = self.schema.intern.get(prop.as_ref()) else {
33            return false;
34        };
35        self.properties.contains_key(&key)
36    }
37
38    pub fn get_name(&self) -> &str {
39        self.schema
40            .intern
41            .lookup_ref(self.name)
42            .unwrap_or("unknown")
43    }
44
45    pub fn prop(&self, prop: impl AsRef<str>) -> Result<&BrdbValue, BrdbSchemaError> {
46        let prop = prop.as_ref();
47        self.get(prop).ok_or_else(|| {
48            BrdbSchemaError::MissingStructField(
49                self.schema
50                    .intern
51                    .lookup(self.name)
52                    .unwrap_or_else(|| "unknown struct".to_string()),
53                prop.to_owned(),
54            )
55        })
56    }
57
58    pub fn set_prop(
59        &mut self,
60        prop: impl AsRef<str>,
61        value: BrdbValue,
62    ) -> Result<(), BrdbSchemaError> {
63        let prop = prop.as_ref();
64        let key = self.schema.intern.get(prop).ok_or_else(|| {
65            BrdbSchemaError::MissingStructField(
66                self.schema
67                    .intern
68                    .lookup(self.name)
69                    .unwrap_or_else(|| "unknown struct".to_string()),
70                prop.to_owned(),
71            )
72        })?;
73        // ensure prop exists
74        if !self.properties.contains_key(&key) {
75            return Err(BrdbSchemaError::MissingStructField(
76                self.schema
77                    .intern
78                    .lookup(self.name)
79                    .unwrap_or_else(|| "unknown struct".to_string()),
80                prop.to_owned(),
81            ));
82        }
83        self.properties.insert(key, value);
84        Ok(())
85    }
86
87    pub fn as_hashmap(&self) -> Result<HashMap<String, Box<dyn AsBrdbValue>>, BrdbSchemaError> {
88        let mut map = HashMap::new();
89        for (k, v) in &self.properties {
90            let key = k
91                .get_ok(&self.schema, || {
92                    BrdbSchemaError::MissingStructField(
93                        self.name.get_or(&self.schema, "unknown struct").to_string(),
94                        "unknown_prop".to_string(),
95                    )
96                })?
97                .to_string();
98            map.insert(key, Box::new(v.clone()) as Box<dyn AsBrdbValue>);
99        }
100        Ok(map)
101    }
102
103    pub fn to_value(self) -> BrdbValue {
104        BrdbValue::Struct(Box::new(self.clone()))
105    }
106}
107
108impl From<BrdbStruct> for BrdbValue {
109    fn from(value: BrdbStruct) -> Self {
110        BrdbValue::Struct(Box::new(value))
111    }
112}
113
114impl Display for BrdbStruct {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        let mut props = self
117            .properties
118            .iter()
119            .map(|(k, v)| {
120                format!(
121                    "  {}: {},\n",
122                    k.get_or(&self.schema, "unknown_prop"),
123                    v.display_inner(&self.schema, 1)
124                )
125            })
126            .collect::<Vec<_>>();
127        props.sort();
128        write!(
129            f,
130            "{} {{\n{}}}",
131            self.name.get_or(&self.schema, "unknown struct"),
132            props.join("")
133        )
134    }
135}
136
137impl BrdbEnum {
138    pub fn get_value_raw(&self) -> u64 {
139        self.value
140    }
141
142    pub fn get_name(&self) -> &str {
143        self.schema
144            .intern
145            .lookup_ref(self.name)
146            .unwrap_or("unknown")
147    }
148
149    pub fn get_value(&self) -> String {
150        self.schema
151            .intern
152            .lookup(self.name)
153            .unwrap_or_else(|| "unknown".to_string())
154    }
155}
156
157#[derive(Clone, Debug)]
158pub enum BrdbValue {
159    Nil,
160    Bool(bool),
161    U8(u8),
162    U16(u16),
163    U32(u32),
164    U64(u64),
165    I8(i8),
166    I16(i16),
167    I32(i32),
168    I64(i64),
169    F32(f32),
170    F64(f64),
171    String(String),
172    Asset(Option<usize>),
173    Enum(BrdbEnum),
174    Struct(Box<BrdbStruct>),
175    Array(Vec<BrdbValue>),
176    FlatArray(Vec<BrdbValue>),
177    Map(IndexMap<BrdbValue, BrdbValue>),
178    WireVar(WireVariant),
179}
180
181/// A wire graph variant value. The members mirror the engine's
182/// `WireGraphVariant` union (`f64`, `i64`, `bool`, `weak_object`,
183/// `WireGraphExec`, `Vector`, `Rotator`, `Quat`, `str`, `LinearColor`);
184/// `WireGraphPrimMathVariant` uses the numeric/spatial subset.
185#[derive(Clone, Debug)]
186pub enum WireVariant {
187    Number(f64),
188    Int(i64),
189    Bool(bool),
190    /// `weak_object` member: an object/asset reference, stored as an index into
191    /// `global_data.external_asset_references` (`None` is a null reference).
192    Object(Option<usize>),
193    Exec,
194    /// `Vector` member (X, Y, Z).
195    Vector(Vector3f),
196    /// `Rotator` member (Pitch, Yaw, Roll — f64 degrees).
197    Rotator { pitch: f64, yaw: f64, roll: f64 },
198    /// `Quat` member (X, Y, Z, W — f64).
199    Quat { x: f64, y: f64, z: f64, w: f64 },
200    /// `str` member: a string value.
201    Str(String),
202    /// `LinearColor` member (R, G, B, A — f32, linear 0–1).
203    LinearColor { r: f32, g: f32, b: f32, a: f32 },
204}
205impl Default for WireVariant {
206    fn default() -> Self {
207        WireVariant::Number(0.0)
208    }
209}
210impl Display for WireVariant {
211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212        match self {
213            WireVariant::Number(n) => write!(f, "{n}"),
214            WireVariant::Int(i) => write!(f, "{i}"),
215            WireVariant::Bool(b) => write!(f, "{b}"),
216            WireVariant::Object(Some(i)) => write!(f, "obj#{i}"),
217            WireVariant::Object(None) => write!(f, "obj#null"),
218            WireVariant::Exec => write!(f, "exec"),
219            WireVariant::Vector(v) => write!(f, "({}, {}, {})", v.x, v.y, v.z),
220            WireVariant::Rotator { pitch, yaw, roll } => {
221                write!(f, "rot({pitch}, {yaw}, {roll})")
222            }
223            WireVariant::Quat { x, y, z, w } => write!(f, "quat({x}, {y}, {z}, {w})"),
224            WireVariant::Str(s) => write!(f, "{s:?}"),
225            WireVariant::LinearColor { r, g, b, a } => {
226                write!(f, "color({r}, {g}, {b}, {a})")
227            }
228        }
229    }
230}
231impl From<f64> for WireVariant {
232    fn from(value: f64) -> Self {
233        WireVariant::Number(value)
234    }
235}
236impl From<f32> for WireVariant {
237    fn from(value: f32) -> Self {
238        WireVariant::Number(value as f64)
239    }
240}
241
242macro_rules! wire_var_int {
243    ($ty:ty) => {
244        impl From<$ty> for WireVariant {
245            fn from(value: $ty) -> Self {
246                WireVariant::Int(value as i64)
247            }
248        }
249    };
250    ($ty:ty, $($rest:ty),*) => {
251        wire_var_int!($ty);
252        wire_var_int!($($rest),*);
253    };
254}
255wire_var_int!(i8, i16, i32, i64, u8, u16, u32, u64);
256
257impl From<bool> for WireVariant {
258    fn from(value: bool) -> Self {
259        WireVariant::Bool(value)
260    }
261}
262impl From<String> for WireVariant {
263    fn from(value: String) -> Self {
264        WireVariant::Str(value)
265    }
266}
267impl From<&str> for WireVariant {
268    fn from(value: &str) -> Self {
269        WireVariant::Str(value.to_string())
270    }
271}
272impl From<Vector3f> for WireVariant {
273    fn from(value: Vector3f) -> Self {
274        WireVariant::Vector(value)
275    }
276}
277
278/// Convert a decoded value back into a `WireVariant`. Named variants decode to
279/// raw `BrdbValue`s (the tag is consumed on read); legacy variants decode to a
280/// `WireVar`. Both round-trip here.
281impl TryFrom<&BrdbValue> for WireVariant {
282    type Error = BrdbSchemaError;
283    fn try_from(value: &BrdbValue) -> Result<Self, Self::Error> {
284        Ok(match value {
285            BrdbValue::WireVar(v) => v.clone(),
286            BrdbValue::F64(n) => WireVariant::Number(*n),
287            BrdbValue::F32(n) => WireVariant::Number(*n as f64),
288            BrdbValue::I64(i) => WireVariant::Int(*i),
289            BrdbValue::I32(i) => WireVariant::Int(*i as i64),
290            BrdbValue::Bool(b) => WireVariant::Bool(*b),
291            BrdbValue::String(s) => WireVariant::Str(s.clone()),
292            BrdbValue::Asset(opt) => WireVariant::Object(*opt),
293            BrdbValue::Struct(s) if s.get_name() == "Vector" => {
294                WireVariant::Vector(Vector3f::try_from(value)?)
295            }
296            other => {
297                return Err(BrdbSchemaError::ExpectedType(
298                    "wire variant".to_owned(),
299                    other.get_type().to_owned(),
300                ));
301            }
302        })
303    }
304}
305
306/// A wire graph array variant value, mirroring the engine's
307/// `WireGraphArrayVariant` union (one array member per element type). Each
308/// member is a `WireGraph*Array` struct holding a single `Values` array.
309#[derive(Clone, Debug, PartialEq)]
310pub enum WireArrayVariant {
311    /// `WireGraphDoubleArray`
312    DoubleArray(Vec<f64>),
313    /// `WireGraphInt64Array`
314    Int64Array(Vec<i64>),
315    /// `WireGraphBoolArray`
316    BoolArray(Vec<bool>),
317    /// `WireGraphObjectArray` (weak_object references, as asset indices)
318    ObjectArray(Vec<Option<usize>>),
319    /// `WireGraphVectorArray`
320    VectorArray(Vec<Vector3f>),
321    /// `WireGraphRotatorArray` — (Pitch, Yaw, Roll) f64 elements.
322    RotatorArray(Vec<(f64, f64, f64)>),
323    /// `WireGraphQuatArray` — (X, Y, Z, W) f64 elements.
324    QuatArray(Vec<(f64, f64, f64, f64)>),
325    /// `WireGraphStringArray`
326    StringArray(Vec<String>),
327    /// `WireGraphLinearColorArray` — (R, G, B, A) f32 elements, linear 0–1.
328    LinearColorArray(Vec<(f32, f32, f32, f32)>),
329}
330
331impl WireArrayVariant {
332    /// The `WireGraph*Array` struct name this array maps to.
333    pub fn member_type(&self) -> &'static str {
334        match self {
335            WireArrayVariant::DoubleArray(_) => "WireGraphDoubleArray",
336            WireArrayVariant::Int64Array(_) => "WireGraphInt64Array",
337            WireArrayVariant::BoolArray(_) => "WireGraphBoolArray",
338            WireArrayVariant::ObjectArray(_) => "WireGraphObjectArray",
339            WireArrayVariant::VectorArray(_) => "WireGraphVectorArray",
340            WireArrayVariant::RotatorArray(_) => "WireGraphRotatorArray",
341            WireArrayVariant::QuatArray(_) => "WireGraphQuatArray",
342            WireArrayVariant::StringArray(_) => "WireGraphStringArray",
343            WireArrayVariant::LinearColorArray(_) => "WireGraphLinearColorArray",
344        }
345    }
346}
347
348/// Key type of a `WireGraphMapVariant`. The schema nests
349/// `WireGraphMapVariant` -> `WireGraphMapKeyWrapper_<K>` -> (per-value variant).
350#[derive(Clone, Copy, Debug, PartialEq, Eq)]
351pub enum WireMapKey {
352    /// `int64`
353    Int64,
354    /// `FWireGraphString`
355    Str,
356    /// `FWeakObjectPtr`
357    Object,
358}
359
360/// Value type of a `WireGraphMapVariant` (the inner `WireGraphMap_<K>_<V>`).
361#[derive(Clone, Copy, Debug, PartialEq, Eq)]
362pub enum WireMapValue {
363    /// `double`
364    Number,
365    /// `int64`
366    Int64,
367    /// `bool`
368    Bool,
369    /// `FWeakObjectPtr`
370    Object,
371    /// `FVector`
372    Vector,
373    /// `FRotator`
374    Rotator,
375    /// `FQuat`
376    Quat,
377    /// `FWireGraphString`
378    Str,
379    /// `FLinearColor`
380    LinearColor,
381}
382
383/// Concrete key data for a populated `WireGraphMapVariant` entry.
384#[derive(Clone, Debug, PartialEq)]
385pub enum WireMapKeyData {
386    Int64(i64),
387    Str(String),
388    /// weak-object asset index (`None` = null → -1).
389    Object(Option<u32>),
390}
391
392/// Concrete value data for a populated `WireGraphMapVariant` entry.
393#[derive(Clone, Debug, PartialEq)]
394pub enum WireMapValueData {
395    Number(f64),
396    Int64(i64),
397    Bool(bool),
398    Str(String),
399    Vector(Vector3f),
400    Rotator(f64, f64, f64),
401    Quat(f64, f64, f64, f64),
402    LinearColor(f32, f32, f32, f32),
403    Object(Option<u32>),
404}
405
406// --- WireVariant <-> map key/value data ---
407//
408// The map data types are (almost) a subset of `WireVariant`, so map-data ->
409// `WireVariant` is exact and lossless. The reverse is lossy only where a
410// `WireVariant` carries a kind the target can't hold; those degenerate inputs
411// fall back (documented per arm) rather than failing, so these stay infallible
412// `From` conversions.
413
414impl From<WireVariant> for WireMapValueData {
415    fn from(v: WireVariant) -> Self {
416        match v {
417            WireVariant::Number(f) => WireMapValueData::Number(f),
418            WireVariant::Int(i) => WireMapValueData::Int64(i),
419            WireVariant::Bool(b) => WireMapValueData::Bool(b),
420            WireVariant::Str(s) => WireMapValueData::Str(s),
421            WireVariant::Object(o) => WireMapValueData::Object(o.map(|i| i as u32)),
422            WireVariant::Vector(vec) => WireMapValueData::Vector(vec),
423            WireVariant::Rotator { pitch, yaw, roll } => {
424                WireMapValueData::Rotator(pitch, yaw, roll)
425            }
426            WireVariant::Quat { x, y, z, w } => WireMapValueData::Quat(x, y, z, w),
427            WireVariant::LinearColor { r, g, b, a } => WireMapValueData::LinearColor(r, g, b, a),
428            // A map value is never `Exec`; fall back to a zero number.
429            WireVariant::Exec => WireMapValueData::Number(0.0),
430        }
431    }
432}
433
434impl From<WireMapValueData> for WireVariant {
435    fn from(v: WireMapValueData) -> Self {
436        match v {
437            WireMapValueData::Number(f) => WireVariant::Number(f),
438            WireMapValueData::Int64(i) => WireVariant::Int(i),
439            WireMapValueData::Bool(b) => WireVariant::Bool(b),
440            WireMapValueData::Str(s) => WireVariant::Str(s),
441            WireMapValueData::Vector(vec) => WireVariant::Vector(vec),
442            WireMapValueData::Rotator(pitch, yaw, roll) => {
443                WireVariant::Rotator { pitch, yaw, roll }
444            }
445            WireMapValueData::Quat(x, y, z, w) => WireVariant::Quat { x, y, z, w },
446            WireMapValueData::LinearColor(r, g, b, a) => WireVariant::LinearColor { r, g, b, a },
447            WireMapValueData::Object(o) => WireVariant::Object(o.map(|i| i as usize)),
448        }
449    }
450}
451
452impl From<WireVariant> for WireMapKeyData {
453    fn from(v: WireVariant) -> Self {
454        match v {
455            WireVariant::Int(i) => WireMapKeyData::Int64(i),
456            WireVariant::Str(s) => WireMapKeyData::Str(s),
457            WireVariant::Object(o) => WireMapKeyData::Object(o.map(|i| i as u32)),
458            // Only int64/str/object are valid map keys. Numeric kinds coerce to
459            // an int64 key; the rest have no key form and fall back to Int64(0).
460            WireVariant::Number(f) => WireMapKeyData::Int64(f as i64),
461            WireVariant::Bool(b) => WireMapKeyData::Int64(b as i64),
462            WireVariant::Vector(_)
463            | WireVariant::Rotator { .. }
464            | WireVariant::Quat { .. }
465            | WireVariant::LinearColor { .. }
466            | WireVariant::Exec => WireMapKeyData::Int64(0),
467        }
468    }
469}
470
471impl From<WireMapKeyData> for WireVariant {
472    fn from(k: WireMapKeyData) -> Self {
473        match k {
474            WireMapKeyData::Int64(i) => WireVariant::Int(i),
475            WireMapKeyData::Str(s) => WireVariant::Str(s),
476            WireMapKeyData::Object(o) => WireVariant::Object(o.map(|i| i as usize)),
477        }
478    }
479}
480
481/// A `WireGraphMapVariant` value. `key`/`value` pick the nested `KeyWrapper_<K>`
482/// / `Map_<K>_<V>` members; `entries` are the concrete pairs (empty = an empty
483/// map, the at-rest state of a fresh `MapVar` brick).
484#[derive(Clone, Debug, PartialEq)]
485pub struct WireMapVariant {
486    pub key: WireMapKey,
487    pub value: WireMapValue,
488    pub entries: Vec<(WireMapKeyData, WireMapValueData)>,
489}
490
491impl WireMapVariant {
492    /// The `WireGraphMapKeyWrapper_<K>` member of `WireGraphMapVariant`.
493    pub fn key_wrapper(&self) -> &'static str {
494        match self.key {
495            WireMapKey::Int64 => "WireGraphMapKeyWrapper_int64",
496            WireMapKey::Str => "WireGraphMapKeyWrapper_FWireGraphString",
497            WireMapKey::Object => "WireGraphMapKeyWrapper_FWeakObjectPtr",
498        }
499    }
500    /// The inner `WireGraphMapVariant_<K>` variant name.
501    pub fn inner_variant(&self) -> &'static str {
502        match self.key {
503            WireMapKey::Int64 => "WireGraphMapVariant_int64",
504            WireMapKey::Str => "WireGraphMapVariant_FWireGraphString",
505            WireMapKey::Object => "WireGraphMapVariant_FWeakObjectPtr",
506        }
507    }
508    /// The `WireGraphMap_<K>_<V>` struct name (the concrete keyed map).
509    pub fn map_struct(&self) -> String {
510        let k = match self.key {
511            WireMapKey::Int64 => "int64",
512            WireMapKey::Str => "FWireGraphString",
513            WireMapKey::Object => "FWeakObjectPtr",
514        };
515        let v = match self.value {
516            WireMapValue::Number => "double",
517            WireMapValue::Int64 => "int64",
518            WireMapValue::Bool => "bool",
519            WireMapValue::Object => "FWeakObjectPtr",
520            WireMapValue::Vector => "FVector",
521            WireMapValue::Rotator => "FRotator",
522            WireMapValue::Quat => "FQuat",
523            WireMapValue::Str => "FWireGraphString",
524            WireMapValue::LinearColor => "FLinearColor",
525        };
526        format!("WireGraphMap_{k}_{v}")
527    }
528}
529
530impl From<Vec<f64>> for WireArrayVariant {
531    fn from(v: Vec<f64>) -> Self {
532        WireArrayVariant::DoubleArray(v)
533    }
534}
535impl From<Vec<i64>> for WireArrayVariant {
536    fn from(v: Vec<i64>) -> Self {
537        WireArrayVariant::Int64Array(v)
538    }
539}
540impl From<Vec<bool>> for WireArrayVariant {
541    fn from(v: Vec<bool>) -> Self {
542        WireArrayVariant::BoolArray(v)
543    }
544}
545impl From<Vec<Vector3f>> for WireArrayVariant {
546    fn from(v: Vec<Vector3f>) -> Self {
547        WireArrayVariant::VectorArray(v)
548    }
549}
550impl From<Vec<String>> for WireArrayVariant {
551    fn from(v: Vec<String>) -> Self {
552        WireArrayVariant::StringArray(v)
553    }
554}
555
556/// Convert a decoded `WireGraph*Array` struct back into a `WireArrayVariant`.
557impl TryFrom<&BrdbValue> for WireArrayVariant {
558    type Error = BrdbSchemaError;
559    fn try_from(value: &BrdbValue) -> Result<Self, Self::Error> {
560        let s = value.as_struct()?;
561        let values = s.prop("Values")?.as_array()?;
562        Ok(match s.get_name() {
563            "WireGraphDoubleArray" => WireArrayVariant::DoubleArray(
564                values.iter().map(|v| v.as_brdb_f64()).collect::<Result<_, _>>()?,
565            ),
566            "WireGraphInt64Array" => WireArrayVariant::Int64Array(
567                values.iter().map(|v| v.as_brdb_i64()).collect::<Result<_, _>>()?,
568            ),
569            "WireGraphBoolArray" => WireArrayVariant::BoolArray(
570                values.iter().map(|v| v.as_brdb_bool()).collect::<Result<_, _>>()?,
571            ),
572            "WireGraphStringArray" => WireArrayVariant::StringArray(
573                values
574                    .iter()
575                    .map(|v| v.as_brdb_str().map(str::to_owned))
576                    .collect::<Result<_, _>>()?,
577            ),
578            "WireGraphVectorArray" => WireArrayVariant::VectorArray(
579                values.iter().map(Vector3f::try_from).collect::<Result<_, _>>()?,
580            ),
581            "WireGraphRotatorArray" => WireArrayVariant::RotatorArray(
582                values
583                    .iter()
584                    .map(|v| {
585                        let s = v.as_struct()?;
586                        Ok((
587                            s.prop("Pitch")?.as_brdb_f64()?,
588                            s.prop("Yaw")?.as_brdb_f64()?,
589                            s.prop("Roll")?.as_brdb_f64()?,
590                        ))
591                    })
592                    .collect::<Result<_, BrdbSchemaError>>()?,
593            ),
594            "WireGraphQuatArray" => WireArrayVariant::QuatArray(
595                values
596                    .iter()
597                    .map(|v| {
598                        let s = v.as_struct()?;
599                        Ok((
600                            s.prop("X")?.as_brdb_f64()?,
601                            s.prop("Y")?.as_brdb_f64()?,
602                            s.prop("Z")?.as_brdb_f64()?,
603                            s.prop("W")?.as_brdb_f64()?,
604                        ))
605                    })
606                    .collect::<Result<_, BrdbSchemaError>>()?,
607            ),
608            "WireGraphLinearColorArray" => WireArrayVariant::LinearColorArray(
609                values
610                    .iter()
611                    .map(|v| {
612                        let s = v.as_struct()?;
613                        Ok((
614                            s.prop("R")?.as_brdb_f32()?,
615                            s.prop("G")?.as_brdb_f32()?,
616                            s.prop("B")?.as_brdb_f32()?,
617                            s.prop("A")?.as_brdb_f32()?,
618                        ))
619                    })
620                    .collect::<Result<_, BrdbSchemaError>>()?,
621            ),
622            "WireGraphObjectArray" => WireArrayVariant::ObjectArray(
623                values
624                    .iter()
625                    .map(|v| match v {
626                        BrdbValue::Asset(opt) => Ok(*opt),
627                        other => Err(BrdbSchemaError::ExpectedType(
628                            "weak_object".to_owned(),
629                            other.get_type().to_owned(),
630                        )),
631                    })
632                    .collect::<Result<_, _>>()?,
633            ),
634            other => return Err(BrdbSchemaError::UnknownType(other.to_owned())),
635        })
636    }
637}
638
639impl BrdbValue {
640    pub fn get_type(&self) -> &'static str {
641        match self {
642            BrdbValue::Nil => "nil",
643            BrdbValue::Bool(_) => "bool",
644            BrdbValue::U8(_) => "u8",
645            BrdbValue::U16(_) => "u16",
646            BrdbValue::U32(_) => "u32",
647            BrdbValue::U64(_) => "u64",
648            BrdbValue::I8(_) => "i8",
649            BrdbValue::I16(_) => "i16",
650            BrdbValue::I32(_) => "i32",
651            BrdbValue::I64(_) => "i64",
652            BrdbValue::F32(_) => "f32",
653            BrdbValue::F64(_) => "f64",
654            BrdbValue::String(_) => "string",
655            BrdbValue::Asset(_) => "asset",
656            BrdbValue::Enum(_) => "enum",
657            BrdbValue::Struct(_) => "struct",
658            BrdbValue::Array(_) => "array",
659            BrdbValue::FlatArray(_) => "flatarray",
660            BrdbValue::Map(_) => "map",
661            BrdbValue::WireVar(_) => "wire_variant",
662        }
663    }
664    pub fn as_struct(&self) -> Result<&BrdbStruct, BrdbSchemaError> {
665        if let Self::Struct(v) = self {
666            Ok(v)
667        } else {
668            Err(BrdbSchemaError::ExpectedType(
669                "struct".to_owned(),
670                self.get_type().to_string(),
671            ))
672        }
673    }
674
675    pub fn prop(&self, prop: impl AsRef<str>) -> Result<&BrdbValue, BrdbSchemaError> {
676        let prop = prop.as_ref();
677        let s = self.as_struct()?;
678        s.get(prop).ok_or_else(|| {
679            BrdbSchemaError::MissingStructField(
680                s.schema
681                    .intern
682                    .lookup(s.name)
683                    .unwrap_or_else(|| "unknown struct".to_string()),
684                prop.to_owned(),
685            )
686        })
687    }
688
689    pub fn contains_key(&self, prop: impl AsRef<str>) -> bool {
690        let Some(s) = self.as_struct().ok() else {
691            return false;
692        };
693        s.contains_key(prop)
694    }
695
696    pub fn as_array(&self) -> Result<&Vec<BrdbValue>, BrdbSchemaError> {
697        match self {
698            Self::Array(v) | Self::FlatArray(v) => Ok(v),
699            _ => Err(BrdbSchemaError::ExpectedType(
700                "array".to_owned(),
701                self.get_type().to_string(),
702            )),
703        }
704    }
705
706    pub fn index(&self, index: usize) -> Result<Option<&BrdbValue>, BrdbSchemaError> {
707        Ok(self.as_array()?.get(index))
708    }
709
710    pub fn index_unwrap(&self, index: usize) -> Result<&BrdbValue, BrdbSchemaError> {
711        let vec = self.as_array()?;
712        Ok(vec
713            .get(index)
714            .ok_or_else(|| BrdbSchemaError::ArrayIndexOutOfBounds {
715                len: vec.len(),
716                index,
717            })?)
718    }
719
720    pub fn as_str(&self) -> Result<&str, BrdbSchemaError> {
721        if let Self::String(v) = self {
722            Ok(v)
723        } else {
724            Err(BrdbSchemaError::ExpectedType(
725                "string".to_owned(),
726                self.get_type().to_string(),
727            ))
728        }
729    }
730
731    pub fn display(&self, schema: &BrdbSchema) -> String {
732        self.display_inner(schema, 0)
733    }
734
735    fn display_inner(&self, schema: &BrdbSchema, depth: usize) -> String {
736        match self {
737            BrdbValue::Nil => "nil".to_string(),
738            BrdbValue::Bool(v) => format!("{v}"),
739            BrdbValue::U8(v) => format!("{v}u8"),
740            BrdbValue::U16(v) => format!("{v}u16"),
741            BrdbValue::U32(v) => format!("{v}u32"),
742            BrdbValue::U64(v) => format!("{v}u64"),
743            BrdbValue::I8(v) => format!("{v}i8"),
744            BrdbValue::I16(v) => format!("{v}i16"),
745            BrdbValue::I32(v) => format!("{v}i32"),
746            BrdbValue::I64(v) => format!("{v}i64"),
747            BrdbValue::F32(v) => format!("{v}f32"),
748            BrdbValue::F64(v) => format!("{v}f64"),
749            BrdbValue::WireVar(v) => match v {
750                WireVariant::Number(n) => format!("wire {n}f64"),
751                WireVariant::Int(i) => format!("wire {i}i64"),
752                WireVariant::Bool(b) => format!("wire {b}"),
753                WireVariant::Object(o) => format!("wire obj#{o:?}"),
754                WireVariant::Exec => "w exec".to_string(),
755                WireVariant::Vector(v) => format!("wire ({}, {}, {})", v.x, v.y, v.z),
756                WireVariant::Rotator { pitch, yaw, roll } => {
757                    format!("wire rot({pitch}, {yaw}, {roll})")
758                }
759                WireVariant::Quat { x, y, z, w } => format!("wire quat({x}, {y}, {z}, {w})"),
760                WireVariant::Str(s) => format!("wire {s:?}"),
761                WireVariant::LinearColor { r, g, b, a } => {
762                    format!("wire color({r}, {g}, {b}, {a})")
763                }
764            },
765            BrdbValue::String(v) => format!("\"{v}\""),
766            BrdbValue::Asset(None) => "none".to_string(),
767            BrdbValue::Asset(Some(v)) => {
768                if let Some((asset_ty, asset_name)) =
769                    schema.global_data.external_asset_references.get_index(*v)
770                {
771                    format!("{asset_ty}/{asset_name}")
772                } else {
773                    format!("unknown asset {v}")
774                }
775            }
776            BrdbValue::Enum(e) => format!("{}::{}", e.get_name(), e.get_value()),
777            BrdbValue::Struct(s) => {
778                let pad = "  ".repeat(depth);
779                let mut props = s
780                    .properties
781                    .iter()
782                    .map(|(k, v)| {
783                        format!(
784                            "{pad}  {}: {},\n",
785                            schema.intern.lookup_ref(*k).unwrap_or("unknown prop"),
786                            v.display_inner(schema, depth + 1)
787                        )
788                    })
789                    .collect::<Vec<_>>();
790                props.sort();
791                format!(
792                    "{} {{\n{}{pad}}}",
793                    schema.intern.lookup_ref(s.name).unwrap_or("unknown struct"),
794                    props.join("")
795                )
796            }
797            BrdbValue::Array(v) => {
798                let pad = "  ".repeat(depth);
799                let elems = v
800                    .iter()
801                    .map(|e| format!("{pad}  {},\n", e.display_inner(schema, depth + 1)))
802                    .collect::<Vec<_>>();
803                format!("[\n{}{}]", elems.join(""), "  ".repeat(depth))
804            }
805            BrdbValue::FlatArray(v) => {
806                let pad = "  ".repeat(depth);
807                let elems = v
808                    .iter()
809                    .map(|e| format!("{pad}  {},\n", e.display_inner(schema, depth + 1)))
810                    .collect::<Vec<_>>();
811                format!("flat[\n{}{}]", elems.join(""), "  ".repeat(depth))
812            }
813            BrdbValue::Map(map) => {
814                let pad = "  ".repeat(depth);
815                let mut entries = map
816                    .iter()
817                    .map(|(k, v)| {
818                        format!(
819                            "{pad}  {}: {},\n",
820                            k.display_inner(schema, depth + 1),
821                            v.display_inner(schema, depth + 1)
822                        )
823                    })
824                    .collect::<Vec<_>>();
825                entries.sort();
826                format!("{{\n{}\n{pad}}}", entries.join(""))
827            }
828        }
829    }
830}
831
832impl Hash for BrdbValue {
833    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
834        core::mem::discriminant(self).hash(state);
835        match self {
836            BrdbValue::Nil => {}
837            BrdbValue::Bool(v) => v.hash(state),
838            BrdbValue::U8(v) => v.hash(state),
839            BrdbValue::U16(v) => v.hash(state),
840            BrdbValue::U32(v) => v.hash(state),
841            BrdbValue::U64(v) => v.hash(state),
842            BrdbValue::I8(v) => v.hash(state),
843            BrdbValue::I16(v) => v.hash(state),
844            BrdbValue::I32(v) => v.hash(state),
845            BrdbValue::I64(v) => v.hash(state),
846            BrdbValue::F32(v) => v.to_bits().hash(state),
847            BrdbValue::F64(v) => v.to_bits().hash(state),
848            BrdbValue::String(v) => v.hash(state),
849            BrdbValue::Asset(v) => v.hash(state),
850            BrdbValue::Enum(e) => {
851                e.name.hash(state);
852                e.value.hash(state);
853            }
854            BrdbValue::Struct(s) => {
855                s.name.hash(state);
856                for (k, v) in &s.properties {
857                    k.hash(state);
858                    v.hash(state);
859                }
860            }
861            BrdbValue::Array(v) => v.hash(state),
862            BrdbValue::FlatArray(v) => v.hash(state),
863            BrdbValue::Map(map) => map.iter().for_each(|(k, v)| {
864                k.hash(state);
865                v.hash(state);
866            }),
867            BrdbValue::WireVar(w) => match w {
868                WireVariant::Number(n) => n.to_bits().hash(state),
869                WireVariant::Int(i) => i.hash(state),
870                WireVariant::Bool(b) => b.hash(state),
871                WireVariant::Object(o) => o.hash(state),
872                WireVariant::Exec => {}
873                WireVariant::Vector(v) => {
874                    v.x.to_bits().hash(state);
875                    v.y.to_bits().hash(state);
876                    v.z.to_bits().hash(state);
877                }
878                WireVariant::Rotator { pitch, yaw, roll } => {
879                    pitch.to_bits().hash(state);
880                    yaw.to_bits().hash(state);
881                    roll.to_bits().hash(state);
882                }
883                WireVariant::Quat { x, y, z, w } => {
884                    x.to_bits().hash(state);
885                    y.to_bits().hash(state);
886                    z.to_bits().hash(state);
887                    w.to_bits().hash(state);
888                }
889                WireVariant::Str(s) => s.hash(state),
890                WireVariant::LinearColor { r, g, b, a } => {
891                    r.to_bits().hash(state);
892                    g.to_bits().hash(state);
893                    b.to_bits().hash(state);
894                    a.to_bits().hash(state);
895                }
896            },
897        }
898    }
899}
900
901impl Eq for BrdbValue {}
902
903impl PartialEq for BrdbValue {
904    fn eq(&self, other: &Self) -> bool {
905        match (self, other) {
906            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
907            (Self::U8(l0), Self::U8(r0)) => l0 == r0,
908            (Self::U16(l0), Self::U16(r0)) => l0 == r0,
909            (Self::U32(l0), Self::U32(r0)) => l0 == r0,
910            (Self::U64(l0), Self::U64(r0)) => l0 == r0,
911            (Self::I8(l0), Self::I8(r0)) => l0 == r0,
912            (Self::I16(l0), Self::I16(r0)) => l0 == r0,
913            (Self::I32(l0), Self::I32(r0)) => l0 == r0,
914            (Self::I64(l0), Self::I64(r0)) => l0 == r0,
915            (Self::F32(l0), Self::F32(r0)) => l0 == r0,
916            (Self::F64(l0), Self::F64(r0)) => l0 == r0,
917            (Self::String(l0), Self::String(r0)) => l0 == r0,
918            (Self::Asset(l0), Self::Asset(r0)) => l0 == r0,
919            (Self::Enum(l0), Self::Enum(r0)) => l0.name == r0.name && l0.value == r0.value,
920            (Self::Struct(l0), Self::Struct(r0)) => {
921                if l0.name != r0.name {
922                    return false;
923                }
924                // Compare all properties
925                for (k, lv) in &l0.properties {
926                    let Some(kv) = r0.properties.get(k) else {
927                        return false;
928                    };
929                    if lv != kv {
930                        return false;
931                    }
932                }
933                return true;
934            }
935            (Self::Array(l0), Self::Array(r0)) => l0 == r0,
936            (Self::FlatArray(l0), Self::FlatArray(r0)) => l0 == r0,
937            (Self::Map(l0), Self::Map(r0)) => l0 == r0,
938            (Self::WireVar(l0), Self::WireVar(r0)) => match (l0, r0) {
939                (WireVariant::Number(l), WireVariant::Number(r)) => l == r,
940                (WireVariant::Int(l), WireVariant::Int(r)) => l == r,
941                (WireVariant::Bool(l), WireVariant::Bool(r)) => l == r,
942                (WireVariant::Object(l), WireVariant::Object(r)) => l == r,
943                (WireVariant::Exec, WireVariant::Exec) => false,
944                (WireVariant::Vector(l), WireVariant::Vector(r)) => l == r,
945                (WireVariant::Str(l), WireVariant::Str(r)) => l == r,
946                _ => false,
947            },
948            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
949        }
950    }
951}
952
953impl TryFrom<&BrdbValue> for String {
954    type Error = BrdbSchemaError;
955
956    fn try_from(value: &BrdbValue) -> Result<Self, Self::Error> {
957        value.as_str().map(|s| s.to_string())
958    }
959}
960impl TryFrom<BrdbValue> for String {
961    type Error = BrdbSchemaError;
962
963    fn try_from(value: BrdbValue) -> Result<Self, Self::Error> {
964        match value {
965            BrdbValue::String(s) => Ok(s),
966            _ => Err(BrdbSchemaError::ExpectedType(
967                "string".to_owned(),
968                value.get_type().to_string(),
969            )),
970        }
971    }
972}
973
974impl<'a> TryFrom<&'a BrdbValue> for &'a str {
975    type Error = BrdbSchemaError;
976
977    fn try_from(value: &'a BrdbValue) -> Result<&'a str, Self::Error> {
978        if let BrdbValue::String(v) = value {
979            Ok(v.as_ref())
980        } else {
981            Err(BrdbSchemaError::ExpectedType(
982                "string".to_owned(),
983                value.get_type().to_string(),
984            ))
985        }
986    }
987}
988
989impl<'a, T: TryFrom<&'a BrdbValue, Error = BrdbSchemaError>> TryFrom<&'a BrdbValue> for Vec<T> {
990    type Error = BrdbSchemaError;
991
992    fn try_from(value: &'a BrdbValue) -> Result<Self, Self::Error> {
993        let array = value.as_array()?;
994        let mut vec = Vec::with_capacity(array.len());
995        for item in array {
996            vec.push(T::try_from(item)?);
997        }
998        Ok(vec)
999    }
1000}
1001
1002impl<T: TryFrom<BrdbValue, Error = BrdbSchemaError>> TryFrom<BrdbValue> for Vec<T> {
1003    type Error = BrdbSchemaError;
1004
1005    fn try_from(value: BrdbValue) -> Result<Self, Self::Error> {
1006        let array = match value {
1007            BrdbValue::Array(v) => v,
1008            BrdbValue::FlatArray(v) => v,
1009            _ => {
1010                return Err(BrdbSchemaError::ExpectedType(
1011                    "array".to_owned(),
1012                    value.get_type().to_string(),
1013                ));
1014            }
1015        };
1016        let mut vec = Vec::with_capacity(array.len());
1017        for item in array {
1018            vec.push(T::try_from(item)?);
1019        }
1020        Ok(vec)
1021    }
1022}
1023
1024macro_rules! try_from_impl(
1025    ($id:ident @ $ty:ty) => {
1026        impl TryFrom<&BrdbValue> for $ty {
1027            type Error = BrdbSchemaError;
1028
1029            fn try_from(value: &BrdbValue) -> Result<Self, Self::Error> {
1030                value.$id()
1031            }
1032        }
1033        impl TryFrom<BrdbValue> for $ty {
1034            type Error = BrdbSchemaError;
1035
1036            fn try_from(value: BrdbValue) -> Result<Self, Self::Error> {
1037                value.$id()
1038            }
1039        }
1040
1041    }
1042);
1043
1044try_from_impl!(as_brdb_bool @ bool);
1045try_from_impl!(as_brdb_u8 @ u8);
1046try_from_impl!(as_brdb_u16 @ u16);
1047try_from_impl!(as_brdb_u32 @ u32);
1048try_from_impl!(as_brdb_u64 @ u64);
1049try_from_impl!(as_brdb_i8 @ i8);
1050try_from_impl!(as_brdb_i16 @ i16);
1051try_from_impl!(as_brdb_i32 @ i32);
1052try_from_impl!(as_brdb_i64 @ i64);
1053try_from_impl!(as_brdb_f32 @ f32);
1054try_from_impl!(as_brdb_f64 @ f64);
1055
1056#[cfg(test)]
1057mod wire_map_conversion_tests {
1058    use super::*;
1059
1060    #[test]
1061    fn map_value_data_round_trips_through_wire_variant() {
1062        let cases = [
1063            WireMapValueData::Number(1.5),
1064            WireMapValueData::Int64(-7),
1065            WireMapValueData::Bool(true),
1066            WireMapValueData::Str("hi".into()),
1067            WireMapValueData::Vector(Vector3f { x: 1.0, y: 2.0, z: 3.0 }),
1068            WireMapValueData::Rotator(10.0, 20.0, 30.0),
1069            WireMapValueData::Quat(0.0, 0.0, 0.0, 1.0),
1070            WireMapValueData::LinearColor(0.1, 0.2, 0.3, 1.0),
1071            WireMapValueData::Object(Some(5)),
1072            WireMapValueData::Object(None),
1073        ];
1074        for v in cases {
1075            let round: WireMapValueData = WireVariant::from(v.clone()).into();
1076            assert_eq!(round, v);
1077        }
1078    }
1079
1080    #[test]
1081    fn map_key_data_round_trips_through_wire_variant() {
1082        let cases = [
1083            WireMapKeyData::Int64(42),
1084            WireMapKeyData::Str("k".into()),
1085            WireMapKeyData::Object(Some(3)),
1086            WireMapKeyData::Object(None),
1087        ];
1088        for k in cases {
1089            let round: WireMapKeyData = WireVariant::from(k.clone()).into();
1090            assert_eq!(round, k);
1091        }
1092    }
1093
1094    #[test]
1095    fn wire_variant_forward_conversions_and_fallbacks() {
1096        // Exact forward conversions.
1097        assert_eq!(WireMapValueData::from(WireVariant::Int(9)), WireMapValueData::Int64(9));
1098        assert_eq!(
1099            WireMapKeyData::from(WireVariant::Str("s".into())),
1100            WireMapKeyData::Str("s".into())
1101        );
1102        // Documented degenerate fallbacks.
1103        assert_eq!(WireMapValueData::from(WireVariant::Exec), WireMapValueData::Number(0.0));
1104        assert_eq!(WireMapKeyData::from(WireVariant::Number(3.9)), WireMapKeyData::Int64(3));
1105        assert_eq!(WireMapKeyData::from(WireVariant::Bool(true)), WireMapKeyData::Int64(1));
1106        assert_eq!(
1107            WireMapKeyData::from(WireVariant::Vector(Vector3f { x: 0.0, y: 0.0, z: 0.0 })),
1108            WireMapKeyData::Int64(0)
1109        );
1110    }
1111}