Repository: ManevilleF/hexx
Pull request #68 (2023-04-02 09:42:06)

Rings return an iterator
# Work done

* `Hex::ring` now returns a `ExactSizeIterator` instead of a `Vec`
* `Hex::custom_ring` now returns a `ExactSizeIterator` instead of a `Vec`
* Fixed `Hex::ring_count` returning 0 on radius == 0 



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fefcc2..3754eb9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,7 @@
 
 * Changed logo (#63)
 * Drop `itertools` dependency (#58)
-* Removed deprecated `Hex::directions_to` method (#58)
+* (**BREAKING**) Removed deprecated `Hex::directions_to` method (#58)
 * Added `MeshInfo::facing` utils method to rotate hex meshes (#65)
 * Added `hex` utils function to create an `Hex`, making it less boilerplate (#66):
   - Before: `Hex::new()`
@@ -12,6 +12,11 @@
 * `Hex` won't derive `Debug` or `Hash` on spirv archs (#66)
 * Added `packed` feature to make `Hex` `repr(C)` (#67)
 
+### ExactSizeIterator
+
+* (**BREAKING**) `Hex::ring` now returns a `ExactSizeIterator` instead of a `Vec` (#68)
+* (**BREAKING**) `Hex::custom_ring` now returns a `ExactSizeIterator` instead of a `Vec` (#68)
+
 ### Examples
 
 * `hex_grid` example now uses a 2d mesh and camera (#65)
diff --git a/README.md b/README.md
index c6ca9b7..ac6a53d 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,7 @@
  // Compute a line between points
  let line: Vec<Hex> = point_a.line_to(point_b).collect();
  // Compute a ring from `point_a` containing `point_b`
- let ring: Vec<Hex> = point_a.ring(dist);
+ let ring: Vec<Hex> = point_a.ring(dist).collect();
  // Rotate `point_b` around `point_a` by 2 times 60 degrees clockwise
  let rotated = point_b.rotate_right_around(point_a, 2);
  // Find the direction between the two points
diff --git a/examples/3d_columns.rs b/examples/3d_columns.rs
index 8c62b29..b89abc0 100644
--- a/examples/3d_columns.rs
+++ b/examples/3d_columns.rs
@@ -113,7 +113,7 @@ fn animate_rings(
     if highlighted_hexes.ring > MAP_RADIUS {
         highlighted_hexes.ring = 0;
     }
-    highlighted_hexes.hexes = Hex::ZERO.ring(highlighted_hexes.ring);
+    highlighted_hexes.hexes = Hex::ZERO.ring(highlighted_hexes.ring).collect();
     // Draw a ring
     for h in &highlighted_hexes.hexes {
         if let Some(e) = map.entities.get(h) {
diff --git a/examples/hex_grid.rs b/examples/hex_grid.rs
index a71aad8..18ff70f 100644
--- a/examples/hex_grid.rs
+++ b/examples/hex_grid.rs
@@ -143,19 +143,19 @@ fn handle_input(
             // Draw a  line
             highlighted_hexes.line = Hex::ZERO.line_to(coord).collect();
             // Draw a ring
-            highlighted_hexes.ring = Hex::ZERO.ring(coord.ulength());
+            highlighted_hexes.ring = Hex::ZERO.ring(coord.ulength()).collect();
             // Draw an wedge
             highlighted_hexes.wedge = Hex::ZERO.wedge_to(coord).collect();
             // Draw a half ring
-            highlighted_hexes.half_ring = Hex::ZERO.ring(coord.ulength() / 2);
+            highlighted_hexes.half_ring = Hex::ZERO.ring(coord.ulength() / 2).collect();
             // Draw rotations
             highlighted_hexes.rotated = (1..6).map(|i| coord.rotate_right(i)).collect();
             // Draw an dual wedge
             highlighted_hexes.dir_wedge = Hex::ZERO.corner_wedge_to(coord / 2).collect();
             for (vec, mat) in [
-                (&highlighted_hexes.ring, &map.ring_material),
                 (&highlighted_hexes.wedge, &map.wedge_material),
                 (&highlighted_hexes.dir_wedge, &map.dir_wedge_material),
+                (&highlighted_hexes.ring, &map.ring_material),
                 (&highlighted_hexes.line, &map.line_material),
                 (&highlighted_hexes.half_ring, &map.half_ring_material),
                 (&highlighted_hexes.rotated, &map.selected_material),
diff --git a/src/hex/mod.rs b/src/hex/mod.rs
index 5ce792d..26c3152 100644
--- a/src/hex/mod.rs
+++ b/src/hex/mod.rs
@@ -834,7 +834,7 @@ impl Hex {
         ExactSizeHexIterator {
             iter: (-radius..=radius).flat_map(move |x| {
                 (max(-radius, -x - radius)..=min(radius, radius - x))
-                    .map(move |y| self + Self::new(x, y))
+                    .map(move |y| self.const_add(Self::new(x, y)))
             }),
             count: Self::range_count(range),
         }
diff --git a/src/hex/rings.rs b/src/hex/rings.rs
index b0c4a33..73d0e1b 100644
--- a/src/hex/rings.rs
+++ b/src/hex/rings.rs
@@ -8,14 +8,17 @@ impl Hex {
     /// The returned coordinates start from `start_dir` and loop counter clockwise around `self`
     /// unless `clockwise` is set to `true`.
     ///
-    /// If you only need the coordinates see [`Self::ring`].
+    /// > If you only need the coordinates see [`Self::ring`]
     ///
     /// # Note
-    /// The returned vector will be of `6 * radius` ([`Self::ring_count`]) length
-    pub fn custom_ring(self, range: u32, start_dir: Direction, clockwise: bool) -> Vec<Self> {
-        if range == 0 {
-            return vec![self];
-        }
+    /// The returned iterator will have `6 * range` ([`Self::ring_count`]) items, unless `range` is
+    /// 0 which will return `self`
+    pub fn custom_ring(
+        self,
+        range: u32,
+        start_dir: Direction,
+        clockwise: bool,
+    ) -> impl ExactSizeIterator<Item = Self> {
         let mut directions = Self::NEIGHBORS_COORDS;
         // TODO: improve code clarity
         directions.rotate_left(start_dir as usize);
@@ -26,26 +29,32 @@ impl Hex {
             directions.rotate_left(2);
         }
 
-        let mut point = self + start_dir * range as i32;
-        let mut res = Vec::with_capacity(Self::ring_count(range));
-        for dir in directions {
-            (0..range).for_each(|_| {
-                res.push(point);
-                point += dir;
-            });
+        let point = self + start_dir * range as i32;
+        let iter = directions
+            .into_iter()
+            .flat_map(move |dir| std::iter::repeat(dir).take(range as usize))
+            .scan(point, move |pos, dir| {
+                let next = *pos + dir;
+                *pos = next;
+                Some(next)
+            })
+            .take((range as usize * 6).saturating_sub(1));
+        ExactSizeHexIterator {
+            iter: std::iter::once(point).chain(iter),
+            count: Self::ring_count(range),
         }
-        res
     }
 
     #[must_use]
     /// Retrieves one [`Hex`] ring around `self` in a given `range`.
     /// The returned coordinates start from [`Direction::TopRight`] and loop around `self` counter clockwise.
     ///
-    /// See [`Self::custom_ring`] for more options.
+    /// > See [`Self::custom_ring`] for more options.
     ///
     /// # Note
-    /// The returned vector will be of `6 * radius` ([`Self::ring_count`]) length
-    pub fn ring(self, range: u32) -> Vec<Self> {
+    /// The returned iterator will have `6 * range` ([`Self::ring_count`]) items, unless `range` is
+    /// 0 which will return `self`
+    pub fn ring(self, range: u32) -> impl ExactSizeIterator<Item = Self> {
         self.custom_ring(range, Direction::TopRight, false)
     }
 
@@ -59,14 +68,11 @@ impl Hex {
     ///
     /// ```rust
     /// # use hexx::*;
-    /// let rings: Vec<_> = Hex::ZERO.rings(3..10).collect();
+    /// let rings: Vec<Vec<Hex>> = Hex::ZERO.rings(3..10).collect();
     /// assert_eq!(rings.len(), 7);
     /// ```
-    ///
-    /// # Note
-    /// The returned iterator will be of `radius + 1` length
     pub fn rings(self, range: impl Iterator<Item = u32>) -> impl Iterator<Item = Vec<Self>> {
-        range.map(move |r| self.ring(r))
+        range.map(move |r| self.ring(r).collect())
     }
 
     /// Retrieves `range` [`Hex`] rings around `self` in a given `range`.
@@ -79,19 +85,16 @@ impl Hex {
     ///
     /// ```rust
     /// # use hexx::*;
-    /// let rings: Vec<_> = Hex::ZERO.custom_rings(3..10, Direction::Top, true).collect();
+    /// let rings: Vec<Vec<Hex>> = Hex::ZERO.custom_rings(3..10, Direction::Top, true).collect();
     /// assert_eq!(rings.len(), 7);
     /// ```
-    ///
-    /// # Note
-    /// The returned iterator will be of `radius + 1` length
     pub fn custom_rings(
         self,
         range: impl Iterator<Item = u32>,
         start_dir: Direction,
         clockwise: bool,
     ) -> impl Iterator<Item = Vec<Self>> {
-        range.map(move |r| self.custom_ring(r, start_dir, clockwise))
+        range.map(move |r| self.custom_ring(r, start_dir, clockwise).collect())
     }
 
     #[must_use]
@@ -418,7 +421,7 @@ impl Hex {
     /// See this [article](https://www.redblobgames.com/grids/hexagons/#rings-spiral) for more
     /// information
     pub fn cached_rings<const RANGE: usize>(self) -> [Vec<Self>; RANGE] {
-        std::array::from_fn(|r| self.ring(r as u32))
+        std::array::from_fn(|r| self.ring(r as u32).collect())
     }
 
     #[allow(clippy::cast_possible_truncation)]
@@ -456,7 +459,7 @@ impl Hex {
         start_dir: Direction,
         clockwise: bool,
     ) -> [Vec<Self>; RANGE] {
-        std::array::from_fn(|r| self.custom_ring(r as u32, start_dir, clockwise))
+        std::array::from_fn(|r| self.custom_ring(r as u32, start_dir, clockwise).collect())
     }
 
     /// Retrieves all [`Hex`] around `self` in a given `range` but ordered as successive rings,
@@ -490,6 +493,10 @@ impl Hex {
     #[must_use]
     /// Counts how many coordinates there are in a ring at the given `range`
     pub const fn ring_count(range: u32) -> usize {
-        6 * range as usize
+        if range == 0 {
+            1
+        } else {
+            6 * range as usize
+        }
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 9078d1a..5a39d55 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -62,7 +62,7 @@
 //! // Compute a line between points
 //! let line: Vec<Hex> = point_a.line_to(point_b).collect();
 //! // Compute a ring from `point_a` containing `point_b`
-//! let ring: Vec<Hex> = point_a.ring(dist);
+//! let ring: Vec<Hex> = point_a.ring(dist).collect();
 //! // Rotate `point_b` around `point_a` by 2 times 60 degrees clockwise
 //! let rotated = point_b.rotate_right_around(point_a, 2);
 //! // Find the direction between the two points


Test patch:
diff --git a/src/hex/tests.rs b/src/hex/tests.rs
index d51c061..83ee9de 100644
--- a/src/hex/tests.rs
+++ b/src/hex/tests.rs
@@ -394,9 +394,9 @@ fn range() {
 #[test]
 fn ring() {
     let point = Hex::ZERO;
-    assert_eq!(point.ring(0), vec![point]);
+    assert_eq!(point.ring(0).collect::<Vec<_>>(), vec![point]);
     let expected = point.all_neighbors().to_vec();
-    assert_eq!(point.ring(1), expected);
+    assert_eq!(point.ring(1).collect::<Vec<_>>(), expected);
 
     let radius = 5;
     let mut range: Vec<_> = point.range(radius).collect();
@@ -404,8 +404,8 @@ fn ring() {
     range.retain(|h| !removed.contains(h));
     let ring = point.ring(5);
     assert_eq!(ring.len(), range.len());
-    for h in &ring {
-        assert!(range.contains(h));
+    for h in ring {
+        assert!(range.contains(&h));
     }
 }
 
@@ -415,7 +415,8 @@ fn cached_rings() {
     let point = Hex::ZERO;
     let cache = point.cached_rings::<10>();
     for (r, ring) in cache.into_iter().enumerate() {
-        assert_eq!(ring, point.ring(r as u32));
+        let expected: Vec<_> = point.ring(r as u32).collect();
+        assert_eq!(ring, expected);
     }
 }
 
@@ -424,26 +425,36 @@ fn ring_offset() {
     let zero = Hex::ZERO;
     let target = Hex::new(14, 7);
 
-    let expected: Vec<_> = zero.ring(10).into_iter().map(|h| h + target).collect();
-    assert_eq!(target.ring(10), expected);
+    let expected: Vec<_> = zero.ring(10).map(|h| h + target).collect();
+    assert_eq!(target.ring(10).collect::<Vec<_>>(), expected);
 }
 
 #[test]
 fn custom_ring() {
     let point = Hex::ZERO;
-    assert_eq!(point.custom_ring(0, Direction::TopLeft, true), vec![point]);
+    assert_eq!(
+        point
+            .custom_ring(0, Direction::TopLeft, true)
+            .collect::<Vec<_>>(),
+        vec![point]
+    );
 
     // clockwise
-    let mut expected = point.ring(5);
+    let mut expected: Vec<_> = point.ring(5).collect();
     expected.reverse();
     expected.rotate_right(1);
-    assert_eq!(point.custom_ring(5, Direction::TopRight, true), expected);
+    assert_eq!(
+        point
+            .custom_ring(5, Direction::TopRight, true)
+            .collect::<Vec<_>>(),
+        expected
+    );
     // offsetted
-    let expected = point.ring(5);
+    let expected: Vec<_> = point.ring(5).collect();
     let ring = point.custom_ring(5, Direction::BottomLeft, false);
     assert_eq!(expected.len(), ring.len());
-    for h in &ring {
-        assert!(expected.contains(h));
+    for h in ring {
+        assert!(expected.contains(&h));
     }
 }