Skip to main content

rakata_formats/schema/
scalar.rs

1//! A type states its own GFF mapping.
2//!
3//! The alternative is a table inside the derive keyed by Rust type name, which
4//! this layer has grown twice: nineteen width tokens in a `macro_rules!`, then
5//! sixteen rows in a proc macro. Both had to be edited for every new type, and
6//! both had rows nothing could reach, because a key nothing matches looks
7//! exactly like a key nothing needs.
8//!
9//! With the mapping delegated, the derive names no type at all. Adding a
10//! domain newtype is one impl beside that newtype and no edit here.
11
12use super::GffType;
13use crate::gff::{GffLocalizedString, GffValue};
14use rakata_core::ResRef;
15
16/// How one Rust type maps onto a GFF value.
17///
18/// # Tolerance belongs in the impl
19///
20/// `from_gff_value` for `i16` accepting an `Int32` is that type's business,
21/// stated once, rather than a coercion ladder repeated at every read site. The
22/// engine tolerates more encodings than it writes, and this crate has to read
23/// the files it merely tolerates.
24///
25/// `to_gff_value` is the other half and is not its inverse: it emits the one
26/// encoding the engine's own writer produces. A type that reads three widths
27/// and writes one is behaving correctly.
28pub trait GffScalar: Sized {
29    /// The wire type this maps to.
30    const GFF_TYPE: GffType;
31
32    /// Reads a value, accepting every encoding this type can hold.
33    ///
34    /// `None` where the value is a type this cannot represent, which the
35    /// caller treats as the field being absent rather than as an error.
36    fn from_gff_value(value: &GffValue) -> Option<Self>;
37
38    /// Writes the one encoding the engine's own writer emits.
39    fn to_gff_value(self) -> GffValue;
40}
41
42/// Implements the trait for a type whose read accepts several widths.
43///
44/// The widths are listed per type rather than derived from a size, because
45/// what a field tolerates is a fact about that field's readers rather than
46/// about how many bits the value needs.
47macro_rules! integer_scalar {
48    ($rust:ty, $gff:ident, $write:ident, $( $accept:ident ),+ $(,)?) => {
49        impl GffScalar for $rust {
50            const GFF_TYPE: GffType = GffType::$gff;
51
52            fn from_gff_value(value: &GffValue) -> Option<Self> {
53                match value {
54                    $( GffValue::$accept(v) => Self::try_from(*v).ok(), )+
55                    _ => None,
56                }
57            }
58
59            fn to_gff_value(self) -> GffValue {
60                GffValue::$write(self)
61            }
62        }
63    };
64}
65
66// Transcribed from the coercion ladders in `gff::coerce`, which is what these
67// replace. A width one of those readers accepted and one of these did not
68// would change what a migrated view reads, silently and only on the files
69// carrying that width.
70//
71// `get_u32_extended_signed` is the one ladder with no home here: it is a
72// second, wider reading of the same Rust type, so the tolerance belongs to the
73// field rather than to `u32`. Resolve it when a view declaring one of those
74// fields migrates.
75integer_scalar!(u8, UInt8, UInt8, UInt8, Int8, UInt16, Int16, UInt32, Int32);
76integer_scalar!(i8, Int8, Int8, Int8, UInt8, Int16, UInt16, Int32, UInt32);
77integer_scalar!(u16, UInt16, UInt16, UInt16, Int16, UInt8, Int8, UInt32, Int32);
78integer_scalar!(i16, Int16, Int16, Int16, UInt16, Int8, UInt8, Int32, UInt32);
79integer_scalar!(u32, UInt32, UInt32, UInt32, Int32, UInt16, UInt8);
80integer_scalar!(i32, Int32, Int32, Int32, UInt32, Int16, UInt16, Int8, UInt8);
81integer_scalar!(u64, UInt64, UInt64, UInt64, UInt32, Int64, Int32, UInt16, UInt8);
82// No predecessor ladder read an `i64`, so there is nothing to transcribe and
83// this is the pair the format itself offers.
84integer_scalar!(i64, Int64, Int64, Int32, Int64);
85
86impl GffScalar for f32 {
87    const GFF_TYPE: GffType = GffType::Single;
88
89    fn from_gff_value(value: &GffValue) -> Option<Self> {
90        match value {
91            GffValue::Single(v) => Some(*v),
92            // Narrowing on purpose. A `Double` where the view models an f32 is
93            // a wider encoding of the same field, and refusing it would read
94            // as the field being absent.
95            #[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
96            GffValue::Double(v) => Some(*v as f32),
97            _ => None,
98        }
99    }
100
101    fn to_gff_value(self) -> GffValue {
102        GffValue::Single(self)
103    }
104}
105
106impl GffScalar for f64 {
107    const GFF_TYPE: GffType = GffType::Double;
108
109    fn from_gff_value(value: &GffValue) -> Option<Self> {
110        match value {
111            GffValue::Double(v) => Some(*v),
112            _ => None,
113        }
114    }
115
116    fn to_gff_value(self) -> GffValue {
117        GffValue::Double(self)
118    }
119}
120
121/// A `bool` is carried as a `UInt8` almost everywhere and as a `UInt16` once,
122/// which is why the file's type is a fact to record rather than to derive from
123/// the Rust type.
124impl GffScalar for bool {
125    const GFF_TYPE: GffType = GffType::UInt8;
126
127    fn from_gff_value(value: &GffValue) -> Option<Self> {
128        match value {
129            GffValue::UInt8(v) => Some(*v != 0),
130            GffValue::Int8(v) => Some(*v != 0),
131            GffValue::UInt16(v) => Some(*v != 0),
132            GffValue::Int16(v) => Some(*v != 0),
133            GffValue::UInt32(v) => Some(*v != 0),
134            GffValue::Int32(v) => Some(*v != 0),
135            _ => None,
136        }
137    }
138
139    fn to_gff_value(self) -> GffValue {
140        GffValue::UInt8(u8::from(self))
141    }
142}
143
144impl GffScalar for String {
145    const GFF_TYPE: GffType = GffType::String;
146
147    fn from_gff_value(value: &GffValue) -> Option<Self> {
148        match value {
149            GffValue::String(v) => Some(v.clone()),
150            _ => None,
151        }
152    }
153
154    fn to_gff_value(self) -> GffValue {
155        GffValue::String(self)
156    }
157}
158
159impl GffScalar for ResRef {
160    const GFF_TYPE: GffType = GffType::ResRef;
161
162    fn from_gff_value(value: &GffValue) -> Option<Self> {
163        match value {
164            GffValue::ResRef(v) => Some(*v),
165            _ => None,
166        }
167    }
168
169    fn to_gff_value(self) -> GffValue {
170        GffValue::ResRef(self)
171    }
172}
173
174impl GffScalar for GffLocalizedString {
175    const GFF_TYPE: GffType = GffType::LocalizedString;
176
177    fn from_gff_value(value: &GffValue) -> Option<Self> {
178        match value {
179            GffValue::LocalizedString(v) => Some(v.clone()),
180            _ => None,
181        }
182    }
183
184    fn to_gff_value(self) -> GffValue {
185        GffValue::LocalizedString(self)
186    }
187}
188
189impl GffScalar for Vec<u8> {
190    const GFF_TYPE: GffType = GffType::Binary;
191
192    fn from_gff_value(value: &GffValue) -> Option<Self> {
193        match value {
194            GffValue::Binary(v) => Some(v.clone()),
195            _ => None,
196        }
197    }
198
199    fn to_gff_value(self) -> GffValue {
200        GffValue::Binary(self)
201    }
202}
203
204impl GffScalar for [f32; 3] {
205    const GFF_TYPE: GffType = GffType::Vector3;
206
207    fn from_gff_value(value: &GffValue) -> Option<Self> {
208        match value {
209            GffValue::Vector3(v) => Some(*v),
210            _ => None,
211        }
212    }
213
214    fn to_gff_value(self) -> GffValue {
215        GffValue::Vector3(self)
216    }
217}
218
219impl GffScalar for [f32; 4] {
220    const GFF_TYPE: GffType = GffType::Vector4;
221
222    fn from_gff_value(value: &GffValue) -> Option<Self> {
223        match value {
224            GffValue::Vector4(v) => Some(*v),
225            _ => None,
226        }
227    }
228
229    fn to_gff_value(self) -> GffValue {
230        GffValue::Vector4(self)
231    }
232}