From f0a10a773114b7c84115aa0923f7994ad382dcd7 Mon Sep 17 00:00:00 2001 From: Ken Hoover Date: Tue, 29 Oct 2024 15:22:55 -0700 Subject: [PATCH 1/9] feat: implement Step for RoomCoordinate --- src/lib.rs | 1 + src/local/room_coordinate.rs | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0b87246b..5056a83a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,7 @@ // to build locally with doc_cfg enabled, run: // `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(feature = "nightly", feature(step_trait))] // warn when functions can safely be given the const keyword, see // https://rust-lang.github.io/rust-clippy/master/index.html#/missing_const_for_fn // unfortunately this warns for bindgen-attached functions so we can't leave it diff --git a/src/local/room_coordinate.rs b/src/local/room_coordinate.rs index 51dc66b2..906c39d1 100644 --- a/src/local/room_coordinate.rs +++ b/src/local/room_coordinate.rs @@ -562,6 +562,53 @@ impl Neg for RoomOffset { } } +#[cfg(feature = "nightly")] +impl std::iter::Step for RoomCoordinate { + fn steps_between(start: &Self, end: &Self) -> Option { + end.0.checked_sub(start.0).map(|x| x as usize) + } + + fn forward_checked(start: Self, count: usize) -> Option { + if count < ROOM_USIZE { + start.checked_add(count as i8) + } else { + None + } + } + + fn backward_checked(start: Self, count: usize) -> Option { + if count < ROOM_USIZE { + start.checked_add(-(count as i8)) + } else { + None + } + } + + fn forward(start: Self, count: usize) -> Self { + if cfg!(debug_assertions) { + self.forward_checked(count).unwrap_throw() + } else { + self.saturating_add(count.min(ROOM_USIZE) as i8) + } + } + + fn backward(start: Self, count: usize) -> Self { + if cfg!(debug_assertions) { + self.backward_checked(count).unwrap_throw() + } else { + self.saturating_add(-(count.min(ROOM_USIZE) as i8)) + } + } + + unsafe fn forward_unchecked(start: Self, count: usize) -> Self { + start.unchecked_add(count as i8) + } + + unsafe fn backward_unchecked(start: Self, count: usize) -> Self { + start.unchecked_add(-(count as i8)) + } +} + #[cfg(test)] mod test { use super::*; From 9ac72158265d22a967d38922b6bf8e87ba0c0d23 Mon Sep 17 00:00:00 2001 From: Ken Hoover Date: Tue, 29 Oct 2024 15:29:33 -0700 Subject: [PATCH 2/9] feat: add a nightly feature --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 191fbaa7..200cdb87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,3 +66,5 @@ sim = [] # Enable unsafe conversions of return codes with undefined behavior when values # aren't in the expected range unsafe-return-conversion = [] + +nightly = [] \ No newline at end of file From 1ac9962737449a2b6a6024345b3ea39e52b542bf Mon Sep 17 00:00:00 2001 From: Ken Hoover Date: Tue, 29 Oct 2024 15:49:08 -0700 Subject: [PATCH 3/9] feat: add step impl for RoomOffset Also includes TrustedStep marker impls --- src/local/room_coordinate.rs | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/local/room_coordinate.rs b/src/local/room_coordinate.rs index 906c39d1..8bdc7118 100644 --- a/src/local/room_coordinate.rs +++ b/src/local/room_coordinate.rs @@ -370,6 +370,10 @@ impl RoomOffset { } } + fn saturating_new(offset: i8) -> Self { + Self(offset.clamp(Self::MIN.0, Self::MAX.0)) + } + /// Create a `RoomOffset` from an `i8`, without checking whether it's in the /// range of valid values. /// @@ -565,6 +569,8 @@ impl Neg for RoomOffset { #[cfg(feature = "nightly")] impl std::iter::Step for RoomCoordinate { fn steps_between(start: &Self, end: &Self) -> Option { + start.assume_bounds_constraint(); + end.assume_bounds_constraint(); end.0.checked_sub(start.0).map(|x| x as usize) } @@ -609,6 +615,66 @@ impl std::iter::Step for RoomCoordinate { } } +#[cfg(feature = "nightly")] +unsafe impl std::iter::TrustedStep for RoomCoordinate {} + +#[cfg(feature = "nightly")] +impl std::iter::Step for RoomOffset { + fn steps_between(start: &Self, end: &Self) -> Option { + start.assume_bounds_constraint(); + end.assume_bounds_constraint(); + let res = end.0 - start.0; + res.try_into().ok() + } + + fn forward_checked(start: Self, count: usize) -> Option { + start.assume_bounds_constraint(); + i8::try_from(count) + .ok() + .and_then(|count| self.0.checked_add(count)) + .and_then(Self::new) + } + + fn backward_checked(start: Self, count: usize) -> Option { + start.assume_bounds_constraint(); + i8::try_from(count) + .ok() + .and_then(|count| start.0.checked_sub(count)) + .and_then(Self::new) + } + + fn forward(start: Self, count: usize) -> Self { + if cfg!(debug_assertions) { + self.forward_checked(count).unwrap_throw() + } else { + start.assume_bounds_constraint(); + Self::saturating_new(start.0.saturating_add(count.min(2 * ROOM_USIZE) as i8)) + } + } + + fn backward(start: Self, count: usize) -> Self { + if cfg!(debug_assertions) { + self.backward_checked(count).unwrap_throw() + } else { + start.assume_bounds_constraint(); + Self::saturating_new(start.0.saturating_sub(count.min(2 * ROOM_USIZE) as i8)) + } + } + + unsafe fn forward_unchecked(start: Self, count: usize) -> Self { + start.assume_bounds_constraint(); + Self::unchecked_new(start.0.unchecked_add(count as i8)) + } + + unsafe fn backward_unchecked(start: Self, count: usize) -> Self { + start.assume_bounds_constraint(); + Self::unchecked_new(start.0.unchecked_sub(count as i8)) + } +} + +#[cfg(feature = "nightly")] +unsafe impl std::iter::TrustedStep for RoomOffset {} + #[cfg(test)] mod test { use super::*; From c9673228bec97adcffa54d92cfe2142a5569cccd Mon Sep 17 00:00:00 2001 From: Ken Hoover Date: Thu, 14 Nov 2024 23:04:06 -0800 Subject: [PATCH 4/9] Exclude nightly feature from stable pipeline --- .github/workflows/check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5c6c4157..ee1e5811 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -11,8 +11,8 @@ jobs: with: components: clippy, rustfmt - run: cargo fmt --all -- --check - - run: cargo clippy --all-features -- -D warnings - - run: cargo test --all-features + - run: cargo clippy --features mmo,seasonal-season-1,seasonal-season-2,seasonal-season-5,sim,unsafe-return-conversion -- -D warnings + - run: cargo test --features mmo,seasonal-season-1,seasonal-season-2,seasonal-season-5,sim,unsafe-return-conversion nightly: runs-on: ubuntu-latest steps: From 775d9a8929fc37ea4f74a1f23e18dbc4d7a7d658 Mon Sep 17 00:00:00 2001 From: Ken Hoover Date: Thu, 14 Nov 2024 23:04:23 -0800 Subject: [PATCH 5/9] Lots of breaking changes to RoomXY math. --- src/local/room_coordinate.rs | 32 ++- src/local/room_xy.rs | 313 +++++++++++++++++------ src/local/room_xy/approximate_offsets.rs | 15 +- src/local/room_xy/extra_math.rs | 33 +-- src/local/room_xy/game_math.rs | 8 +- 5 files changed, 281 insertions(+), 120 deletions(-) diff --git a/src/local/room_coordinate.rs b/src/local/room_coordinate.rs index 8bdc7118..f2c9cb0b 100644 --- a/src/local/room_coordinate.rs +++ b/src/local/room_coordinate.rs @@ -340,6 +340,18 @@ impl Sub for RoomCoordinate { } } +impl std::cmp::PartialOrd for RoomCoordinate { + fn partial_cmp(&self, other: &u8) -> Option { + Some(self.0.cmp(other)) + } +} + +impl std::cmp::PartialEq for RoomCoordinate { + fn eq(&self, other: &u8) -> bool { + self.0 == *other + } +} + const ROOM_SIZE_I8: i8 = { // If this fails, we need to rework the arithmetic code debug_assert!(2 * ROOM_SIZE <= i8::MAX as u8); @@ -439,7 +451,7 @@ impl RoomOffset { pub fn saturating_add(self, rhs: Self) -> Self { self.assume_bounds_constraint(); rhs.assume_bounds_constraint(); - Self::new((self.0 + rhs.0).clamp(-ROOM_SIZE_I8 + 1, ROOM_SIZE_I8 - 1)).unwrap_throw() + Self::saturating_new(self.0 + rhs.0) } /// Add two offsets together, wrapping around at the ends of the valid @@ -528,6 +540,12 @@ impl RoomOffset { } } +impl fmt::Display for RoomOffset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + impl From for i8 { fn from(offset: RoomOffset) -> i8 { offset.0 @@ -566,6 +584,18 @@ impl Neg for RoomOffset { } } +impl std::cmp::PartialOrd for RoomOffset { + fn partial_cmp(&self, other: &i8) -> Option { + Some(self.0.cmp(other)) + } +} + +impl std::cmp::PartialEq for RoomOffset { + fn eq(&self, other: &i8) -> bool { + self.0 == *other + } +} + #[cfg(feature = "nightly")] impl std::iter::Step for RoomCoordinate { fn steps_between(start: &Self, end: &Self) -> Option { diff --git a/src/local/room_xy.rs b/src/local/room_xy.rs index bf523d20..e34d72d4 100644 --- a/src/local/room_xy.rs +++ b/src/local/room_xy.rs @@ -1,12 +1,16 @@ use std::{ cmp::Ordering, fmt, - ops::{Index, IndexMut}, + ops::{Index, IndexMut, Sub}, }; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use wasm_bindgen::UnwrapThrowExt; -use super::room_coordinate::{OutOfBoundsError, RoomCoordinate}; +use super::{ + room_coordinate::{OutOfBoundsError, RoomCoordinate, RoomOffset}, + OffsetOutOfBoundsError, +}; use crate::constants::{Direction, ROOM_AREA, ROOM_USIZE}; mod approximate_offsets; @@ -65,27 +69,37 @@ pub fn terrain_index_to_xy(idx: usize) -> RoomXY { } } -/// An X/Y pair representing a given coordinate relative to any room. +/// A generic x-y pair of values. #[derive(Debug, Default, Hash, Clone, Copy, PartialEq, Eq)] -pub struct RoomXY { - pub x: RoomCoordinate, - pub y: RoomCoordinate, +pub struct XY { + pub x: T, + pub y: T, } -impl RoomXY { - /// Create a new `RoomXY` from a pair of `RoomCoordinate`. +/// An X/Y pair representing a given coordinate relative to any room. +pub type RoomXY = XY; + +/// An X/Y pair representing a given offset relative to any room. +pub type RoomOffsetXY = XY; + +impl XY { + /// Create a new `XY` from a pair of `T`s. #[inline] - pub fn new(x: RoomCoordinate, y: RoomCoordinate) -> Self { - RoomXY { x, y } + pub fn new(x: T, y: T) -> Self { + Self { x, y } } - /// Create a new `RoomXY` from a pair of `u8`, checking that they're in - /// the range of valid values. + /// Try to create a new `XY` from a pair of convertable values. #[inline] - pub fn checked_new(x: u8, y: u8) -> Result { - RoomXY::try_from((x, y)) + pub fn checked_new(x: U, y: U) -> Result>::Error> + where + Self: TryFrom<(U, U)>, + { + Self::try_from((x, y)) } +} +impl RoomXY { /// Create a `RoomXY` from a pair of `u8`, without checking whether it's in /// the range of valid values. /// @@ -129,6 +143,12 @@ impl RoomXY { Some(RoomXY { x, y }) } + pub fn checked_add_offset(self, rhs: RoomOffsetXY) -> Option { + let x = self.x.checked_add_offset(rhs.x)?; + let y = self.y.checked_add_offset(rhs.y)?; + Some(Self { x, y }) + } + /// Get the coordinate adjusted by a certain value, saturating at the edges /// of the room if the result would be outside the valid room area. /// @@ -154,48 +174,48 @@ impl RoomXY { RoomXY { x, y } } - /// Get the neighbor of a given `RoomXY` in the given direction, returning - /// `None` if the result is outside the valid room area. - /// - /// Example usage: - /// - /// ``` - /// use screeps::{constants::Direction::*, local::RoomXY}; - /// - /// let zero = unsafe { RoomXY::unchecked_new(0, 0) }; - /// let one = unsafe { RoomXY::unchecked_new(1, 1) }; - /// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) }; - /// - /// assert_eq!(zero.checked_add_direction(BottomRight), Some(one)); - /// assert_eq!(zero.checked_add_direction(TopLeft), None); - /// assert_eq!(one.checked_add_direction(TopLeft), Some(zero)); - /// assert_eq!(forty_nine.checked_add_direction(BottomRight), None); - /// ``` - pub fn checked_add_direction(self, rhs: Direction) -> Option { - let (dx, dy) = rhs.into(); - self.checked_add((dx as i8, dy as i8)) + pub fn saturating_add_offset(self, rhs: RoomOffsetXY) -> Self { + let x = self.x.saturating_add_offset(rhs.x); + let y = self.y.saturating_add_offset(rhs.y); + Self { x, y } } - /// Get the neighbor of a given `RoomXY` in the given direction, saturating - /// at the edges if the result is outside the valid room area. - /// - /// Example usage: - /// - /// ``` - /// use screeps::{constants::Direction::*, local::RoomXY}; - /// - /// let zero = unsafe { RoomXY::unchecked_new(0, 0) }; - /// let one = unsafe { RoomXY::unchecked_new(1, 1) }; - /// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) }; - /// - /// assert_eq!(zero.saturating_add_direction(BottomRight), one); - /// assert_eq!(zero.saturating_add_direction(TopLeft), zero); - /// assert_eq!(one.saturating_add_direction(TopLeft), zero); - /// assert_eq!(forty_nine.saturating_add_direction(BottomRight), forty_nine); - /// ``` - pub fn saturating_add_direction(self, rhs: Direction) -> RoomXY { - let (dx, dy) = rhs.into(); - self.saturating_add((dx as i8, dy as i8)) + pub fn overflowing_add(self, rhs: (i8, i8)) -> (Self, (bool, bool)) { + let (x, x_overflow) = self.x.overflowing_add(rhs.0); + let (y, y_overflow) = self.y.overflowing_add(rhs.1); + (Self { x, y }, (x_overflow, y_overflow)) + } + + pub fn overflowing_add_offset(self, rhs: RoomOffsetXY) -> (Self, XY) { + let (x, x_overflow) = self.x.overflowing_add_offset(rhs.x); + let (y, y_overflow) = self.y.overflowing_add_offset(rhs.y); + ( + Self { x, y }, + XY { + x: x_overflow, + y: y_overflow, + }, + ) + } + + pub fn wrapping_add(self, rhs: (i8, i8)) -> Self { + self.overflowing_add(rhs).0 + } + + pub fn wrapping_add_offset(self, rhs: RoomOffsetXY) -> Self { + self.overflowing_add_offset(rhs).0 + } + + pub unsafe fn unchecked_add(self, rhs: (i8, i8)) -> Self { + let x = self.x.unchecked_add(rhs.0); + let y = self.y.unchecked_add(rhs.1); + Self { x, y } + } + + pub unsafe fn unchecked_add_offset(self, rhs: RoomOffsetXY) -> Self { + let x = self.x.unchecked_add_offset(rhs.x); + let y = self.y.unchecked_add_offset(rhs.y); + Self { x, y } } /// Get all the valid neighbors of a given `RoomXY`. @@ -237,7 +257,7 @@ impl RoomXY { /// ``` pub fn neighbors(self) -> Vec { Direction::iter() - .filter_map(|dir| self.checked_add_direction(*dir)) + .filter_map(|&dir| self.checked_add_offset(dir.into())) .collect() } } @@ -261,50 +281,147 @@ impl fmt::Display for RoomXY { } } -impl From for (u8, u8) { - fn from(xy: RoomXY) -> (u8, u8) { - (xy.x.u8(), xy.y.u8()) +impl RoomOffsetXY { + #[inline] + pub unsafe fn unchecked_new(x: i8, y: i8) -> Self { + Self { + x: RoomOffset::unchecked_new(x), + y: RoomOffset::unchecked_new(y), + } + } + + pub fn manhattan_distance(self) -> u8 { + self.x.abs() + self.y.abs() + } + + pub fn chebyshev_distance(self) -> u8 { + self.x.abs().max(self.y.abs()) + } +} + +impl std::ops::Neg for RoomOffsetXY { + type Output = Self; + + fn neg(self) -> Self::Output { + Self { + x: -self.x, + y: -self.y, + } + } +} + +impl fmt::Display for RoomOffsetXY { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({}, {})", self.x, self.y) + } +} + +impl From> for (U, U) +where + U: From, +{ + fn from(XY { x, y }: XY) -> Self { + (x.into(), y.into()) + } +} + +impl From<(U, U)> for XY +where + T: From, +{ + fn from((x, y): (U, U)) -> Self { + Self { + x: x.into(), + y: y.into(), + } + } +} + +impl Sub> for XY +where + A: Sub, +{ + type Output = XY; + + /// Implements subtraction between [`XY`] values when the contained types can be subtracted from each other as scalars. + /// + /// # Example + /// + /// ``` + /// # use screeps::{RoomXY, XY, RoomOffsetXY}; + /// + /// assert_eq!(XY {x: 5, y: 4} - XY {x: 1, y: 1}, XY {x: 4, y: 3}); + /// let pos1 = RoomXY::checked_new(40, 40).unwrap(); + /// let pos2 = RoomXY::checked_new(0, 20).unwrap(); + /// assert_eq!(pos1 - pos2, RoomOffsetXY::checked_new(40, 20).unwrap()); + /// + /// let pos3 = RoomXY::checked_new(45, 45).unwrap(); + /// assert_eq!(pos1 - pos3, RoomOffsetXY::checked_new(-5, -5).unwrap()); + /// ``` + fn sub(self, rhs: XY) -> XY { + XY { + x: self.x - rhs.x, + y: self.y - rhs.y, + } } } impl TryFrom<(u8, u8)> for RoomXY { type Error = OutOfBoundsError; - fn try_from(xy: (u8, u8)) -> Result { - Ok(RoomXY { - x: RoomCoordinate::try_from(xy.0)?, - y: RoomCoordinate::try_from(xy.1)?, + fn try_from((x, y): (u8, u8)) -> Result { + Ok(Self { + x: x.try_into()?, + y: y.try_into()?, }) } } -impl From<(RoomCoordinate, RoomCoordinate)> for RoomXY { - fn from(xy: (RoomCoordinate, RoomCoordinate)) -> RoomXY { - RoomXY { x: xy.0, y: xy.1 } +impl TryFrom<(i8, i8)> for RoomOffsetXY { + type Error = OffsetOutOfBoundsError; + + fn try_from((x, y): (i8, i8)) -> Result { + Ok(Self { + x: x.try_into()?, + y: y.try_into()?, + }) } } -impl From for (RoomCoordinate, RoomCoordinate) { - fn from(xy: RoomXY) -> (RoomCoordinate, RoomCoordinate) { - (xy.x, xy.y) +impl From for RoomOffsetXY { + fn from(value: Direction) -> Self { + use Direction::*; + let y = match value { + Top | TopLeft | TopRight => RoomOffset::new(-1), + Right | Left => RoomOffset::new(0), + Bottom | BottomLeft | BottomRight => RoomOffset::new(1), + } + .unwrap_throw(); + let x = match value { + Left | TopLeft | BottomLeft => RoomOffset::new(-1), + Top | Bottom => RoomOffset::new(0), + Right | TopRight | BottomRight => RoomOffset::new(1), + } + .unwrap_throw(); + Self { x, y } } } #[derive(Serialize, Deserialize)] -struct ReadableXY { - x: RoomCoordinate, - y: RoomCoordinate, +struct ReadableXY { + x: T, + y: T, } -impl From for RoomXY { - fn from(ReadableXY { x, y }: ReadableXY) -> RoomXY { - RoomXY { x, y } +impl From> for XY { + fn from(ReadableXY { x, y }: ReadableXY) -> XY { + Self { x, y } } } -impl From for ReadableXY { - fn from(RoomXY { x, y }: RoomXY) -> ReadableXY { - ReadableXY { x, y } +impl From> for ReadableXY { + fn from(XY { x, y }: XY) -> Self { + Self { x, y } } } @@ -314,7 +431,7 @@ impl Serialize for RoomXY { S: Serializer, { if serializer.is_human_readable() { - ReadableXY::from(*self).serialize(serializer) + ReadableXY::::from(*self).serialize(serializer) } else { let xy: (u8, u8) = (*self).into(); let packed: u16 = ((xy.0 as u16) << 8) | (xy.1 as u16); @@ -329,7 +446,7 @@ impl<'de> Deserialize<'de> for RoomXY { D: Deserializer<'de>, { if deserializer.is_human_readable() { - ReadableXY::deserialize(deserializer).map(Into::into) + ReadableXY::::deserialize(deserializer).map(Into::into) } else { let packed = u16::deserialize(deserializer)?; let xy = (((packed >> 8) & 0xFF) as u8, (packed & 0xFF) as u8); @@ -343,6 +460,44 @@ impl<'de> Deserialize<'de> for RoomXY { } } +impl Serialize for RoomOffsetXY { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + ReadableXY::::from(*self).serialize(serializer) + } else { + let xy: (i8, i8) = (*self).into(); + let packed: u16 = ((xy.0 as u8 as u16) << 8) | (xy.1 as u8 as u16); + packed.serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for RoomOffsetXY { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + ReadableXY::::deserialize(deserializer).map(Into::into) + } else { + let packed = u16::deserialize(deserializer)?; + let xy = ( + ((packed >> 8) & 0xFF) as u8 as i8, + (packed & 0xFF) as u8 as i8, + ); + RoomOffsetXY::try_from(xy).map_err(|err| { + de::Error::invalid_value( + de::Unexpected::Signed(err.0 as i64), + &format!("an integer with absolute value less-than {ROOM_USIZE}").as_str(), + ) + }) + } + } +} + /// A wrapper struct indicating that the inner array should be indexed X major, /// i.e. ``` /// use screeps::{ diff --git a/src/local/room_xy/approximate_offsets.rs b/src/local/room_xy/approximate_offsets.rs index b141b6e6..9fb98b59 100644 --- a/src/local/room_xy/approximate_offsets.rs +++ b/src/local/room_xy/approximate_offsets.rs @@ -1,7 +1,7 @@ //! Methods related to approximating in-room positions //! between other in-room positions. -use super::RoomXY; +use super::{RoomOffsetXY, RoomXY}; impl RoomXY { /// Calculates an approximate midpoint between this point and the target. @@ -50,14 +50,17 @@ impl RoomXY { /// ); /// ``` pub fn towards(self, target: RoomXY, distance_towards_target: i8) -> RoomXY { - let (offset_x, offset_y) = target - self; - let total_distance = offset_x.abs().max(offset_y.abs()); + let RoomOffsetXY { + x: offset_x, + y: offset_y, + } = target - self; + let total_distance = offset_x.abs().max(offset_y.abs()) as i8; if distance_towards_target > total_distance { return target; } - let new_offset_x = (offset_x * distance_towards_target) / total_distance; - let new_offset_y = (offset_y * distance_towards_target) / total_distance; + let new_offset_x = (i8::from(offset_x) * distance_towards_target) / total_distance; + let new_offset_y = (i8::from(offset_y) * distance_towards_target) / total_distance; self + (new_offset_x, new_offset_y) } @@ -178,7 +181,7 @@ impl RoomXY { /// ); /// ``` pub fn midpoint_between(self, target: RoomXY) -> RoomXY { - let (offset_x, offset_y) = self - target; + let (offset_x, offset_y) = <(i8, i8)>::from(self - target); let new_offset_x = offset_x / 2; let new_offset_y = offset_y / 2; diff --git a/src/local/room_xy/extra_math.rs b/src/local/room_xy/extra_math.rs index d26c73a3..286dd4d6 100644 --- a/src/local/room_xy/extra_math.rs +++ b/src/local/room_xy/extra_math.rs @@ -3,7 +3,7 @@ use std::ops::{Add, Sub}; -use super::RoomXY; +use super::{RoomOffsetXY, RoomXY, XY}; use crate::constants::Direction; impl RoomXY { @@ -89,7 +89,7 @@ impl Add for RoomXY { #[inline] #[track_caller] fn add(self, direction: Direction) -> Self { - self.checked_add_direction(direction).unwrap() + self.checked_add_offset(direction.into()).unwrap() } } @@ -138,32 +138,7 @@ impl Sub for RoomXY { /// ``` #[inline] fn sub(self, direction: Direction) -> Self { - self.checked_add_direction(-direction).unwrap() - } -} - -impl Sub for RoomXY { - type Output = (i8, i8); - - /// Subtracts the other position from this one, extracting the - /// difference as the output. - /// - /// # Example - /// - /// ``` - /// # use screeps::RoomXY; - /// - /// let pos1 = RoomXY::checked_new(40, 40).unwrap(); - /// let pos2 = RoomXY::checked_new(0, 20).unwrap(); - /// assert_eq!(pos1 - pos2, (40, 20)); - /// - /// let pos3 = RoomXY::checked_new(45, 45).unwrap(); - /// assert_eq!(pos1 - pos3, (-5, -5)); - /// ``` - #[inline] - fn sub(self, other: RoomXY) -> (i8, i8) { - let dx = self.x.u8() as i8 - other.x.u8() as i8; - let dy = self.y.u8() as i8 - other.y.u8() as i8; - (dx, dy) + self.checked_add_offset(-RoomOffsetXY::from(direction)) + .unwrap() } } diff --git a/src/local/room_xy/game_math.rs b/src/local/room_xy/game_math.rs index df19d2a4..ff692961 100644 --- a/src/local/room_xy/game_math.rs +++ b/src/local/room_xy/game_math.rs @@ -1,9 +1,8 @@ //! Utilities for doing math on [`RoomXY`]s which are present in the //! JavaScript API. - use crate::constants::Direction; -use super::RoomXY; +use super::{RoomOffsetXY, RoomXY}; impl RoomXY { /// Gets linear direction to the specified position. @@ -14,7 +13,7 @@ impl RoomXY { /// if the target has a slightly different `x` coordinate. pub fn get_direction_to(self, target: RoomXY) -> Option { // Logic copied from https://github.com/screeps/engine/blob/020ba168a1fde9a8072f9f1c329d5c0be8b440d7/src/utils.js#L73-L107 - let (dx, dy) = target - self; + let RoomOffsetXY { x: dx, y: dy } = target - self; if dx.abs() > dy.abs() * 2 { if dx > 0 { Some(Direction::Right) @@ -59,8 +58,7 @@ impl RoomXY { #[doc(alias = "distance")] #[inline] pub fn get_range_to(self, target: RoomXY) -> u8 { - let (dx, dy) = self - target; - dx.unsigned_abs().max(dy.unsigned_abs()) + (self - target).chebyshev_distance() } /// Checks whether this position is in the given range of another position. From 386f0f37ce1f4d419804b8f40cf78481244f8e0e Mon Sep 17 00:00:00 2001 From: Ken Hoover Date: Mon, 18 Nov 2024 22:41:38 -0800 Subject: [PATCH 6/9] Add RoomOffset constants, RoomOffsetXY math Adds +-1 and 0 as constants to RoomOffset, and adds arithmetic methods to RoomOffsetXY. Also reworks a bit of the math code to take advantage. --- src/local/room_coordinate.rs | 62 +++++++++++++++++++------------- src/local/room_xy.rs | 64 ++++++++++++++++++++++++++------- src/local/room_xy/extra_math.rs | 4 +-- src/local/room_xy/game_math.rs | 3 +- 4 files changed, 91 insertions(+), 42 deletions(-) diff --git a/src/local/room_coordinate.rs b/src/local/room_coordinate.rs index f2c9cb0b..e02552a3 100644 --- a/src/local/room_coordinate.rs +++ b/src/local/room_coordinate.rs @@ -370,7 +370,10 @@ pub struct RoomOffset(i8); impl RoomOffset { pub const MAX: Self = Self(ROOM_SIZE_I8 - 1); - pub const MIN: Self = Self(1 - ROOM_SIZE_I8); + pub const MIN: Self = Self(-(ROOM_SIZE_I8 - 1)); + pub const PLUS_ONE: Self = Self(1); + pub const MINUS_ONE: Self = Self(-1); + pub const ZERO: Self = Self(0); /// Create a `RoomOffset` from an `i8`, returning an error if it's not /// within the valid range. @@ -420,12 +423,12 @@ impl RoomOffset { /// ``` /// use screeps::local::RoomOffset; /// - /// let zero = RoomOffset::new(0).unwrap(); - /// let one = RoomOffset::new(1).unwrap(); - /// - /// assert_eq!(RoomOffset::MIN.checked_add(RoomOffset::MAX), Some(zero)); - /// assert_eq!(RoomOffset::MAX.checked_add(one), None); - /// assert_eq!(RoomOffset::MIN.checked_add(-one), None); + /// assert_eq!( + /// RoomOffset::MIN.checked_add(RoomOffset::MAX), + /// Some(RoomOffset::ZERO) + /// ); + /// assert_eq!(RoomOffset::MAX.checked_add(RoomOffset::PLUS_ONE), None); + /// assert_eq!(RoomOffset::MIN.checked_add(RoomOffset::MINUS_ONE), None); /// ``` pub fn checked_add(self, rhs: Self) -> Option { self.assume_bounds_constraint(); @@ -441,12 +444,18 @@ impl RoomOffset { /// ``` /// use screeps::local::RoomOffset; /// - /// let zero = RoomOffset::new(0).unwrap(); - /// let one = RoomOffset::new(1).unwrap(); - /// - /// assert_eq!(RoomOffset::MIN.saturating_add(RoomOffset::MAX), zero); - /// assert_eq!(RoomOffset::MAX.saturating_add(one), RoomOffset::MAX); - /// assert_eq!(RoomOffset::MIN.saturating_add(-one), RoomOffset::MIN); + /// assert_eq!( + /// RoomOffset::MIN.saturating_add(RoomOffset::MAX), + /// RoomOffset::ZERO + /// ); + /// assert_eq!( + /// RoomOffset::MAX.saturating_add(RoomOffset::PLUS_ONE), + /// RoomOffset::MAX + /// ); + /// assert_eq!( + /// RoomOffset::MIN.saturating_add(RoomOffset::MINUS_ONE), + /// RoomOffset::MIN + /// ); /// ``` pub fn saturating_add(self, rhs: Self) -> Self { self.assume_bounds_constraint(); @@ -462,20 +471,17 @@ impl RoomOffset { /// ``` /// use screeps::local::RoomOffset; /// - /// let zero = RoomOffset::new(0).unwrap(); - /// let one = RoomOffset::new(1).unwrap(); - /// /// assert_eq!( - /// RoomOffset::MAX.overflowing_add(one), + /// RoomOffset::MAX.overflowing_add(RoomOffset::PLUS_ONE), /// (RoomOffset::MIN, true) /// ); /// assert_eq!( - /// RoomOffset::MIN.overflowing_add(-one), + /// RoomOffset::MIN.overflowing_add(RoomOffset::MINUS_ONE), /// (RoomOffset::MAX, true) /// ); /// assert_eq!( /// RoomOffset::MIN.overflowing_add(RoomOffset::MAX), - /// (zero, false) + /// (RoomOffset::ZERO, false) /// ); /// ``` pub fn overflowing_add(self, rhs: Self) -> (Self, bool) { @@ -500,12 +506,18 @@ impl RoomOffset { /// ``` /// use screeps::local::RoomOffset; /// - /// let zero = RoomOffset::new(0).unwrap(); - /// let one = RoomOffset::new(1).unwrap(); - /// - /// assert_eq!(RoomOffset::MAX.wrapping_add(one), RoomOffset::MIN); - /// assert_eq!(RoomOffset::MIN.wrapping_add(-one), RoomOffset::MAX); - /// assert_eq!(RoomOffset::MIN.wrapping_add(RoomOffset::MAX), zero); + /// assert_eq!( + /// RoomOffset::MAX.wrapping_add(RoomOffset::PLUS_ONE), + /// RoomOffset::MIN + /// ); + /// assert_eq!( + /// RoomOffset::MIN.wrapping_add(RoomOffset::MINUS_ONE), + /// RoomOffset::MAX + /// ); + /// assert_eq!( + /// RoomOffset::MIN.wrapping_add(RoomOffset::MAX), + /// RoomOffset::ZERO + /// ); /// ``` pub fn wrapping_add(self, rhs: Self) -> Self { self.overflowing_add(rhs).0 diff --git a/src/local/room_xy.rs b/src/local/room_xy.rs index e34d72d4..4956a2c9 100644 --- a/src/local/room_xy.rs +++ b/src/local/room_xy.rs @@ -5,7 +5,6 @@ use std::{ }; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; -use wasm_bindgen::UnwrapThrowExt; use super::{ room_coordinate::{OutOfBoundsError, RoomCoordinate, RoomOffset}, @@ -297,6 +296,46 @@ impl RoomOffsetXY { pub fn chebyshev_distance(self) -> u8 { self.x.abs().max(self.y.abs()) } + + pub fn checked_add(self, rhs: Self) -> Option { + Some(Self { + x: self.x.checked_add(rhs.x)?, + y: self.y.checked_add(rhs.y)?, + }) + } + + pub fn saturating_add(self, rhs: Self) -> Self { + Self { + x: self.x.saturating_add(rhs.x), + y: self.y.saturating_add(rhs.y), + } + } + + pub fn overflowing_add(self, rhs: Self) -> (Self, XY) { + let (x, x_overflow) = self.x.overflowing_add(rhs.x); + let (y, y_overflow) = self.y.overflowing_add(rhs.y); + ( + Self { x, y }, + XY { + x: x_overflow, + y: y_overflow, + }, + ) + } + + pub fn wrapping_add(self, rhs: Self) -> Self { + Self { + x: self.x.wrapping_add(rhs.x), + y: self.y.wrapping_add(rhs.y), + } + } + + pub unsafe fn unchecked_add(self, rhs: Self) -> Self { + Self { + x: self.x.unchecked_add(rhs.x), + y: self.y.unchecked_add(rhs.y), + } + } } impl std::ops::Neg for RoomOffsetXY { @@ -343,14 +382,15 @@ where { type Output = XY; - /// Implements subtraction between [`XY`] values when the contained types can be subtracted from each other as scalars. + /// Implements subtraction between [`XY`] values when the contained types + /// can be subtracted from each other as scalars. /// /// # Example /// /// ``` /// # use screeps::{RoomXY, XY, RoomOffsetXY}; /// - /// assert_eq!(XY {x: 5, y: 4} - XY {x: 1, y: 1}, XY {x: 4, y: 3}); + /// assert_eq!(XY { x: 5, y: 4 } - XY { x: 1, y: 1 }, XY { x: 4, y: 3 }); /// let pos1 = RoomXY::checked_new(40, 40).unwrap(); /// let pos2 = RoomXY::checked_new(0, 20).unwrap(); /// assert_eq!(pos1 - pos2, RoomOffsetXY::checked_new(40, 20).unwrap()); @@ -392,17 +432,15 @@ impl From for RoomOffsetXY { fn from(value: Direction) -> Self { use Direction::*; let y = match value { - Top | TopLeft | TopRight => RoomOffset::new(-1), - Right | Left => RoomOffset::new(0), - Bottom | BottomLeft | BottomRight => RoomOffset::new(1), - } - .unwrap_throw(); + Top | TopLeft | TopRight => RoomOffset::MINUS_ONE, + Right | Left => RoomOffset::ZERO, + Bottom | BottomLeft | BottomRight => RoomOffset::PLUS_ONE, + }; let x = match value { - Left | TopLeft | BottomLeft => RoomOffset::new(-1), - Top | Bottom => RoomOffset::new(0), - Right | TopRight | BottomRight => RoomOffset::new(1), - } - .unwrap_throw(); + Left | TopLeft | BottomLeft => RoomOffset::MINUS_ONE, + Top | Bottom => RoomOffset::ZERO, + Right | TopRight | BottomRight => RoomOffset::PLUS_ONE, + }; Self { x, y } } } diff --git a/src/local/room_xy/extra_math.rs b/src/local/room_xy/extra_math.rs index 286dd4d6..d7141066 100644 --- a/src/local/room_xy/extra_math.rs +++ b/src/local/room_xy/extra_math.rs @@ -3,7 +3,7 @@ use std::ops::{Add, Sub}; -use super::{RoomOffsetXY, RoomXY, XY}; +use super::{RoomOffsetXY, RoomXY}; use crate::constants::Direction; impl RoomXY { @@ -39,7 +39,7 @@ impl RoomXY { #[inline] #[track_caller] pub fn offset(&mut self, x: i8, y: i8) { - *self = *self + (x, y); + *self = self.checked_add((x, y)).unwrap(); } } diff --git a/src/local/room_xy/game_math.rs b/src/local/room_xy/game_math.rs index ff692961..3258d375 100644 --- a/src/local/room_xy/game_math.rs +++ b/src/local/room_xy/game_math.rs @@ -131,8 +131,7 @@ impl RoomXY { /// ``` #[inline] pub fn is_near_to(self, target: RoomXY) -> bool { - (u8::from(self.x) as i32 - u8::from(target.x) as i32).abs() <= 1 - && (u8::from(self.y) as i32 - u8::from(target.y) as i32).abs() <= 1 + self.in_range_to(target, 1) } } From 34cc3c4c41c50d4afdbd216b894ce6f18c1f8d67 Mon Sep 17 00:00:00 2001 From: Ken Hoover Date: Mon, 18 Nov 2024 22:51:04 -0800 Subject: [PATCH 7/9] Fix the step impls --- src/local/room_coordinate.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/local/room_coordinate.rs b/src/local/room_coordinate.rs index e02552a3..b32308fa 100644 --- a/src/local/room_coordinate.rs +++ b/src/local/room_coordinate.rs @@ -634,17 +634,17 @@ impl std::iter::Step for RoomCoordinate { fn forward(start: Self, count: usize) -> Self { if cfg!(debug_assertions) { - self.forward_checked(count).unwrap_throw() + start.forward_checked(count).unwrap_throw() } else { - self.saturating_add(count.min(ROOM_USIZE) as i8) + start.saturating_add(count.min(ROOM_USIZE) as i8) } } fn backward(start: Self, count: usize) -> Self { if cfg!(debug_assertions) { - self.backward_checked(count).unwrap_throw() + start.backward_checked(count).unwrap_throw() } else { - self.saturating_add(-(count.min(ROOM_USIZE) as i8)) + start.saturating_add(-(count.min(ROOM_USIZE) as i8)) } } @@ -673,21 +673,21 @@ impl std::iter::Step for RoomOffset { start.assume_bounds_constraint(); i8::try_from(count) .ok() - .and_then(|count| self.0.checked_add(count)) - .and_then(Self::new) + .and_then(|count| Self::new(count).ok()) + .and_then(|offset| start.checked_add(offset)) } fn backward_checked(start: Self, count: usize) -> Option { start.assume_bounds_constraint(); i8::try_from(count) .ok() - .and_then(|count| start.0.checked_sub(count)) - .and_then(Self::new) + .and_then(|count| Self::new(count).ok()) + .and_then(|offset| start.checked_add(-offset)) } fn forward(start: Self, count: usize) -> Self { if cfg!(debug_assertions) { - self.forward_checked(count).unwrap_throw() + start.forward_checked(count).unwrap_throw() } else { start.assume_bounds_constraint(); Self::saturating_new(start.0.saturating_add(count.min(2 * ROOM_USIZE) as i8)) @@ -696,7 +696,7 @@ impl std::iter::Step for RoomOffset { fn backward(start: Self, count: usize) -> Self { if cfg!(debug_assertions) { - self.backward_checked(count).unwrap_throw() + start.backward_checked(count).unwrap_throw() } else { start.assume_bounds_constraint(); Self::saturating_new(start.0.saturating_sub(count.min(2 * ROOM_USIZE) as i8)) From e14d125d847e097974d7789f2c378f4bbdb6e35b Mon Sep 17 00:00:00 2001 From: Ken Hoover Date: Mon, 18 Nov 2024 22:51:53 -0800 Subject: [PATCH 8/9] Add needed features for TrustedStep --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5056a83a..0e653f4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,7 +87,10 @@ // to build locally with doc_cfg enabled, run: // `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![cfg_attr(feature = "nightly", feature(step_trait))] +#![cfg_attr( + feature = "nightly", + feature(step_trait, trusted_step, min_specialization) +)] // warn when functions can safely be given the const keyword, see // https://rust-lang.github.io/rust-clippy/master/index.html#/missing_const_for_fn // unfortunately this warns for bindgen-attached functions so we can't leave it From 766eff919ceb0cd31260139b452463e1176b0387 Mon Sep 17 00:00:00 2001 From: Ken Hoover Date: Mon, 18 Nov 2024 22:55:36 -0800 Subject: [PATCH 9/9] Last fixes to Step --- src/local/room_coordinate.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/local/room_coordinate.rs b/src/local/room_coordinate.rs index b32308fa..1da7e4d8 100644 --- a/src/local/room_coordinate.rs +++ b/src/local/room_coordinate.rs @@ -634,7 +634,7 @@ impl std::iter::Step for RoomCoordinate { fn forward(start: Self, count: usize) -> Self { if cfg!(debug_assertions) { - start.forward_checked(count).unwrap_throw() + RoomCoordinate::forward_checked(start, count).unwrap_throw() } else { start.saturating_add(count.min(ROOM_USIZE) as i8) } @@ -642,7 +642,7 @@ impl std::iter::Step for RoomCoordinate { fn backward(start: Self, count: usize) -> Self { if cfg!(debug_assertions) { - start.backward_checked(count).unwrap_throw() + RoomCoordinate::backward_checked(start, count).unwrap_throw() } else { start.saturating_add(-(count.min(ROOM_USIZE) as i8)) } @@ -687,7 +687,7 @@ impl std::iter::Step for RoomOffset { fn forward(start: Self, count: usize) -> Self { if cfg!(debug_assertions) { - start.forward_checked(count).unwrap_throw() + RoomOffset::forward_checked(start, count).unwrap_throw() } else { start.assume_bounds_constraint(); Self::saturating_new(start.0.saturating_add(count.min(2 * ROOM_USIZE) as i8)) @@ -696,7 +696,7 @@ impl std::iter::Step for RoomOffset { fn backward(start: Self, count: usize) -> Self { if cfg!(debug_assertions) { - start.backward_checked(count).unwrap_throw() + RoomOffset::backward_checked(start, count).unwrap_throw() } else { start.assume_bounds_constraint(); Self::saturating_new(start.0.saturating_sub(count.min(2 * ROOM_USIZE) as i8))