all_is_cubes_base/
resolution.rs

1use core::ops;
2
3// Note: Public documentation for this is in its re-export from `all_is_cubes::block`.
4#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, exhaust::Exhaust)]
5#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
6#[allow(missing_docs)]
7#[repr(u8)]
8#[non_exhaustive] // unlikely to change but on general principle: not supposed to match this
9pub enum Resolution {
10    R1 = 0,
11    R2 = 1,
12    R4 = 2,
13    R8 = 3,
14    R16 = 4,
15    R32 = 5,
16    R64 = 6,
17    R128 = 7,
18}
19use core::fmt;
20
21use crate::math::GridCoordinate;
22
23impl Resolution {
24    /// The maximum available resolution.
25    pub const MAX: Resolution = Resolution::R128;
26
27    /// Returns the [`Resolution`] that’s twice this one, or [`None`] at the limit.
28    #[inline]
29    pub const fn double(self) -> Option<Self> {
30        match self {
31            Self::R1 => Some(Self::R2),
32            Self::R2 => Some(Self::R4),
33            Self::R4 => Some(Self::R8),
34            Self::R8 => Some(Self::R16),
35            Self::R16 => Some(Self::R32),
36            Self::R32 => Some(Self::R64),
37            Self::R64 => Some(Self::R128),
38            Self::R128 => None,
39        }
40    }
41
42    /// Returns the [`Resolution`] that’s half this one, or [`None`] if `self` is
43    /// [`R1`](Self::R1).
44    #[inline]
45    pub const fn halve(self) -> Option<Self> {
46        match self {
47            Self::R1 => None,
48            Self::R2 => Some(Self::R1),
49            Self::R4 => Some(Self::R2),
50            Self::R8 => Some(Self::R4),
51            Self::R16 => Some(Self::R8),
52            Self::R32 => Some(Self::R16),
53            Self::R64 => Some(Self::R32),
54            Self::R128 => Some(Self::R64),
55        }
56    }
57
58    #[inline]
59    #[doc(hidden)] // interim while waiting for better const-eval support in Rust
60    pub const fn to_grid(self) -> GridCoordinate {
61        1 << self as GridCoordinate
62    }
63}
64
65impl fmt::Debug for Resolution {
66    #[allow(clippy::missing_inline_in_public_items)]
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        GridCoordinate::from(*self).fmt(f)
69    }
70}
71impl fmt::Display for Resolution {
72    #[allow(clippy::missing_inline_in_public_items)]
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        GridCoordinate::from(*self).fmt(f)
75    }
76}
77
78macro_rules! impl_try_from {
79    ($t:ty) => {
80        impl TryFrom<$t> for Resolution {
81            type Error = IntoResolutionError<$t>;
82            #[inline]
83            fn try_from(value: $t) -> Result<Self, Self::Error> {
84                match value {
85                    1 => Ok(Self::R1),
86                    2 => Ok(Self::R2),
87                    4 => Ok(Self::R4),
88                    8 => Ok(Self::R8),
89                    16 => Ok(Self::R16),
90                    32 => Ok(Self::R32),
91                    64 => Ok(Self::R64),
92                    128 => Ok(Self::R128),
93                    _ => Err(IntoResolutionError(value)),
94                }
95            }
96        }
97    };
98}
99impl_try_from!(i16);
100impl_try_from!(i32);
101impl_try_from!(i64);
102impl_try_from!(i128);
103impl_try_from!(isize);
104impl_try_from!(u16);
105impl_try_from!(u32);
106impl_try_from!(u64);
107impl_try_from!(u128);
108impl_try_from!(usize);
109
110impl From<Resolution> for i32 {
111    /// ```
112    /// # mod all_is_cubes { pub mod block { pub use all_is_cubes_base::resolution::Resolution; } }
113    /// use all_is_cubes::block::Resolution;
114    ///
115    /// assert_eq!(64, i32::from(Resolution::R64));
116    /// ```
117    #[inline]
118    fn from(r: Resolution) -> i32 {
119        1 << (r as i32)
120    }
121}
122impl From<Resolution> for u16 {
123    #[inline]
124    fn from(r: Resolution) -> u16 {
125        1 << (r as u16)
126    }
127}
128impl From<Resolution> for u32 {
129    #[inline]
130    fn from(r: Resolution) -> u32 {
131        1 << (r as u32)
132    }
133}
134impl From<Resolution> for usize {
135    #[inline]
136    fn from(r: Resolution) -> usize {
137        1 << (r as usize)
138    }
139}
140impl From<Resolution> for f32 {
141    #[inline]
142    fn from(r: Resolution) -> f32 {
143        u16::from(r).into()
144    }
145}
146impl From<Resolution> for f64 {
147    #[inline]
148    fn from(r: Resolution) -> f64 {
149        u16::from(r).into()
150    }
151}
152
153impl ops::Mul<Resolution> for Resolution {
154    type Output = Option<Resolution>;
155
156    #[inline]
157    fn mul(self, rhs: Resolution) -> Self::Output {
158        // not the most efficient way to implement this, but straightforward
159        Self::try_from(u32::from(self) * u32::from(rhs)).ok()
160    }
161}
162
163impl ops::Div<Resolution> for Resolution {
164    type Output = Option<Resolution>;
165
166    #[inline]
167    fn div(self, rhs: Resolution) -> Self::Output {
168        // not the most efficient way to implement this, but straightforward
169        Self::try_from(u32::from(self) / u32::from(rhs)).ok()
170    }
171}
172
173#[cfg(feature = "serde")]
174impl serde::Serialize for Resolution {
175    #[allow(clippy::missing_inline_in_public_items)]
176    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
177        u16::from(*self).serialize(serializer)
178    }
179}
180
181#[cfg(feature = "serde")]
182impl<'de> serde::Deserialize<'de> for Resolution {
183    #[allow(clippy::missing_inline_in_public_items)]
184    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
185        u16::deserialize(deserializer)?
186            .try_into()
187            .map_err(serde::de::Error::custom)
188    }
189}
190
191/// Error type produced by [`TryFrom`] for [`Resolution`], and deserializing resolutions,
192/// when the number is not a permitted resolution value.
193#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
194pub struct IntoResolutionError<N>(N);
195
196impl<N: fmt::Display + fmt::Debug> core::error::Error for IntoResolutionError<N> {}
197
198impl<N: fmt::Display> fmt::Display for IntoResolutionError<N> {
199    #[allow(clippy::missing_inline_in_public_items)]
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        write!(
202            f,
203            "{number} is not a permitted resolution; must be a power of 2 between 1 and 127",
204            number = self.0
205        )
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use alloc::vec::Vec;
213    use exhaust::Exhaust as _;
214    use Resolution::*;
215
216    const RS: [Resolution; 8] = [R1, R2, R4, R8, R16, R32, R64, R128];
217
218    #[test]
219    fn test_list_is_complete() {
220        assert_eq!(
221            Vec::from(RS),
222            Resolution::exhaust().collect::<Vec<Resolution>>()
223        );
224    }
225
226    #[test]
227    fn max_is_max() {
228        assert_eq!(Resolution::MAX, *RS.last().unwrap());
229        assert_eq!(None, Resolution::MAX.double());
230    }
231
232    #[test]
233    fn resolution_steps() {
234        for i in 0..RS.len() - 1 {
235            assert_eq!(RS[i].double().unwrap(), RS[i + 1]);
236            assert_eq!(RS[i + 1].halve().unwrap(), RS[i]);
237        }
238    }
239
240    #[test]
241    fn resolution_values() {
242        assert_eq!(RS.map(i32::from), [1, 2, 4, 8, 16, 32, 64, 128]);
243        assert_eq!(RS.map(u16::from), [1, 2, 4, 8, 16, 32, 64, 128]);
244        assert_eq!(RS.map(u32::from), [1, 2, 4, 8, 16, 32, 64, 128]);
245        assert_eq!(RS.map(usize::from), [1, 2, 4, 8, 16, 32, 64, 128]);
246    }
247
248    #[test]
249    fn mul() {
250        assert_eq!(R4 * R2, Some(R8));
251        assert_eq!(R128 * R2, None);
252        assert_eq!(R2 * R128, None);
253    }
254
255    #[test]
256    fn div() {
257        assert_eq!(R8 / R2, Some(R4));
258        assert_eq!(R128 / R128, Some(R1));
259        assert_eq!(R1 / R2, None);
260        assert_eq!(R64 / R128, None);
261    }
262}