screeps/local/
cost_matrix.rs

1use std::ops::{Index, IndexMut};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    constants::ROOM_AREA,
7    objects::CostMatrix,
8    traits::{CostMatrixGet, CostMatrixSet},
9};
10
11use super::{linear_index_to_xy, Position, RoomXY, XMajor};
12
13/// A matrix of pathing costs for a room, stored in Rust memory.
14///
15/// Use [`CostMatrix`] if a reference to data stored in JavaScript memory is
16/// preferred.
17#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(transparent)]
19pub struct LocalCostMatrix {
20    #[serde(with = "serde_impls")]
21    bits: [u8; ROOM_AREA],
22}
23
24impl Default for LocalCostMatrix {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl LocalCostMatrix {
31    /// Create a `LocalCostMatrix` with a default value of 0 for all positions.
32    ///
33    /// # Example
34    ///
35    /// ```rust
36    /// use screeps::local::{LocalCostMatrix, RoomXY};
37    ///
38    /// let lcm = LocalCostMatrix::new();
39    /// let pos = unsafe { RoomXY::unchecked_new(10, 10) };
40    /// assert_eq!(lcm.get(pos), 0);
41    /// ```
42    #[inline]
43    pub const fn new() -> Self {
44        LocalCostMatrix::new_with_value(0)
45    }
46
47    /// Create a `LocalCostMatrix` with a default value for all positions.
48    ///
49    /// # Example
50    ///
51    /// ```rust
52    /// use screeps::local::{LocalCostMatrix, RoomXY};
53    ///
54    /// let lcm = LocalCostMatrix::new_with_value(u8::MAX);
55    /// let pos = unsafe { RoomXY::unchecked_new(10, 10) };
56    /// assert_eq!(lcm.get(pos), u8::MAX);
57    /// ```
58    #[inline]
59    pub const fn new_with_value(value: u8) -> Self {
60        LocalCostMatrix {
61            bits: [value; ROOM_AREA],
62        }
63    }
64
65    // # Notes
66    // This method does no bounds checking for the passed-in `RoomXY`, you may use
67    // `RoomXY::unchecked_new` to skip all bounds checking.
68    #[inline]
69    pub fn set(&mut self, xy: RoomXY, val: u8) {
70        self[xy] = val;
71    }
72
73    // # Notes
74    // This method does no bounds checking for the passed-in `RoomXY`, you may use
75    // `RoomXY::unchecked_new` to skip all bounds checking.
76    #[inline]
77    pub fn get(&self, xy: RoomXY) -> u8 {
78        self[xy]
79    }
80
81    pub const fn get_bits(&self) -> &[u8; ROOM_AREA] {
82        &self.bits
83    }
84
85    pub fn iter(&self) -> impl Iterator<Item = (RoomXY, u8)> + '_ {
86        self.bits
87            .iter()
88            .enumerate()
89            .map(|(idx, &val)| (linear_index_to_xy(idx), val))
90    }
91
92    pub fn iter_mut(&mut self) -> impl Iterator<Item = (RoomXY, &mut u8)> {
93        self.bits
94            .iter_mut()
95            .enumerate()
96            .map(|(idx, val)| (linear_index_to_xy(idx), val))
97    }
98}
99
100impl From<LocalCostMatrix> for Vec<u8> {
101    /// Returns a vector of bits length ROOM_AREA, where each position is
102    /// `idx = ((x * ROOM_SIZE) + y)`.
103    #[inline]
104    fn from(lcm: LocalCostMatrix) -> Vec<u8> {
105        lcm.bits.into()
106    }
107}
108
109impl From<&LocalCostMatrix> for Vec<u8> {
110    fn from(lcm: &LocalCostMatrix) -> Vec<u8> {
111        lcm.bits.into()
112    }
113}
114
115impl From<&CostMatrix> for LocalCostMatrix {
116    fn from(js_matrix: &CostMatrix) -> Self {
117        let mut bits = [0; ROOM_AREA];
118        js_matrix.get_bits().copy_to(&mut bits);
119
120        LocalCostMatrix { bits }
121    }
122}
123
124impl AsRef<XMajor<u8>> for LocalCostMatrix {
125    fn as_ref(&self) -> &XMajor<u8> {
126        XMajor::from_flat_ref(&self.bits)
127    }
128}
129
130impl AsMut<XMajor<u8>> for LocalCostMatrix {
131    fn as_mut(&mut self) -> &mut XMajor<u8> {
132        XMajor::from_flat_mut(&mut self.bits)
133    }
134}
135
136impl Index<RoomXY> for LocalCostMatrix {
137    type Output = u8;
138
139    fn index(&self, xy: RoomXY) -> &Self::Output {
140        &self.bits[xy.x][xy.y]
141    }
142}
143
144impl IndexMut<RoomXY> for LocalCostMatrix {
145    fn index_mut(&mut self, xy: RoomXY) -> &mut Self::Output {
146        &mut self.bits[xy.x][xy.y]
147    }
148}
149
150impl Index<Position> for LocalCostMatrix {
151    type Output = u8;
152
153    fn index(&self, idx: Position) -> &Self::Output {
154        &self[RoomXY::from(idx)]
155    }
156}
157
158impl IndexMut<Position> for LocalCostMatrix {
159    fn index_mut(&mut self, idx: Position) -> &mut Self::Output {
160        &mut self[RoomXY::from(idx)]
161    }
162}
163
164impl CostMatrixSet for LocalCostMatrix {
165    fn set_xy(&mut self, xy: RoomXY, cost: u8) {
166        LocalCostMatrix::set(self, xy, cost);
167    }
168}
169
170impl CostMatrixGet for LocalCostMatrix {
171    fn get_xy(&mut self, xy: RoomXY) -> u8 {
172        LocalCostMatrix::get(self, xy)
173    }
174}
175
176// need custom implementation in order to ensure length of 'bits' is always
177// ROOM_AREA
178mod serde_impls {
179    use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
180
181    use super::ROOM_AREA;
182
183    pub(super) fn serialize<S>(bits: &[u8; ROOM_AREA], serializer: S) -> Result<S::Ok, S::Error>
184    where
185        S: Serializer,
186    {
187        bits[..].serialize(serializer)
188    }
189
190    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<[u8; ROOM_AREA], D::Error>
191    where
192        D: Deserializer<'de>,
193    {
194        let bits_slice: &[u8] = <&[u8]>::deserialize(deserializer)?;
195
196        if bits_slice.len() != ROOM_AREA {
197            return Err(D::Error::invalid_length(
198                bits_slice.len(),
199                &format!("a vec of length {ROOM_AREA}").as_str(),
200            ));
201        }
202
203        // SAFETY: If the length wasn't right, we would have hit the check above
204        Ok(bits_slice.try_into().unwrap())
205    }
206}