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    /// Returns the logarithm base 2 of this resolution.
65    ///
66    /// This is always an exact integer value; all resolutions are powers of 2.
67    ///
68    /// # Example
69    ///
70    /// ```
71    /// # use all_is_cubes_base::resolution::Resolution;
72    ///
73    /// assert_eq!(Resolution::R1.log2(), 0);
74    /// assert_eq!(Resolution::R16.log2(), 4);
75    /// ```
76    #[inline]
77    pub const fn log2(self) -> u8 {
78        self as u8
79    }
80
81    /// Returns the reciprocal of this resolution; that is, the scale factor from
82    /// voxels to blocks of this resolution.
83    ///
84    /// Equivalent to `f32::from(self).recip()` but does not perform division.
85    #[inline]
86    pub const fn recip_f32(self) -> f32 {
87        match self {
88            Self::R1 => const { 1.0f32.recip() },
89            Self::R2 => const { 2.0f32.recip() },
90            Self::R4 => const { 4.0f32.recip() },
91            Self::R8 => const { 8.0f32.recip() },
92            Self::R16 => const { 16.0f32.recip() },
93            Self::R32 => const { 32.0f32.recip() },
94            Self::R64 => const { 64.0f32.recip() },
95            Self::R128 => const { 128.0f32.recip() },
96        }
97    }
98
99    /// Returns the reciprocal of this resolution; that is, the scale factor from
100    /// voxels to blocks of this resolution.
101    ///
102    /// Equivalent to `f64::from(self).recip()` but does not perform division.
103    #[inline]
104    pub const fn recip_f64(self) -> f64 {
105        match self {
106            Self::R1 => const { 1.0f64.recip() },
107            Self::R2 => const { 2.0f64.recip() },
108            Self::R4 => const { 4.0f64.recip() },
109            Self::R8 => const { 8.0f64.recip() },
110            Self::R16 => const { 16.0f64.recip() },
111            Self::R32 => const { 32.0f64.recip() },
112            Self::R64 => const { 64.0f64.recip() },
113            Self::R128 => const { 128.0f64.recip() },
114        }
115    }
116}
117
118impl fmt::Debug for Resolution {
119    #[allow(clippy::missing_inline_in_public_items)]
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        GridCoordinate::from(*self).fmt(f)
122    }
123}
124impl fmt::Display for Resolution {
125    #[allow(clippy::missing_inline_in_public_items)]
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        GridCoordinate::from(*self).fmt(f)
128    }
129}
130
131macro_rules! impl_try_from {
132    ($t:ty) => {
133        impl TryFrom<$t> for Resolution {
134            type Error = IntoResolutionError<$t>;
135            #[inline]
136            fn try_from(value: $t) -> Result<Self, Self::Error> {
137                match value {
138                    1 => Ok(Self::R1),
139                    2 => Ok(Self::R2),
140                    4 => Ok(Self::R4),
141                    8 => Ok(Self::R8),
142                    16 => Ok(Self::R16),
143                    32 => Ok(Self::R32),
144                    64 => Ok(Self::R64),
145                    128 => Ok(Self::R128),
146                    _ => Err(IntoResolutionError(value)),
147                }
148            }
149        }
150    };
151}
152impl_try_from!(i16);
153impl_try_from!(i32);
154impl_try_from!(i64);
155impl_try_from!(i128);
156impl_try_from!(isize);
157impl_try_from!(u16);
158impl_try_from!(u32);
159impl_try_from!(u64);
160impl_try_from!(u128);
161impl_try_from!(usize);
162
163impl From<Resolution> for i32 {
164    /// ```
165    /// # mod all_is_cubes { pub mod block { pub use all_is_cubes_base::resolution::Resolution; } }
166    /// use all_is_cubes::block::Resolution;
167    ///
168    /// assert_eq!(64, i32::from(Resolution::R64));
169    /// ```
170    #[inline]
171    fn from(r: Resolution) -> i32 {
172        1 << (r as i32)
173    }
174}
175impl From<Resolution> for u16 {
176    #[inline]
177    fn from(r: Resolution) -> u16 {
178        1 << (r as u16)
179    }
180}
181impl From<Resolution> for u32 {
182    #[inline]
183    fn from(r: Resolution) -> u32 {
184        1 << (r as u32)
185    }
186}
187impl From<Resolution> for usize {
188    #[inline]
189    fn from(r: Resolution) -> usize {
190        1 << (r as usize)
191    }
192}
193impl From<Resolution> for f32 {
194    #[inline]
195    fn from(r: Resolution) -> f32 {
196        u16::from(r).into()
197    }
198}
199impl From<Resolution> for f64 {
200    #[inline]
201    fn from(r: Resolution) -> f64 {
202        u16::from(r).into()
203    }
204}
205
206impl ops::Mul<Resolution> for Resolution {
207    type Output = Option<Resolution>;
208
209    #[inline]
210    fn mul(self, rhs: Resolution) -> Self::Output {
211        // not the most efficient way to implement this, but straightforward
212        Self::try_from(u32::from(self) * u32::from(rhs)).ok()
213    }
214}
215
216impl ops::Div<Resolution> for Resolution {
217    type Output = Option<Resolution>;
218
219    #[inline]
220    fn div(self, rhs: Resolution) -> Self::Output {
221        // not the most efficient way to implement this, but straightforward
222        Self::try_from(u32::from(self) / u32::from(rhs)).ok()
223    }
224}
225
226#[cfg(feature = "serde")]
227impl serde::Serialize for Resolution {
228    #[allow(clippy::missing_inline_in_public_items)]
229    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
230        u16::from(*self).serialize(serializer)
231    }
232}
233
234#[cfg(feature = "serde")]
235impl<'de> serde::Deserialize<'de> for Resolution {
236    #[allow(clippy::missing_inline_in_public_items)]
237    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
238        u16::deserialize(deserializer)?.try_into().map_err(serde::de::Error::custom)
239    }
240}
241
242/// Error type produced by [`TryFrom`] for [`Resolution`], and deserializing resolutions,
243/// when the number is not a permitted resolution value.
244#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
245pub struct IntoResolutionError<N>(N);
246
247impl<N: fmt::Display + fmt::Debug> core::error::Error for IntoResolutionError<N> {}
248
249impl<N: fmt::Display> fmt::Display for IntoResolutionError<N> {
250    #[allow(clippy::missing_inline_in_public_items)]
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        write!(
253            f,
254            "{number} is not a permitted resolution; must be a power of 2 between 1 and 127",
255            number = self.0
256        )
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263    use Resolution::*;
264    use alloc::vec::Vec;
265    use exhaust::Exhaust as _;
266
267    const RS: [Resolution; 8] = [R1, R2, R4, R8, R16, R32, R64, R128];
268
269    #[test]
270    fn test_list_is_complete() {
271        assert_eq!(
272            Vec::from(RS),
273            Resolution::exhaust().collect::<Vec<Resolution>>()
274        );
275    }
276
277    #[test]
278    fn max_is_max() {
279        assert_eq!(Resolution::MAX, *RS.last().unwrap());
280        assert_eq!(None, Resolution::MAX.double());
281    }
282
283    #[test]
284    fn resolution_steps() {
285        for i in 0..RS.len() - 1 {
286            assert_eq!(RS[i].double().unwrap(), RS[i + 1]);
287            assert_eq!(RS[i + 1].halve().unwrap(), RS[i]);
288        }
289    }
290
291    #[test]
292    fn resolution_values() {
293        assert_eq!(RS.map(i32::from), [1, 2, 4, 8, 16, 32, 64, 128]);
294        assert_eq!(RS.map(u16::from), [1, 2, 4, 8, 16, 32, 64, 128]);
295        assert_eq!(RS.map(u32::from), [1, 2, 4, 8, 16, 32, 64, 128]);
296        assert_eq!(RS.map(usize::from), [1, 2, 4, 8, 16, 32, 64, 128]);
297    }
298
299    #[test]
300    fn mul() {
301        assert_eq!(R4 * R2, Some(R8));
302        assert_eq!(R128 * R2, None);
303        assert_eq!(R2 * R128, None);
304    }
305
306    #[test]
307    fn div() {
308        assert_eq!(R8 / R2, Some(R4));
309        assert_eq!(R128 / R128, Some(R1));
310        assert_eq!(R1 / R2, None);
311        assert_eq!(R64 / R128, None);
312    }
313
314    #[test]
315    fn recip() {
316        for resolution in RS {
317            assert_eq!(resolution.recip_f32(), f32::from(resolution).recip());
318            assert_eq!(resolution.recip_f64(), f64::from(resolution).recip());
319        }
320    }
321}