Skip to content

Commit

Permalink
Add iterators over various RoomXY and RoomCoordinate ranges (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
khoover authored Sep 9, 2024
1 parent b0d55eb commit 6c82dc2
Show file tree
Hide file tree
Showing 6 changed files with 959 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Unreleased
==========

- Add iterators over various `RoomXY` and `RoomCoordinate` ranges

0.22.0 (2024-08-27)
===================

Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ assert_approx_eq = "1.1"

[features]
default = []

[profile.release]
lto = true
143 changes: 143 additions & 0 deletions benches/room_xy_iters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#![feature(test)]
extern crate test;

#[cfg(test)]
mod benches {
use screeps::local::{LocalCostMatrix, RoomCoordinate, RoomXY};
use screeps_utils::{room_coordinate::*, room_xy::*};
use test::{black_box, Bencher};

fn make_xy(x: u8, y: u8) -> RoomXY {
RoomXY {
x: RoomCoordinate::new(x).unwrap(),
y: RoomCoordinate::new(y).unwrap(),
}
}

#[bench]
fn bench_full_grid_iter_cm_set(b: &mut Bencher) {
let mut cm = LocalCostMatrix::new();
b.iter(|| {
GridIter::new(
black_box(make_xy(0, 0)),
black_box(make_xy(49, 49)),
Order::XMajor,
)
.for_each(|xy| black_box(&mut cm).set(xy, 255));
});
}

#[bench]
fn bench_full_for_loop_cm_set(b: &mut Bencher) {
let mut cm = LocalCostMatrix::new();
b.iter(|| {
for x in black_box(0)..black_box(50) {
let x = RoomCoordinate::new(x).unwrap();
for y in black_box(0)..black_box(50) {
let y = RoomCoordinate::new(y).unwrap();
black_box(&mut cm).set(RoomXY { x, y }, 255);
}
}
});
}

#[bench]
fn bench_full_range_iter_cm_set(b: &mut Bencher) {
let mut cm = LocalCostMatrix::new();
let zero = RoomCoordinate::new(0).unwrap();
let max = RoomCoordinate::new(49).unwrap();
b.iter(|| {
for x in range_inclusive(black_box(zero), black_box(max)) {
for y in range_inclusive(black_box(zero), black_box(max)) {
black_box(&mut cm).set(RoomXY { x, y }, 255);
}
}
});
}

#[bench]
fn bench_chebyshev_iter_cm_set(b: &mut Bencher) {
let mut cm = LocalCostMatrix::new();
let centre = make_xy(10, 10);
b.iter(|| {
chebyshev_range_iter(black_box(centre), 3)
.for_each(|xy| black_box(&mut cm).set(xy, 255));
});
}

#[bench]
fn bench_chebyshev_saturating_cm_set(b: &mut Bencher) {
let mut cm = LocalCostMatrix::new();
let centre = make_xy(10, 10);
b.iter(|| {
let min_x = black_box(centre).x.saturating_add(-3);
let max_x = black_box(centre).x.saturating_add(3);
let min_y = black_box(centre).y.saturating_add(-3);
let max_y = black_box(centre).y.saturating_add(3);
for x in range_inclusive(min_x, max_x) {
for y in range_inclusive(min_y, max_y) {
black_box(&mut cm).set(RoomXY { x, y }, 255);
}
}
});
}

#[bench]
fn bench_chebyshev_checked_cm_set(b: &mut Bencher) {
let mut cm = LocalCostMatrix::new();
let centre = make_xy(10, 10);
b.iter(|| {
for x in (-3..=3).filter_map(|offset| black_box(centre).x.checked_add(offset)) {
for y in (-3..=3).filter_map(|offset| black_box(centre).y.checked_add(offset)) {
black_box(&mut cm).set(RoomXY { x, y }, 255);
}
}
});
}

#[bench]
fn bench_manhattan_iter_cm_set(b: &mut Bencher) {
let mut cm = LocalCostMatrix::new();
let centre = make_xy(10, 10);
b.iter(|| {
manhattan_range_iter(black_box(centre), 3)
.for_each(|xy| black_box(&mut cm).set(xy, 255));
});
}

#[bench]
fn bench_manhattan_saturating_cm_set(b: &mut Bencher) {
let mut cm = LocalCostMatrix::new();
let centre = make_xy(10, 10);
b.iter(|| {
let min_x = black_box(centre).x.saturating_add(-3);
let max_x = black_box(centre).x.saturating_add(3);
for (x, offset) in range_inclusive(min_x, max_x).zip(-3_i8..=3) {
let y_radius = 3 - offset.abs();
let min_y = black_box(centre).y.saturating_add(-y_radius);
let max_y = black_box(centre).y.saturating_add(y_radius);
for y in range_inclusive(min_y, max_y) {
black_box(&mut cm).set(RoomXY { x, y }, 255);
}
}
});
}

#[bench]
fn bench_manhattan_checked_cm_set(b: &mut Bencher) {
let mut cm = LocalCostMatrix::new();
let centre = make_xy(10, 10);
b.iter(|| {
for (x, offset) in (-3..=3)
.filter_map(|offset| black_box(centre).x.checked_add(offset).map(|x| (x, offset)))
{
let y_radius = 3 - offset.abs();
for y in (-y_radius..=y_radius)
.filter_map(|offset| black_box(centre).y.checked_add(offset))
{
black_box(&mut cm).set(RoomXY { x, y }, 255);
}
}
})
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ pub mod map;
pub mod math;
pub mod object;
pub mod offline_map;
pub mod room_coordinate;
pub mod room_xy;
pub mod sparse_cost_matrix;
127 changes: 127 additions & 0 deletions src/room_coordinate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use screeps::local::RoomCoordinate;

/// Iterate over the range of [`RoomCoordinate`]s from `a` to `b`, including
/// both endpoints.
///
/// Safe to call even when `a > b`, will just yield an empty range.
///
/// # Example
///
/// ```
/// use screeps::local::RoomCoordinate;
/// use screeps_utils::room_coordinate::range_inclusive;
///
/// let coords: Vec<u8> = range_inclusive(
/// RoomCoordinate::new(0).unwrap(),
/// RoomCoordinate::new(10).unwrap(),
/// )
/// .map(|coord| coord.u8())
/// .collect();
/// assert_eq!(coords, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10_u8]);
/// ```
pub fn range_inclusive(
a: RoomCoordinate,
b: RoomCoordinate,
) -> impl DoubleEndedIterator<Item = RoomCoordinate> {
// SAFETY: x \in [a.0, b.0], so it's in-bounds.
(a.u8()..=b.u8()).map(|x| unsafe { RoomCoordinate::unchecked_new(x) })
}

/// Iterate over the range of [`RoomCoordinate`]s from `a` to `b`, excluding
/// both endpoints.
///
/// Safe to call even when `a >= b`, will just yield an empty range.
///
/// # Example
///
/// ```
/// use screeps::local::RoomCoordinate;
/// use screeps_utils::room_coordinate::range_exclusive;
///
/// let coords: Vec<u8> = range_exclusive(
/// RoomCoordinate::new(0).unwrap(),
/// RoomCoordinate::new(10).unwrap(),
/// )
/// .map(|coord| coord.u8())
/// .collect();
/// assert_eq!(coords, [1, 2, 3, 4, 5, 6, 7, 8, 9_u8]);
///
/// // Works for empty ranges too.
/// let coords: Vec<u8> = range_exclusive(
/// RoomCoordinate::new(0).unwrap(),
/// RoomCoordinate::new(1).unwrap(),
/// )
/// .map(|coord| coord.u8())
/// .collect();
/// assert!(coords.is_empty());
/// ```
pub fn range_exclusive(
a: RoomCoordinate,
b: RoomCoordinate,
) -> impl DoubleEndedIterator<Item = RoomCoordinate> {
// SAFETY: x \in (a.0, b.0), so it's in-bounds.
(a.u8() + 1..b.u8()).map(|x| unsafe { RoomCoordinate::unchecked_new(x) })
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_inclusive_reverse() {
let coords: Vec<u8> = range_inclusive(
RoomCoordinate::new(10).unwrap(),
RoomCoordinate::new(20).unwrap(),
)
.map(|coord| coord.u8())
.rev()
.collect();
assert_eq!(coords, [20_u8, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10]);
}

#[test]
fn test_inclusive_a_geq_b() {
let coords: Vec<_> = range_inclusive(
RoomCoordinate::new(30).unwrap(),
RoomCoordinate::new(30).unwrap(),
)
.collect();
assert_eq!(coords, [RoomCoordinate::new(30).unwrap()]);

let coords: Vec<_> = range_inclusive(
RoomCoordinate::new(1).unwrap(),
RoomCoordinate::new(0).unwrap(),
)
.collect();
assert!(coords.is_empty());
}

#[test]
fn test_exclusive_reverse() {
let coords: Vec<u8> = range_exclusive(
RoomCoordinate::new(10).unwrap(),
RoomCoordinate::new(20).unwrap(),
)
.map(|coord| coord.u8())
.rev()
.collect();
assert_eq!(coords, [19_u8, 18, 17, 16, 15, 14, 13, 12, 11]);
}

#[test]
fn test_exclusive_a_geq_b() {
let coords: Vec<_> = range_exclusive(
RoomCoordinate::new(49).unwrap(),
RoomCoordinate::new(49).unwrap(),
)
.collect();
assert!(coords.is_empty());

let coords: Vec<_> = range_exclusive(
RoomCoordinate::new(49).unwrap(),
RoomCoordinate::new(48).unwrap(),
)
.collect();
assert!(coords.is_empty());
}
}
Loading

0 comments on commit 6c82dc2

Please sign in to comment.