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 #[inline]
77 pub const fn log2(self) -> u8 {
78 self as u8
79 }
80
81 #[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 #[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 #[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 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 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#[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}