1use core::ops;
2
3#[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] pub 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 pub const MAX: Resolution = Resolution::R128;
26
27 #[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 #[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)] 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 #[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 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 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#[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}