Skip to main content

steel_utils/nbt/
codec.rs

1//! Numeric coercions used by vanilla's NBT-backed codecs.
2
3use simdnbt::{borrow::NbtTag as BorrowedNbtTag, owned::NbtTag as OwnedNbtTag};
4
5/// Decodes numeric NBT tags with the conversions performed by vanilla's
6/// `DynamicOps` number codecs.
7pub trait NbtNumeric {
8    /// Decodes `Codec.BOOL` from any numeric NBT tag.
9    fn codec_bool(&self) -> Option<bool>;
10
11    /// Decodes `Codec.INT` from any numeric NBT tag.
12    fn codec_i32(&self) -> Option<i32>;
13
14    /// Decodes `Codec.FLOAT` from any numeric NBT tag.
15    fn codec_f32(&self) -> Option<f32>;
16
17    /// Decodes `Codec.DOUBLE` from any numeric NBT tag.
18    fn codec_f64(&self) -> Option<f64>;
19}
20
21impl NbtNumeric for OwnedNbtTag {
22    fn codec_bool(&self) -> Option<bool> {
23        match self {
24            Self::Byte(value) => Some(*value != 0),
25            Self::Short(value) => Some(*value != 0),
26            Self::Int(value) => Some(*value != 0),
27            Self::Long(value) => Some(*value != 0),
28            Self::Float(value) => Some(*value != 0.0),
29            Self::Double(value) => Some(*value != 0.0),
30            _ => None,
31        }
32    }
33
34    fn codec_i32(&self) -> Option<i32> {
35        match self {
36            Self::Byte(value) => Some(i32::from(*value)),
37            Self::Short(value) => Some(i32::from(*value)),
38            Self::Int(value) => Some(*value),
39            Self::Long(value) => Some(*value as i32),
40            Self::Float(value) => Some(*value as i32),
41            Self::Double(value) => Some(*value as i32),
42            _ => None,
43        }
44    }
45
46    fn codec_f32(&self) -> Option<f32> {
47        match self {
48            Self::Byte(value) => Some(f32::from(*value)),
49            Self::Short(value) => Some(f32::from(*value)),
50            Self::Int(value) => Some(*value as f32),
51            Self::Long(value) => Some(*value as f32),
52            Self::Float(value) => Some(*value),
53            Self::Double(value) => Some(*value as f32),
54            _ => None,
55        }
56    }
57
58    fn codec_f64(&self) -> Option<f64> {
59        match self {
60            Self::Byte(value) => Some(f64::from(*value)),
61            Self::Short(value) => Some(f64::from(*value)),
62            Self::Int(value) => Some(f64::from(*value)),
63            Self::Long(value) => Some(*value as f64),
64            Self::Float(value) => Some(f64::from(*value)),
65            Self::Double(value) => Some(*value),
66            _ => None,
67        }
68    }
69}
70
71impl NbtNumeric for BorrowedNbtTag<'_, '_> {
72    fn codec_bool(&self) -> Option<bool> {
73        self.byte()
74            .map(|value| value != 0)
75            .or_else(|| self.short().map(|value| value != 0))
76            .or_else(|| self.int().map(|value| value != 0))
77            .or_else(|| self.long().map(|value| value != 0))
78            .or_else(|| self.float().map(|value| value != 0.0))
79            .or_else(|| self.double().map(|value| value != 0.0))
80    }
81
82    fn codec_i32(&self) -> Option<i32> {
83        self.byte()
84            .map(i32::from)
85            .or_else(|| self.short().map(i32::from))
86            .or_else(|| self.int())
87            .or_else(|| self.long().map(|value| value as i32))
88            .or_else(|| self.float().map(|value| value as i32))
89            .or_else(|| self.double().map(|value| value as i32))
90    }
91
92    fn codec_f32(&self) -> Option<f32> {
93        self.byte()
94            .map(f32::from)
95            .or_else(|| self.short().map(f32::from))
96            .or_else(|| self.int().map(|value| value as f32))
97            .or_else(|| self.long().map(|value| value as f32))
98            .or_else(|| self.float())
99            .or_else(|| self.double().map(|value| value as f32))
100    }
101
102    fn codec_f64(&self) -> Option<f64> {
103        self.byte()
104            .map(f64::from)
105            .or_else(|| self.short().map(f64::from))
106            .or_else(|| self.int().map(f64::from))
107            .or_else(|| self.long().map(|value| value as f64))
108            .or_else(|| self.float().map(f64::from))
109            .or_else(|| self.double())
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use std::io::Cursor;
116
117    use simdnbt::{borrow::read_tag, owned::NbtTag};
118
119    use super::NbtNumeric;
120
121    #[test]
122    fn owned_numeric_tags_use_codec_coercions() {
123        assert_eq!(NbtTag::Double(5.5).codec_f32(), Some(5.5));
124        assert_eq!(NbtTag::Float(5.9).codec_i32(), Some(5));
125        assert_eq!(
126            NbtTag::Long(i64::from(i32::MAX) + 1).codec_i32(),
127            Some(i32::MIN)
128        );
129        assert_eq!(NbtTag::Double(f64::NAN).codec_bool(), Some(true));
130        assert_eq!(NbtTag::String("1".into()).codec_f64(), None);
131    }
132
133    #[test]
134    fn borrowed_numeric_tags_use_codec_coercions() {
135        let tag = NbtTag::Double(5.5);
136        let mut bytes = Vec::new();
137        tag.write(&mut bytes);
138        let borrowed =
139            read_tag(&mut Cursor::new(bytes.as_slice())).expect("owned test tag should parse");
140
141        assert_eq!(borrowed.as_tag().codec_f32(), Some(5.5));
142    }
143}