all_is_cubes_base/math/
vol.rs

1#![allow(
2    clippy::module_name_repetitions,
3    reason = "false positive; TODO: remove after Rust 1.84 is released"
4)]
5
6use alloc::boxed::Box;
7use alloc::sync::Arc;
8use core::fmt;
9use core::ops::{Deref, DerefMut};
10
11#[cfg(doc)]
12use alloc::vec::Vec;
13
14use euclid::{vec3, Point3D};
15use manyfmt::Refmt as _;
16
17use crate::math::{Axis, Cube, GridAab, GridCoordinate, GridIter, GridPoint, GridVector};
18
19// #[derive(Clone, Copy, Debug)]
20// pub struct XMaj;
21
22/// Z-major ordering: linearly adjacent elements have adjacent Z coordinates.
23///
24/// `[0, 0, 0], [0, 0, 1], [0, 0, 2], ..., [0, 1, 0], [0, 1, 1], ...`
25///
26/// Use this type with [`Vol`] to store volume data in this order.
27#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
28#[expect(clippy::exhaustive_structs)]
29pub struct ZMaj;
30
31/// Type for volume data stored in a slice, or for generating linear indexing.
32///
33/// * `C` is some slice container type, e.g. `&[T]` or `Box<[T]>`.
34///   It may also be `()` to describe a linearization without actually storing data.
35/// * `O` specifies the choice of linearization.
36///   Currently, only one choice exists, [`ZMaj`].
37///
38/// In addition to the data, each [`Vol`] stores the [`GridAab`] defining its size;
39/// the container's length must be equal to the volume of that AAB. Whether that can be
40/// relied upon entirely depends on whether the specific container value produces
41/// the same length of slice every time it is [`Deref`]erenced without mutating it directly.
42/// For example, `Vec<T>` and `Box<[T]>` satisfy this criterion; the fact that [`Vec`] has
43/// length-mutating operations is irrelevant because no `&mut Vec<T>` is exposed.
44///
45/// A [`Vol`] whose volume exceeds [`usize::MAX`] cannot exist.
46#[derive(Clone, Copy, Eq, Hash, PartialEq)]
47pub struct Vol<C, O = ZMaj> {
48    /// Invariant: `bounds` has a volume that is at most [`usize::MAX`].
49    bounds: GridAab,
50    ordering: O,
51    /// Invariant: `contents.deref().len()`, if it exists, equals `bounds.volume()`.
52    contents: C,
53}
54
55impl<O> Vol<(), O> {
56    /// Use `GridAab::to_vol()` to call this.
57    pub(crate) fn new_dataless(bounds: GridAab, ordering: O) -> Result<Self, VolLengthError> {
58        if bounds.volume().is_none() {
59            Err(VolLengthError {
60                input_length: None,
61                bounds,
62            })
63        } else {
64            Ok(Self {
65                bounds,
66                ordering,
67                contents: (),
68            })
69        }
70    }
71
72    /// Attach some data to this dataless `Vol`.
73    ///
74    /// Returns a [`VolLengthError`] if the number of elements does not match
75    /// [`bounds.volume()`](GridAab::volume).
76    #[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
77    pub fn with_elements<C, V>(self, elements: C) -> Result<Vol<C, O>, VolLengthError>
78    where
79        C: Deref<Target = [V]>,
80    {
81        if elements.len() == self.volume() {
82            Ok(Vol {
83                bounds: self.bounds,
84                ordering: self.ordering,
85                contents: elements,
86            })
87        } else {
88            Err(VolLengthError {
89                input_length: Some(elements.len()),
90                bounds: self.bounds(),
91            })
92        }
93    }
94}
95
96impl Vol<(), ZMaj> {
97    /// Divide `self` into two approximately equal-sized parts which, if they had elements, would
98    /// each be contiguous in the linear ordering.
99    ///
100    /// Returns [`None`] if `self` does not have at least two cubes.
101    ///
102    /// Note that this is one of several `subdivide()` methods for different container types;
103    /// it is also implemented for immutable and mutable references.
104    /// These are intended to be useful in executing parallel algorithms on volume data.
105    #[allow(clippy::missing_inline_in_public_items)]
106    pub fn subdivide(self) -> Result<[Self; 2], Self> {
107        match find_zmaj_subdivision(self) {
108            Some((lower_half, upper_half, _)) => Ok([lower_half, upper_half]),
109            _ => Err(self),
110        }
111    }
112}
113
114/// Constructors from linear containers.
115impl<C, O: Default, V> Vol<C, O>
116where
117    C: Deref<Target = [V]>,
118{
119    /// Constructs a `Vol<C>` containing the provided elements, which must be in the
120    /// ordering specified by `O`.
121    ///
122    /// Returns a [`VolLengthError`] if the number of elements does not match
123    /// [`bounds.volume()`](GridAab::volume).
124    //---
125    // TODO: Remove this in favor of with_elements()?
126    #[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
127    pub fn from_elements(bounds: GridAab, elements: impl Into<C>) -> Result<Self, VolLengthError> {
128        let elements = elements.into();
129        if Some(elements.len()) == bounds.volume() {
130            Ok(Vol {
131                bounds,
132                ordering: O::default(),
133                contents: elements,
134            })
135        } else {
136            Err(VolLengthError {
137                input_length: Some(elements.len()),
138                bounds,
139            })
140        }
141    }
142}
143
144/// Constructors from elements.
145//---
146// TODO: This should be `O: Ordering` instead of `ZMaj` once we have alternative orderings
147#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
148impl<C, V> Vol<C, ZMaj>
149where
150    // Note that the Deref bound is necessary to give this a unique `V`.
151    C: Deref<Target = [V]> + FromIterator<V>,
152{
153    /// Constructs a `Vol<C>` by using the provided function to compute a value
154    /// for each point.
155    ///
156    /// Panics if `bounds` has a volume exceeding `usize::MAX`.
157    /// (But there will likely be a memory allocation failure well below that point.)
158    #[inline]
159    pub fn from_fn<F>(bounds: GridAab, f: F) -> Self
160    where
161        F: FnMut(Cube) -> V,
162    {
163        let bounds = bounds.to_vol::<ZMaj>().unwrap();
164        bounds
165            .with_elements(bounds.iter_cubes().map(f).collect())
166            .unwrap()
167    }
168
169    /// Constructs a `Vol<C>` by cloning the provided value for each point.
170    #[inline]
171    pub fn repeat(bounds: GridAab, value: V) -> Self
172    where
173        V: Clone,
174    {
175        Self::from_fn(bounds, |_| value.clone())
176    }
177
178    /// Constructs a `Vol<C>` with a single value, in bounds `ORIGIN_CUBE`.
179    ///
180    /// If the single element should be at a different location, you can call
181    /// [`.translate(offset)`](Self::translate), or use [`Vol::from_elements()`]
182    /// instead.
183    #[inline]
184    pub fn from_element(value: V) -> Self {
185        Self::from_elements(GridAab::ORIGIN_CUBE, core::iter::once(value).collect::<C>()).unwrap()
186    }
187
188    /// Constructs a [`Vol<Box<[V]>>`] from nested Rust arrays in [Z][Y][X] order with the Y axis
189    /// mirrored. The result's bounds's lower bounds are zero.
190    ///
191    /// Note: The current implementation requires that `V` implement [`Clone`], and will
192    /// clone each element once, but this may be improved in the future.
193    // TODO: Decide if this is a good public interface.
194    // TODO: Reimplement this in terms of adopting the elements as a linear array.
195    // TODO: Test.
196    #[doc(hidden)] // used by all-is-cubes-content
197    #[expect(clippy::needless_pass_by_value)]
198    pub fn from_y_flipped_array<const DX: usize, const DY: usize, const DZ: usize>(
199        array: [[[V; DX]; DY]; DZ],
200    ) -> Self
201    where
202        V: Clone,
203    {
204        Self::from_fn(
205            GridAab::from_lower_size([0, 0, 0], vec3(DX, DY, DZ).to_u32()),
206            |p| array[p.z as usize][(DY - 1) - (p.y as usize)][p.x as usize].clone(),
207        )
208    }
209}
210
211impl<C, O> Vol<C, O> {
212    /// Returns the [`GridAab`] specifying the bounds of this volume data.
213    #[inline]
214    pub fn bounds(&self) -> GridAab {
215        self.bounds
216    }
217
218    /// Returns the volume, also known as the number of elements.
219    #[inline]
220    pub fn volume(&self) -> usize {
221        // Ideally, we could specialize on C and return self.contents.len() if possible,
222        // as it doesn't require doing any multiplications, but that's not currently possible
223        // in Rust.
224
225        let size = self.bounds.size();
226        // This will not overflow, as an invariant of the `Vol` type.
227        size.width as usize * size.height as usize * size.depth as usize
228    }
229
230    /// Extracts the linear contents, discarding the bounds and ordering.
231    #[inline]
232    pub fn into_elements(self) -> C {
233        self.contents
234    }
235
236    /// Returns a `Vol` with the same bounds and ordering but no data.
237    ///
238    /// This is the inverse operation to [`Vol::with_elements()`].
239    #[inline]
240    pub fn without_elements(&self) -> Vol<(), O>
241    where
242        O: Clone,
243    {
244        Vol {
245            bounds: self.bounds,
246            ordering: self.ordering.clone(),
247            contents: (),
248        }
249    }
250
251    /// Translates the volume without affecting its contents.
252    ///
253    /// Panics if this would cause numeric overflow.
254    ///
255    /// TODO: example
256    #[must_use]
257    #[track_caller]
258    #[inline]
259    pub fn translate(self, offset: impl Into<GridVector>) -> Self {
260        self.translate_impl(offset.into())
261    }
262
263    #[track_caller]
264    #[inline]
265    fn translate_impl(mut self, offset: GridVector) -> Self {
266        let new_bounds = self.bounds.translate(offset);
267        if new_bounds.size() != self.bounds.size() {
268            // We can't just continue like `GridAab::translate` does, because that would
269            // break the invariant that self.volume() == self.contents.len().
270            panic!("Vol::translate() offset caused numeric overflow");
271        }
272        self.bounds = new_bounds;
273        self
274    }
275
276    // TODO: reconcile this with from_elements() — should only be implemented once.
277    #[doc(hidden)] // TODO: good public api?
278    #[inline]
279    pub fn map_container<C2, V2, F>(self, f: F) -> Vol<C2, O>
280    where
281        F: FnOnce(C) -> C2,
282        C2: Deref<Target = [V2]>,
283    {
284        let bounds = self.bounds;
285        let volume = self.volume();
286        let contents = f(self.contents);
287        if contents.len() != volume {
288            panic!(
289                "{}",
290                VolLengthError {
291                    input_length: Some(contents.len()),
292                    bounds: self.bounds,
293                }
294            )
295        }
296        Vol {
297            bounds,
298            ordering: self.ordering,
299            contents,
300        }
301    }
302}
303
304impl<C> Vol<C, ZMaj> {
305    /// Iterate over all cubes that this contains, in the order of the linearization,
306    /// without including the stored data (if there is any).
307    #[inline]
308    pub fn iter_cubes(&self) -> GridIter {
309        GridIter::new(self.bounds)
310    }
311
312    /// Determines whether a unit cube lies within this volume and, if it does, returns the
313    /// linearized slice index into it.
314    ///
315    /// The linearized element order is defined by the `O` type.
316    ///
317    /// ```
318    /// # extern crate all_is_cubes_base as all_is_cubes;
319    /// use all_is_cubes::math::{Vol, GridAab};
320    ///
321    /// let vol = GridAab::from_lower_size([0, 0, 0], [10, 10, 10]).to_vol().unwrap();
322    ///
323    /// assert_eq!(vol.index([0, 0, 0].into()), Some(0));
324    /// assert_eq!(vol.index([1, 2, 3].into()), Some(123));
325    /// assert_eq!(vol.index([9, 9, 9].into()), Some(999));
326    /// assert_eq!(vol.index([0, 0, -1].into()), None);
327    /// assert_eq!(vol.index([0, 0, 10].into()), None);
328    /// ```
329    ///
330    /// TODO: more example, less unit-test
331    #[inline(always)] // very hot code
332    pub fn index(&self, cube: Cube) -> Option<usize> {
333        let sizes = self.bounds.size();
334
335        // This might overflow and wrap, but if it does, the result will still be out
336        // of bounds, just in the other direction, because wrapping subtraction is an
337        // injective mapping of integers, and every in-bounds maps to in-bounds, so
338        // every out-of-bounds must also map to out-of-bounds.
339        let deoffsetted: GridPoint = GridPoint::from(cube)
340            .zip(self.bounds.lower_bounds(), GridCoordinate::wrapping_sub)
341            .to_point();
342
343        // Bounds check, expressed as a single unsigned comparison.
344        if (deoffsetted.x as u32 >= sizes.width)
345            | (deoffsetted.y as u32 >= sizes.height)
346            | (deoffsetted.z as u32 >= sizes.depth)
347        {
348            return None;
349        }
350
351        // Convert to usize for indexing.
352        // This cannot overflow because:
353        // * We just checked it is not negative
354        // * We just checked it is not greater than `self.sizes[i]`, which is an `i32`
355        // * We don't support platforms with `usize` smaller than 32 bits
356        // We use `as usize` rather than `deoffsetted.to_usize()` because the latter has an
357        // overflow check.
358        let ixvec: Point3D<usize, _> = deoffsetted.map(|s| s as usize);
359
360        // Compute index.
361        // Always use wrapping (rather than maybe-checked) arithmetic, because we
362        // checked the criteria for it to not overflow.
363        Some(
364            (ixvec
365                .x
366                .wrapping_mul(sizes.height as usize)
367                .wrapping_add(ixvec.y))
368            .wrapping_mul(sizes.depth as usize)
369            .wrapping_add(ixvec.z),
370        )
371    }
372}
373
374/// Linear data access.
375#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
376impl<C, O, V> Vol<C, O>
377where
378    C: Deref<Target = [V]>,
379    O: Copy,
380{
381    /// Return a [`Vol`] that borrows the contents of this one.
382    pub fn as_ref(&self) -> Vol<&[V], O> {
383        Vol {
384            bounds: self.bounds,
385            ordering: self.ordering,
386            contents: self.as_linear(),
387        }
388    }
389
390    /// Return a [`Vol`] that mutably borrows the contents of this one.
391    pub fn as_mut(&mut self) -> Vol<&mut [V], O>
392    where
393        C: DerefMut,
394    {
395        Vol {
396            bounds: self.bounds,
397            ordering: self.ordering,
398            contents: self.as_linear_mut(),
399        }
400    }
401
402    /// Returns the linear contents viewed as a slice.
403    pub fn as_linear(&self) -> &[V] {
404        let s = &*self.contents;
405        debug_assert_eq!(s.len(), self.volume());
406        s
407    }
408
409    /// Returns the linear contents viewed as a mutable slice.
410    pub fn as_linear_mut(&mut self) -> &mut [V]
411    where
412        C: DerefMut,
413    {
414        let s = &mut *self.contents;
415        debug_assert_eq!(s.len(), self.bounds.volume().unwrap());
416        s
417    }
418}
419
420impl<'a, V> Vol<&'a [V], ZMaj> {
421    /// Returns the element at `position` of this volume data, or [`None`] if `position` is out
422    /// of bounds.
423    ///
424    /// This differs from [`Self::get()`] in that it inherits the lifetime of the container
425    /// reference, rather than reborrowing. It is therefore only available for `Vol<&'a [V]>`.
426    #[inline]
427    pub fn get_ref(&self, position: impl Into<Cube>) -> Option<&'a V> {
428        let index = self.index(position.into())?;
429        Some(&self.contents[index])
430    }
431
432    /// Divide `self` into two approximately equal-sized parts,
433    /// each of which refers to the appropriate sub-slice of elements.
434    ///
435    /// Returns [`None`] if `self` does not have at least two cubes.
436    ///
437    /// Note that this is one of several `subdivide()` methods for different container types;
438    /// it is also implemented for mutable references and `()`.
439    /// These are intended to be useful in executing parallel algorithms on volume data.
440    #[allow(clippy::missing_inline_in_public_items)]
441    pub fn subdivide(self) -> Result<[Self; 2], Self> {
442        match find_zmaj_subdivision(self.without_elements()) {
443            Some((lower_half, upper_half, lower_half_len)) => {
444                let (lower_contents, upper_contents) = self.contents.split_at(lower_half_len);
445
446                Ok([
447                    lower_half
448                        .with_elements(lower_contents)
449                        .unwrap_or_else(unreachable_wrong_size),
450                    upper_half
451                        .with_elements(upper_contents)
452                        .unwrap_or_else(unreachable_wrong_size),
453                ])
454            }
455            _ => Err(self),
456        }
457    }
458}
459
460impl<V> Vol<&mut [V], ZMaj> {
461    /// Divide `self` into two approximately equal-sized parts.
462    /// each of which refers to the appropriate sub-slice of elements.
463    ///
464    /// `filter` may be used to reject subdivisions that are too small or otherwise unsuitable;
465    /// it is passed the bounds of the two slices that would be returned.
466    ///
467    /// Returns [`Err`] containing `self` if `self` does not have at least two cubes,
468    /// or if `filter` returns `false`.
469    ///
470    /// Note that this is one of several `subdivide()` methods for different container types;
471    /// it is also implemented for immutable references and `()`.
472    /// These are intended to be useful in executing parallel algorithms on volume data.
473    //---
474    // Design note: This has the `filter` parameter, where other `subdivide()`s do not,
475    // because otherwise the caller may have trouble with conditional returns of mutable borrows
476    // (NLL Problem Case #3); the filter allows cancelling the split rather than discarding it.
477    #[allow(clippy::missing_inline_in_public_items)]
478    pub fn subdivide(self, filter: impl FnOnce([Vol<()>; 2]) -> bool) -> Result<[Self; 2], Self> {
479        match find_zmaj_subdivision(self.without_elements()) {
480            Some((lower_half, upper_half, lower_half_len)) if filter([lower_half, upper_half]) => {
481                let (lower_contents, upper_contents) = self.contents.split_at_mut(lower_half_len);
482
483                Ok([
484                    lower_half
485                        .with_elements(lower_contents)
486                        .unwrap_or_else(unreachable_wrong_size),
487                    upper_half
488                        .with_elements(upper_contents)
489                        .unwrap_or_else(unreachable_wrong_size),
490                ])
491            }
492            _ => Err(self),
493        }
494    }
495}
496
497impl<V: Clone, O> Vol<Arc<[V]>, O> {
498    /// Returns the linear contents viewed as a mutable slice, as if by [`Arc::make_mut()`].
499    #[doc(hidden)] // TODO: good public API?
500    #[allow(clippy::missing_inline_in_public_items)]
501    pub fn make_linear_mut(&mut self) -> &mut [V] {
502        let slice: &mut [V] = Arc::make_mut(&mut self.contents);
503        debug_assert_eq!(slice.len(), self.bounds.volume().unwrap());
504        slice
505    }
506}
507
508/// Element lookup operations by 3D coordinates.
509#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
510impl<C, V> Vol<C, ZMaj>
511where
512    C: Deref<Target = [V]>,
513{
514    /// Returns the element at `position` of this volume data, or [`None`] if `position` is out
515    /// of bounds.
516    #[inline]
517    pub fn get(&self, position: impl Into<Cube>) -> Option<&V> {
518        let index = self.index(position.into())?;
519        Some(&self.as_linear()[index])
520    }
521
522    /// Returns a mutable reference to the element at `position` of this volume data,
523    /// or [`None`] if `position` is out of bounds.
524    #[inline]
525    pub fn get_mut(&mut self, position: impl Into<Cube>) -> Option<&mut V>
526    where
527        C: DerefMut,
528    {
529        let index = self.index(position.into())?;
530        Some(&mut self.as_linear_mut()[index])
531    }
532
533    /// Iterates over all the cubes and values in this volume data, in the ordering specified
534    /// by the `O` type parameter.
535    pub fn iter<'s>(&'s self) -> impl Iterator<Item = (Cube, &'s V)> + Clone
536    where
537        V: 's,
538    {
539        self.iter_cubes().zip(self.as_linear().iter())
540    }
541
542    /// Iterates by mutable reference over all the cubes and values in this volume data,
543    /// in the ordering specified by the `O` type parameter.
544    pub fn iter_mut<'s>(&'s mut self) -> impl Iterator<Item = (Cube, &'s mut V)>
545    where
546        C: DerefMut,
547        V: 's,
548    {
549        self.iter_cubes().zip(self.as_linear_mut().iter_mut())
550    }
551}
552
553#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
554impl<V, O> Vol<Box<[V]>, O> {
555    /// Apply `f` to each element and collect the results into the same shape and ordering.
556    pub fn map<T, F>(self, f: F) -> Vol<Box<[T]>, O>
557    where
558        F: FnMut(V) -> T,
559    {
560        Vol {
561            bounds: self.bounds,
562            ordering: self.ordering,
563            // When we switch to Rust 2024 edition, replace this qualified call with a method call.
564            contents: <Box<[V]> as IntoIterator>::into_iter(self.contents)
565                .map(f)
566                .collect(),
567        }
568    }
569}
570
571#[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
572impl<C: fmt::Debug, O: fmt::Debug> fmt::Debug for Vol<C, O> {
573    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574        // Note: If specialization was available we'd like to use it to print the elements under
575        // our own control if there are elements, but as it is, we'd rather preserve functionality
576        // for `Vol<()>`, which means we always print self.contents as a whole or not at all.
577
578        let Self {
579            bounds,
580            ordering,
581            contents,
582        } = self;
583
584        let mut ds = f.debug_struct(&format!(
585            "Vol<{contents_type}, {ordering:?}>",
586            contents_type = core::any::type_name::<C>(),
587        ));
588        ds.field("bounds", &bounds);
589        let volume = self.volume();
590        if core::any::type_name::<C>() == core::any::type_name::<()>() {
591            // don't print "contents: ()"
592        } else if volume > 32 {
593            ds.field(
594                "contents",
595                &format!("[...{volume} elements]").refmt(&manyfmt::formats::Unquote),
596            );
597        } else {
598            ds.field("contents", &contents);
599        }
600        ds.finish()
601    }
602}
603
604impl<P, C, V> core::ops::Index<P> for Vol<C, ZMaj>
605where
606    P: Into<Cube>,
607    C: Deref<Target = [V]>,
608{
609    type Output = V;
610
611    /// Returns the element at `position` of this volume data,
612    /// or panics if `position` is out of bounds.
613    ///
614    /// Use [`Vol::get()`] for a non-panicing alternative.
615    #[inline(always)] // measured faster on wasm32 in worldgen
616    fn index(&self, position: P) -> &Self::Output {
617        let position: Cube = position.into();
618        if let Some(index) = self.index(position) {
619            &self.contents[index]
620        } else {
621            panic!(
622                "position {:?} out of Vol bounds {:?}",
623                position, self.bounds
624            )
625        }
626    }
627}
628impl<P, C, V> core::ops::IndexMut<P> for Vol<C, ZMaj>
629where
630    P: Into<Cube>,
631    C: DerefMut<Target = [V]>,
632{
633    /// Returns the element at `position` of this volume data,
634    /// or panics if `position` is out of bounds.
635    #[inline(always)]
636    fn index_mut(&mut self, position: P) -> &mut Self::Output {
637        let position: Cube = position.into();
638        if let Some(index) = self.index(position) {
639            &mut self.contents[index]
640        } else {
641            panic!(
642                "position {:?} out of Vol bounds {:?}",
643                position, self.bounds
644            )
645        }
646    }
647}
648
649mod aab_compat {
650    use super::*;
651
652    impl<O> PartialEq<GridAab> for Vol<(), O> {
653        #[inline]
654        fn eq(&self, other: &GridAab) -> bool {
655            self.bounds() == *other
656        }
657    }
658
659    impl<O> PartialEq<Vol<(), O>> for GridAab {
660        #[inline]
661        fn eq(&self, other: &Vol<(), O>) -> bool {
662            *self == other.bounds()
663        }
664    }
665}
666
667#[cfg(feature = "arbitrary")]
668#[allow(clippy::missing_inline_in_public_items)]
669pub(crate) mod vol_arb {
670    use super::*;
671    use arbitrary::Arbitrary;
672
673    /// Let's not spend too much memory on generating arbitrary length vectors.
674    /// This does reduce coverage...
675    const MAX_VOLUME: usize = 2_usize.pow(16);
676
677    /// Size hint for [`Vol::arbitrary_with_max_volume()`].
678    pub(crate) const ARBITRARY_BOUNDS_SIZE_HINT: (usize, Option<usize>) = {
679        // 6 bounding coordinates plus one permutation selection.
680        // Depending on the volume, we could end up consuming as little as 1 byte each
681        // for the sizes, and none for the positions, because `int_in_range()` uses the range
682        // to decide how many bytes to consume.
683        let gc = size_of::<GridCoordinate>();
684        (3 + 1, Some(gc * 6 + 1))
685    };
686
687    impl<O: Default> Vol<(), O> {
688        #[cfg(feature = "arbitrary")]
689        #[doc(hidden)]
690        pub fn arbitrary_with_max_volume(
691            u: &mut arbitrary::Unstructured<'_>,
692            volume: usize,
693        ) -> arbitrary::Result<Self> {
694            // Pick sizes within the volume constraint.
695            let mut limit: u32 = volume.try_into().unwrap_or(u32::MAX);
696            let size_1 = u.int_in_range(0..=limit)?;
697            limit /= size_1.max(1);
698            let size_2 = u.int_in_range(0..=limit)?;
699            limit /= size_2.max(1);
700            let size_3 = u.int_in_range(0..=limit)?;
701
702            // Shuffle the sizes to remove any bias.
703            let sizes = *u.choose(&[
704                vec3(size_1, size_2, size_3),
705                vec3(size_1, size_3, size_2),
706                vec3(size_2, size_1, size_3),
707                vec3(size_2, size_3, size_1),
708                vec3(size_3, size_1, size_2),
709                vec3(size_3, size_2, size_1),
710            ])?;
711
712            // Compute lower bounds that are valid for the sizes.
713            let possible_lower_bounds = sizes.map(|coord| {
714                GridCoordinate::MIN..=GridCoordinate::MAX.saturating_sub_unsigned(coord)
715            });
716            let lower_bounds = GridPoint::new(
717                u.int_in_range(possible_lower_bounds.x)?,
718                u.int_in_range(possible_lower_bounds.y)?,
719                u.int_in_range(possible_lower_bounds.z)?,
720            );
721
722            Ok(GridAab::from_lower_size(lower_bounds, sizes)
723                .to_vol()
724                .expect("GridAab as Arbitrary failed to compute valid bounds"))
725        }
726    }
727
728    impl<'a, V: Arbitrary<'a>, C> Arbitrary<'a> for Vol<C, ZMaj>
729    where
730        C: FromIterator<V> + Deref<Target = [V]>,
731    {
732        fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
733            let bounds = Vol::<()>::arbitrary_with_max_volume(u, MAX_VOLUME)?;
734            let contents: C = u
735                .arbitrary_iter()?
736                .take(bounds.volume())
737                .collect::<Result<C, _>>()?;
738            bounds
739                .with_elements(contents)
740                .map_err(|_| arbitrary::Error::NotEnoughData)
741        }
742
743        fn size_hint(depth: usize) -> (usize, Option<usize>) {
744            // recommended impl from trait documentation
745            Self::try_size_hint(depth).unwrap_or_default()
746        }
747
748        fn try_size_hint(
749            depth: usize,
750        ) -> Result<(usize, Option<usize>), arbitrary::MaxRecursionReached> {
751            arbitrary::size_hint::try_recursion_guard(depth, |depth| {
752                let (lower, upper) = V::try_size_hint(depth)?;
753                Ok(arbitrary::size_hint::and(
754                    ARBITRARY_BOUNDS_SIZE_HINT,
755                    (
756                        lower.saturating_mul(MAX_VOLUME),
757                        upper.map(|u| u.saturating_mul(MAX_VOLUME)),
758                    ),
759                ))
760            })
761        }
762    }
763}
764
765/// Error from [`Vol::from_elements()`] being given the wrong length,
766/// or from constructing a [`Vol`] with a volume greater than [`usize::MAX`].
767#[derive(Clone, Copy, Debug, Eq, PartialEq)]
768pub struct VolLengthError {
769    /// The length of the linear data, or [`None`] if we're constructing a dataless [`Vol`].
770    input_length: Option<usize>,
771    /// The attempted bounds, whose volume is either unequal to `input_length` or overflowing.
772    bounds: GridAab,
773}
774
775impl core::error::Error for VolLengthError {}
776
777impl fmt::Display for VolLengthError {
778    #[allow(clippy::missing_inline_in_public_items)]
779    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
780        let Self {
781            input_length,
782            bounds,
783        } = self;
784        match (input_length, bounds.volume()) {
785            (Some(input_length), Some(volume)) => write!(
786                f,
787                "data of length {input_length} cannot fill volume {volume} of {bounds:?}",
788            ),
789
790            (Some(input_length), None) => write!(
791                f,
792                "data of length {input_length} cannot fill {bounds:?}, \
793                    which is too large to be linearized at all",
794            ),
795
796            (None, None) => write!(
797                f,
798                "{bounds:?} has a volume of {volume_u128}, \
799                    which is too large to be linearized",
800                // u128 is large enough to hold the multiplication of three u32s
801                volume_u128 = bounds.size().cast::<u128>().volume(),
802            ),
803
804            (None, Some(_)) => write!(f, "<malformed error {self:?}>"),
805        }
806    }
807}
808
809/// Find a way to split the bounds of a `Vol` which results in two adjacent volumes
810/// whose linear elements are also adjacent.
811/// Returns the two boxes and the linear split point.
812fn find_zmaj_subdivision(vol: Vol<()>) -> Option<(Vol<()>, Vol<()>, usize)> {
813    // The order of these tests must reflect the ordering in use
814    // for the result to be valid.
815    let bounds = vol.bounds();
816    for axis in [Axis::X, Axis::Y, Axis::Z] {
817        let axis_range = bounds.axis_range(axis);
818        let size: u32 = bounds.size()[axis];
819        if size >= 2 {
820            #[expect(clippy::cast_possible_wrap, reason = "known to fit")]
821            let split_coordinate = axis_range.start + (size / 2) as i32;
822
823            let mut lower_half_ub = bounds.upper_bounds();
824            lower_half_ub[axis] = split_coordinate;
825            let lower_half = GridAab::from_lower_upper(bounds.lower_bounds(), lower_half_ub)
826                .to_vol()
827                .unwrap_or_else(unreachable_wrong_size);
828
829            let mut upper_half_lb = bounds.lower_bounds();
830            upper_half_lb[axis] = split_coordinate;
831            let upper_half = GridAab::from_lower_upper(upper_half_lb, bounds.upper_bounds())
832                .to_vol()
833                .unwrap_or_else(unreachable_wrong_size);
834
835            let lower_half_volume = lower_half.volume();
836            debug_assert_eq!(lower_half_volume + upper_half.volume(), vol.volume());
837            return Some((lower_half, upper_half, lower_half_volume));
838        }
839    }
840    None
841}
842
843/// Function for `.unwrap_or_else()`s inside subdivision operations.
844/// The advantage of this over many `unwrap()`s is generating fewer distinct panic sites for
845/// these cases which are impossible.
846#[cold]
847fn unreachable_wrong_size<T>(error: VolLengthError) -> T {
848    panic!("impossible size mismatch: {error}")
849}
850
851#[cfg(test)]
852mod tests {
853    use super::*;
854    use alloc::string::String;
855    use pretty_assertions::assert_eq;
856
857    type VolBox<T> = Vol<Box<[T]>>;
858
859    fn cube(x: GridCoordinate, y: GridCoordinate, z: GridCoordinate) -> Cube {
860        Cube::new(x, y, z)
861    }
862
863    #[test]
864    fn debug_no_elements() {
865        let vol = GridAab::from_lower_size([10, 0, 0], [4, 1, 1])
866            .to_vol::<ZMaj>()
867            .unwrap();
868        assert_eq!(
869            format!("{vol:#?}"),
870            indoc::indoc! {"
871                Vol<(), ZMaj> {
872                    bounds: GridAab(
873                        10..14 (4),
874                        0..1 (1),
875                        0..1 (1),
876                    ),
877                }\
878            "}
879        )
880    }
881
882    #[test]
883    fn debug_with_contents() {
884        let vol = VolBox::from_fn(GridAab::from_lower_size([10, 0, 0], [4, 1, 1]), |p| p.x);
885        assert_eq!(
886            format!("{vol:#?}"),
887            indoc::indoc! {"
888                Vol<alloc::boxed::Box<[i32]>, ZMaj> {
889                    bounds: GridAab(
890                        10..14 (4),
891                        0..1 (1),
892                        0..1 (1),
893                    ),
894                    contents: [
895                        10,
896                        11,
897                        12,
898                        13,
899                    ],
900                }\
901            "}
902        )
903    }
904
905    #[test]
906    fn debug_without_contents() {
907        let vol = VolBox::from_fn(GridAab::from_lower_size([0, 0, 0], [64, 1, 1]), |p| p.x);
908        assert_eq!(
909            format!("{vol:#?}"),
910            indoc::indoc! {"
911                Vol<alloc::boxed::Box<[i32]>, ZMaj> {
912                    bounds: GridAab(
913                        0..64 (64),
914                        0..1 (1),
915                        0..1 (1),
916                    ),
917                    contents: [...64 elements],
918                }\
919            "}
920        )
921    }
922
923    #[test]
924    fn from_elements() {
925        let bounds = GridAab::from_lower_size([10, 0, 0], [4, 1, 1]);
926        assert_eq!(
927            VolBox::from_fn(bounds, |p| p.x),
928            VolBox::from_elements(bounds, vec![10i32, 11, 12, 13]).unwrap(),
929        );
930    }
931
932    #[test]
933    fn from_elements_error() {
934        let bounds = GridAab::from_lower_size([10, 0, 0], [4, 1, 1]);
935        assert_eq!(
936            VolBox::from_elements(bounds, vec![10i32, 11, 12]),
937            Err(VolLengthError {
938                input_length: Some(3),
939                bounds,
940            })
941        );
942    }
943
944    #[test]
945    fn repeat() {
946        let bounds = GridAab::from_lower_size([10, 0, 0], [2, 2, 1]);
947        assert_eq!(
948            VolBox::repeat(bounds, 9),
949            VolBox::from_elements(bounds, vec![9, 9, 9, 9]).unwrap(),
950        );
951    }
952
953    #[test]
954    fn from_element() {
955        let element = String::from("x");
956        assert_eq!(
957            VolBox::from_element(element.clone()),
958            VolBox::from_elements(GridAab::ORIGIN_CUBE, [element]).unwrap(),
959        );
960    }
961
962    #[test]
963    fn from_y_flipped() {
964        let array = VolBox::from_y_flipped_array([
965            [*b"abcd", *b"efgh", *b"ijkl"],
966            [*b"mnop", *b"qrst", *b"uvwx"],
967        ]);
968        assert_eq!(
969            array,
970            Vol::from_elements(
971                GridAab::from_lower_size([0, 0, 0], [4, 3, 2]),
972                *b"iueqamjvfrbnkwgscolxhtdp"
973            )
974            .unwrap()
975        );
976    }
977
978    #[test]
979    fn index_overflow_low() {
980        // Indexing calculates (point - lower_bounds), so this would overflow in the negative direction if the overflow weren't checked.
981        // Note that MAX - 1 is the highest allowed lower bound since the exclusive upper bound must be representable.
982        let low = GridAab::from_lower_size([GridCoordinate::MAX - 1, 0, 0], [1, 1, 1])
983            .to_vol::<ZMaj>()
984            .unwrap();
985        assert_eq!(low.index(cube(0, 0, 0)), None);
986        assert_eq!(low.index(cube(-1, 0, 0)), None);
987        assert_eq!(low.index(cube(-2, 0, 0)), None);
988        assert_eq!(low.index(cube(GridCoordinate::MIN, 0, 0)), None);
989        // But, an actually in-bounds cube should still work.
990        assert_eq!(low.index(cube(GridCoordinate::MAX - 1, 0, 0)), Some(0));
991    }
992
993    #[test]
994    fn index_overflow_high() {
995        let high = GridAab::from_lower_size([GridCoordinate::MAX - 1, 0, 0], [1, 1, 1])
996            .to_vol::<ZMaj>()
997            .unwrap();
998        assert_eq!(high.index(cube(0, 0, 0)), None);
999        assert_eq!(high.index(cube(1, 0, 0)), None);
1000        assert_eq!(high.index(cube(2, 0, 0)), None);
1001        assert_eq!(high.index(cube(GridCoordinate::MAX - 1, 0, 0)), Some(0));
1002    }
1003
1004    #[test]
1005    fn index_not_overflow_large_volume() {
1006        let vol = GridAab::from_lower_size([0, 0, 0], [2000, 2000, 2000])
1007            .to_vol::<ZMaj>()
1008            .unwrap();
1009        // This value fits in a 32-bit `usize` and is therefore a valid index,
1010        // but it does not fit in a `GridCoordinate` = `i32`.
1011        assert_eq!(
1012            vol.index(cube(1500, 1500, 1500)),
1013            Some(((1500 * 2000) + 1500) * 2000 + 1500)
1014        );
1015    }
1016
1017    /// Test the properties of the `subdivide()` operations, starting from this example.
1018    #[inline(never)]
1019    fn check_subdivide_case(vol: Vol<&mut [Cube]>) {
1020        fn filter(_: [Vol<()>; 2]) -> bool {
1021            true
1022        }
1023
1024        eprintln!("Checking {:?}", vol.bounds());
1025
1026        // Check the elements are as expected
1027        for (cube, &value) in vol.iter() {
1028            assert_eq!(cube, value);
1029        }
1030
1031        if vol.volume() < 2 {
1032            // Never subdivide a cube or empty.
1033            assert!(vol.without_elements().subdivide().is_err()); // () version
1034            assert!(vol.as_ref().subdivide().is_err()); // & version
1035            assert!(vol.subdivide(filter).is_err()); // &mut version
1036        } else {
1037            let Ok([a, b]) = vol.without_elements().subdivide() else {
1038                panic!("{vol:?} failed to subdivide");
1039            };
1040            assert_ne!(a.volume(), 0);
1041            assert_ne!(b.volume(), 0);
1042
1043            // Compare immutable slice subdivide
1044            let [aref, bref] = vol.as_ref().subdivide().unwrap();
1045            assert_eq!((a, b), (aref.without_elements(), bref.without_elements()));
1046
1047            // Compare mutable slice subdivide
1048            let [amut, bmut] = vol.subdivide(filter).unwrap();
1049            assert_eq!((a, b), (amut.without_elements(), bmut.without_elements()));
1050
1051            // Recurse
1052            check_subdivide_case(amut);
1053            check_subdivide_case(bmut);
1054        }
1055    }
1056    fn check_subdivide(bounds: GridAab) {
1057        check_subdivide_case(Vol::<Box<[Cube]>>::from_fn(bounds, std::convert::identity).as_mut());
1058    }
1059
1060    #[test]
1061    fn subdivide_test() {
1062        check_subdivide(GridAab::ORIGIN_CUBE);
1063        check_subdivide(GridAab::ORIGIN_EMPTY);
1064        check_subdivide(GridAab::from_lower_upper([0, 0, 0], [2, 4, 5]));
1065    }
1066
1067    #[cfg(feature = "arbitrary")]
1068    #[test]
1069    fn arbitrary_bounds_size_hint() {
1070        use arbitrary::{Arbitrary, Unstructured};
1071        let hint = vol_arb::ARBITRARY_BOUNDS_SIZE_HINT;
1072        let most_bytes_used = (0..=255)
1073            .map(|byte| {
1074                // TODO: sketchy coverage; would be better to generate some random/hashed data
1075                let data = [byte; 1000];
1076                let mut u = Unstructured::new(&data);
1077                GridAab::arbitrary(&mut u).unwrap();
1078                let bytes_used = 1000 - u.len();
1079                assert!(
1080                    bytes_used >= hint.0,
1081                    "used {}, less than {}",
1082                    bytes_used,
1083                    hint.0
1084                );
1085                bytes_used
1086            })
1087            .max();
1088        assert_eq!(most_bytes_used, hint.1);
1089
1090        // TODO: Also look at the resulting Grids and see if they're good coverage.
1091    }
1092
1093    #[cfg(feature = "arbitrary")]
1094    #[test]
1095    fn arbitrary_bounds_volume() {
1096        use arbitrary::Unstructured;
1097        use itertools::Itertools as _;
1098        let max_volume = 100;
1099        let minmax = (0..=255)
1100            .map(|byte| {
1101                // TODO: sketchy coverage; would be better to generate some random/hashed data
1102                let data = [byte; 25];
1103                let mut u = Unstructured::new(&data);
1104                Vol::<()>::arbitrary_with_max_volume(&mut u, max_volume)
1105                    .unwrap()
1106                    .volume()
1107            })
1108            .minmax()
1109            .into_option();
1110        assert_eq!(minmax, Some((0, max_volume)));
1111    }
1112}