Skip to main content

genie_support/
ids.rs

1use crate::{fallible_try_from, fallible_try_into, infallible_try_into};
2use std::convert::{TryFrom, TryInto};
3use std::fmt;
4use std::num::TryFromIntError;
5
6/// An ID identifying a unit type.
7#[derive(Debug, Hash, Default, Clone, Copy, PartialEq, Eq)]
8pub struct UnitTypeID(u16);
9
10impl From<u16> for UnitTypeID {
11    #[inline]
12    fn from(n: u16) -> Self {
13        UnitTypeID(n)
14    }
15}
16
17impl From<UnitTypeID> for u16 {
18    #[inline]
19    fn from(n: UnitTypeID) -> Self {
20        n.0
21    }
22}
23
24impl From<UnitTypeID> for i32 {
25    #[inline]
26    fn from(n: UnitTypeID) -> Self {
27        n.0.into()
28    }
29}
30
31impl From<UnitTypeID> for u32 {
32    #[inline]
33    fn from(n: UnitTypeID) -> Self {
34        n.0.into()
35    }
36}
37
38impl From<UnitTypeID> for usize {
39    #[inline]
40    fn from(n: UnitTypeID) -> Self {
41        n.0.into()
42    }
43}
44
45fallible_try_into!(UnitTypeID, i16);
46fallible_try_from!(UnitTypeID, i16);
47fallible_try_from!(UnitTypeID, i32);
48fallible_try_from!(UnitTypeID, u32);
49
50/// An ID identifying a tech.
51#[derive(Debug, Hash, Default, Clone, Copy, PartialEq, Eq)]
52pub struct TechID(u16);
53
54impl From<u16> for TechID {
55    fn from(n: u16) -> Self {
56        TechID(n)
57    }
58}
59
60impl From<TechID> for u16 {
61    fn from(n: TechID) -> Self {
62        n.0
63    }
64}
65
66impl From<TechID> for usize {
67    fn from(n: TechID) -> Self {
68        n.0.into()
69    }
70}
71
72fallible_try_into!(TechID, i16);
73infallible_try_into!(TechID, u32);
74fallible_try_from!(TechID, i32);
75fallible_try_from!(TechID, u32);
76
77/// An ID identifying a sprite.
78#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
79pub struct SpriteID(u16);
80impl From<u16> for SpriteID {
81    fn from(n: u16) -> Self {
82        SpriteID(n)
83    }
84}
85
86impl From<SpriteID> for u16 {
87    fn from(n: SpriteID) -> Self {
88        n.0
89    }
90}
91
92impl From<SpriteID> for i32 {
93    fn from(n: SpriteID) -> Self {
94        n.0.into()
95    }
96}
97
98impl From<SpriteID> for u32 {
99    fn from(n: SpriteID) -> Self {
100        n.0.into()
101    }
102}
103
104impl From<SpriteID> for usize {
105    fn from(n: SpriteID) -> Self {
106        n.0.into()
107    }
108}
109
110fallible_try_into!(SpriteID, i16);
111fallible_try_from!(SpriteID, i16);
112fallible_try_from!(SpriteID, i32);
113fallible_try_from!(SpriteID, u32);
114
115/// A key in a language file.
116///
117/// A key may be either a nonnegative integer or an arbitrary string.
118///
119/// The original game supports only nonnegative integers.
120/// The HD Edition allows for integers as well as Strings to serve as keys in a
121/// key value file.
122#[derive(Debug, Hash, Clone, PartialEq, Eq)]
123pub enum StringKey {
124    /// An integer string key.
125    Num(u32),
126
127    /// A named string key.
128    /// The string must not represent a `u32` value (such keys must be `Num`).
129    Name(String),
130}
131
132impl Default for StringKey {
133    #[inline]
134    fn default() -> Self {
135        Self::Num(0)
136    }
137}
138
139impl StringKey {
140    /// Returns `true` if and only if this `StringKey` is a number.
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// use genie_support::StringKey;
146    /// use std::convert::TryFrom;
147    /// assert!(StringKey::try_from(0).unwrap().is_numeric());
148    /// assert!(!StringKey::from("").is_numeric());
149    /// ```
150    #[inline]
151    pub fn is_numeric(&self) -> bool {
152        match self {
153            Self::Num(_) => true,
154            Self::Name(_) => false,
155        }
156    }
157
158    /// Returns `true` if and only if this `StringKey` is a string name.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use genie_support::StringKey;
164    /// use std::convert::TryFrom;
165    /// assert!(!StringKey::try_from(0).unwrap().is_named());
166    /// assert!(StringKey::from("").is_named());
167    /// ```
168    #[inline]
169    pub fn is_named(&self) -> bool {
170        match self {
171            Self::Num(_) => false,
172            Self::Name(_) => true,
173        }
174    }
175}
176
177impl fmt::Display for StringKey {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        match self {
180            Self::Num(n) => write!(f, "{}", n),
181            Self::Name(s) => write!(f, "{}", s),
182        }
183    }
184}
185
186impl From<u32> for StringKey {
187    #[inline]
188    fn from(n: u32) -> Self {
189        Self::Num(n)
190    }
191}
192
193impl From<u16> for StringKey {
194    #[inline]
195    fn from(n: u16) -> Self {
196        Self::Num(n.into())
197    }
198}
199
200impl TryFrom<i32> for StringKey {
201    type Error = TryFromIntError;
202    #[inline]
203    fn try_from(n: i32) -> Result<Self, Self::Error> {
204        u32::try_from(n).map(Self::Num)
205    }
206}
207
208impl TryFrom<i16> for StringKey {
209    type Error = TryFromIntError;
210    #[inline]
211    fn try_from(n: i16) -> Result<Self, Self::Error> {
212        u32::try_from(n).map(Self::Num)
213    }
214}
215
216impl From<&str> for StringKey {
217    #[inline]
218    fn from(s: &str) -> Self {
219        if let Ok(n) = s.parse() {
220            Self::Num(n)
221        } else {
222            Self::Name(String::from(s))
223        }
224    }
225}
226
227impl From<String> for StringKey {
228    #[inline]
229    fn from(s: String) -> Self {
230        Self::from(s.as_ref())
231    }
232}
233
234/// Error that may occur when converting a StringKey to some other Rust value, such as an integer
235/// or a string.
236///
237/// When converting to an integer, this means that the StringKey is a named key, or it has a
238/// numeric value that is out of range for the target type.
239///
240/// When converting to a string, this does not happen, as numeric keys will be converted to
241/// strings.
242#[derive(Debug, Clone, thiserror::Error)]
243#[error("could not convert StringKey to the wanted integer size")]
244pub struct TryFromStringKeyError;
245
246// Implement TryFrom<&StringKey> conversions for a bunch of stuff
247macro_rules! try_from_string_key {
248    ($type:ty) => {
249        impl TryFrom<&StringKey> for $type {
250            type Error = TryFromStringKeyError;
251            #[inline]
252            fn try_from(key: &StringKey) -> Result<Self, Self::Error> {
253                match key {
254                    StringKey::Num(n) => (*n).try_into().map_err(|_| TryFromStringKeyError),
255                    _ => Err(TryFromStringKeyError),
256                }
257            }
258        }
259    };
260}
261
262try_from_string_key!(u32);
263try_from_string_key!(i32);
264try_from_string_key!(u16);
265try_from_string_key!(i16);
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    /// Tests converting from an int to a string key.
272    #[test]
273    fn string_key_from_int() {
274        if let StringKey::Num(n) = StringKey::try_from(0).unwrap() {
275            assert_eq!(0, n);
276        } else {
277            panic!();
278        }
279    }
280
281    /// Tests converting from a string representing an int to a string key.
282    #[test]
283    fn string_key_from_str_to_int() {
284        let s = "57329";
285        if let StringKey::Num(n) = StringKey::from(s) {
286            assert_eq!(57329, n);
287        } else {
288            panic!();
289        }
290    }
291
292    /// Tests converting from a string not representing an int to a string key.
293    #[test]
294    fn string_key_from_str_to_str() {
295        let s = "grassDaut";
296        if let StringKey::Name(n) = StringKey::from(s) {
297            assert_eq!(s, n);
298        } else {
299            panic!();
300        }
301    }
302}