Skip to main content

steel_utils/nbt/
codec.rs

1//! Numeric coercions used by vanilla's NBT-backed codecs and `ValueInput` getters.
2
3use simdnbt::{borrow::NbtTag as BorrowedNbtTag, owned::NbtTag as OwnedNbtTag};
4
5/// Decodes numeric NBT tags with vanilla's numeric conversions.
6///
7/// `codec_*` mirror `DynamicOps` number codecs, which truncate floating values.
8/// `*_value` mirror `NumericTag`, used by `ValueInput.get*Or`, which floors them.
9/// Floating-point reads agree, so `codec_f32`/`codec_f64` also serve
10/// `getFloatOr`/`getDoubleOr`.
11pub trait NbtNumeric {
12    /// Decodes `Codec.BOOL` from any numeric NBT tag.
13    fn codec_bool(&self) -> Option<bool>;
14
15    /// Decodes `Codec.BYTE` from any numeric NBT tag.
16    fn codec_i8(&self) -> Option<i8>;
17
18    /// Decodes `Codec.INT` from any numeric NBT tag.
19    fn codec_i32(&self) -> Option<i32>;
20
21    /// Decodes `Codec.LONG` from any numeric NBT tag.
22    fn codec_i64(&self) -> Option<i64>;
23
24    /// Decodes `Codec.FLOAT` from any numeric NBT tag.
25    fn codec_f32(&self) -> Option<f32>;
26
27    /// Decodes `Codec.DOUBLE` from any numeric NBT tag.
28    fn codec_f64(&self) -> Option<f64>;
29
30    /// Converts to an integer, flooring floating values.
31    fn int_value(&self) -> Option<i32>;
32
33    /// Returns the low 16 bits of `int_value`.
34    fn short_value(&self) -> Option<i16> {
35        self.int_value().map(|value| value as i16)
36    }
37
38    /// Returns the low 8 bits of `int_value`.
39    fn byte_value(&self) -> Option<i8> {
40        self.int_value().map(|value| value as i8)
41    }
42}
43
44impl NbtNumeric for OwnedNbtTag {
45    fn codec_bool(&self) -> Option<bool> {
46        match self {
47            Self::Byte(value) => Some(*value != 0),
48            Self::Short(value) => Some(*value != 0),
49            Self::Int(value) => Some(*value != 0),
50            Self::Long(value) => Some(*value != 0),
51            Self::Float(value) => Some(*value != 0.0),
52            Self::Double(value) => Some(*value != 0.0),
53            _ => None,
54        }
55    }
56
57    fn codec_i8(&self) -> Option<i8> {
58        match self {
59            Self::Byte(value) => Some(*value),
60            Self::Short(value) => Some(*value as i8),
61            Self::Int(value) => Some(*value as i8),
62            Self::Long(value) => Some(*value as i8),
63            Self::Float(value) => Some(*value as i32 as i8),
64            Self::Double(value) => Some(*value as i32 as i8),
65            _ => None,
66        }
67    }
68
69    fn codec_i32(&self) -> Option<i32> {
70        match self {
71            Self::Byte(value) => Some(i32::from(*value)),
72            Self::Short(value) => Some(i32::from(*value)),
73            Self::Int(value) => Some(*value),
74            Self::Long(value) => Some(*value as i32),
75            Self::Float(value) => Some(*value as i32),
76            Self::Double(value) => Some(*value as i32),
77            _ => None,
78        }
79    }
80
81    fn codec_i64(&self) -> Option<i64> {
82        match self {
83            Self::Byte(value) => Some(i64::from(*value)),
84            Self::Short(value) => Some(i64::from(*value)),
85            Self::Int(value) => Some(i64::from(*value)),
86            Self::Long(value) => Some(*value),
87            Self::Float(value) => Some(*value as i64),
88            Self::Double(value) => Some(*value as i64),
89            _ => None,
90        }
91    }
92
93    fn codec_f32(&self) -> Option<f32> {
94        match self {
95            Self::Byte(value) => Some(f32::from(*value)),
96            Self::Short(value) => Some(f32::from(*value)),
97            Self::Int(value) => Some(*value as f32),
98            Self::Long(value) => Some(*value as f32),
99            Self::Float(value) => Some(*value),
100            Self::Double(value) => Some(*value as f32),
101            _ => None,
102        }
103    }
104
105    fn codec_f64(&self) -> Option<f64> {
106        match self {
107            Self::Byte(value) => Some(f64::from(*value)),
108            Self::Short(value) => Some(f64::from(*value)),
109            Self::Int(value) => Some(f64::from(*value)),
110            Self::Long(value) => Some(*value as f64),
111            Self::Float(value) => Some(f64::from(*value)),
112            Self::Double(value) => Some(*value),
113            _ => None,
114        }
115    }
116
117    fn int_value(&self) -> Option<i32> {
118        match self {
119            Self::Byte(value) => Some(i32::from(*value)),
120            Self::Short(value) => Some(i32::from(*value)),
121            Self::Int(value) => Some(*value),
122            Self::Long(value) => Some(*value as i32),
123            Self::Float(value) => Some(value.floor() as i32),
124            Self::Double(value) => Some(value.floor() as i32),
125            _ => None,
126        }
127    }
128}
129
130impl NbtNumeric for BorrowedNbtTag<'_, '_> {
131    fn codec_bool(&self) -> Option<bool> {
132        self.byte()
133            .map(|value| value != 0)
134            .or_else(|| self.short().map(|value| value != 0))
135            .or_else(|| self.int().map(|value| value != 0))
136            .or_else(|| self.long().map(|value| value != 0))
137            .or_else(|| self.float().map(|value| value != 0.0))
138            .or_else(|| self.double().map(|value| value != 0.0))
139    }
140
141    fn codec_i8(&self) -> Option<i8> {
142        self.byte()
143            .or_else(|| self.short().map(|value| value as i8))
144            .or_else(|| self.int().map(|value| value as i8))
145            .or_else(|| self.long().map(|value| value as i8))
146            .or_else(|| self.float().map(|value| value as i32 as i8))
147            .or_else(|| self.double().map(|value| value as i32 as i8))
148    }
149
150    fn codec_i32(&self) -> Option<i32> {
151        self.byte()
152            .map(i32::from)
153            .or_else(|| self.short().map(i32::from))
154            .or_else(|| self.int())
155            .or_else(|| self.long().map(|value| value as i32))
156            .or_else(|| self.float().map(|value| value as i32))
157            .or_else(|| self.double().map(|value| value as i32))
158    }
159
160    fn codec_i64(&self) -> Option<i64> {
161        self.byte()
162            .map(i64::from)
163            .or_else(|| self.short().map(i64::from))
164            .or_else(|| self.int().map(i64::from))
165            .or_else(|| self.long())
166            .or_else(|| self.float().map(|value| value as i64))
167            .or_else(|| self.double().map(|value| value as i64))
168    }
169
170    fn codec_f32(&self) -> Option<f32> {
171        self.byte()
172            .map(f32::from)
173            .or_else(|| self.short().map(f32::from))
174            .or_else(|| self.int().map(|value| value as f32))
175            .or_else(|| self.long().map(|value| value as f32))
176            .or_else(|| self.float())
177            .or_else(|| self.double().map(|value| value as f32))
178    }
179
180    fn codec_f64(&self) -> Option<f64> {
181        self.byte()
182            .map(f64::from)
183            .or_else(|| self.short().map(f64::from))
184            .or_else(|| self.int().map(f64::from))
185            .or_else(|| self.long().map(|value| value as f64))
186            .or_else(|| self.float().map(f64::from))
187            .or_else(|| self.double())
188    }
189
190    fn int_value(&self) -> Option<i32> {
191        self.byte()
192            .map(i32::from)
193            .or_else(|| self.short().map(i32::from))
194            .or_else(|| self.int())
195            .or_else(|| self.long().map(|value| value as i32))
196            .or_else(|| self.float().map(|value| value.floor() as i32))
197            .or_else(|| self.double().map(|value| value.floor() as i32))
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use std::io::Cursor;
204
205    use simdnbt::{borrow::read_tag, owned::NbtTag};
206
207    use super::NbtNumeric;
208
209    #[test]
210    fn owned_numeric_tags_use_codec_coercions() {
211        assert_eq!(NbtTag::Double(5.5).codec_f32(), Some(5.5));
212        assert_eq!(NbtTag::Float(5.9).codec_i32(), Some(5));
213        assert_eq!(NbtTag::Int(0x1FF).codec_i8(), Some(-1));
214        assert_eq!(NbtTag::Double(-1.5).codec_i64(), Some(-1));
215        assert_eq!(NbtTag::Float(-1.5).codec_i8(), Some(-1));
216        assert_eq!(
217            NbtTag::Long(i64::from(i32::MAX) + 1).codec_i32(),
218            Some(i32::MIN)
219        );
220        assert_eq!(NbtTag::Double(f64::NAN).codec_bool(), Some(true));
221        assert_eq!(NbtTag::String("1".into()).codec_f64(), None);
222    }
223
224    #[test]
225    fn borrowed_numeric_tags_use_codec_coercions() {
226        let tag = NbtTag::Double(5.5);
227        let mut bytes = Vec::new();
228        tag.write(&mut bytes);
229        let borrowed =
230            read_tag(&mut Cursor::new(bytes.as_slice())).expect("owned test tag should parse");
231
232        assert_eq!(borrowed.as_tag().codec_f32(), Some(5.5));
233    }
234}