screeps/local/terrain.rs
1use std::mem::MaybeUninit;
2
3use js_sys::Uint8Array;
4
5use crate::{
6 constants::{Terrain, ROOM_AREA},
7 objects::RoomTerrain,
8};
9
10use super::RoomXY;
11
12#[derive(Debug, Clone)]
13pub struct LocalRoomTerrain {
14 bits: Box<[u8; ROOM_AREA]>,
15}
16
17/// A matrix representing the terrain of a room, stored in Rust memory.
18///
19/// Use [`RoomTerrain`] if data stored in JavaScript memory is preferred.
20impl LocalRoomTerrain {
21 /// Gets the terrain at the specified position in this room.
22 pub fn get_xy(&self, xy: RoomXY) -> Terrain {
23 let byte = self.bits[xy.y][xy.x];
24 // not using Terrain::from_u8() because `0b11` value, wall+swamp, happens
25 // in commonly used server environments (notably the private server default
26 // map), and is special-cased in the engine code; we special-case it here
27 match byte & 0b11 {
28 0b00 => Terrain::Plain,
29 0b01 | 0b11 => Terrain::Wall,
30 0b10 => Terrain::Swamp,
31 // Should be optimized out
32 _ => unreachable!("all combinations of 2 bits are covered"),
33 }
34 }
35
36 /// Creates a `LocalRoomTerrain` from the bytes that correspond to the
37 /// room's terrain data.
38 ///
39 /// This is like the `RoomTerrain` type but performs all operations on data
40 /// stored in wasm memory. Each byte in the array corresponds to the value
41 /// of the `Terrain` at the given position.
42 ///
43 /// The bytes are in row-major order - that is they start at the top left,
44 /// then move to the top right, and then start at the left of the next row.
45 /// This is different from `LocalCostMatrix`, which is column-major.
46 pub fn new_from_bits(bits: Box<[u8; ROOM_AREA]>) -> Self {
47 Self { bits }
48 }
49}
50
51impl From<RoomTerrain> for LocalRoomTerrain {
52 fn from(terrain: RoomTerrain) -> LocalRoomTerrain {
53 // create an uninitialized array of the correct size
54 let mut data: Box<[MaybeUninit<u8>; ROOM_AREA]> =
55 Box::new([MaybeUninit::uninit(); ROOM_AREA]);
56 // create a Uint8Array mapped to the same point in wasm linear memory as our
57 // uninitialized boxed array
58
59 // SAFETY: if any allocations happen in rust, this buffer will be detached from
60 // wasm memory and no longer writable - we use it immediately then discard it to
61 // avoid this
62 let js_buffer =
63 unsafe { Uint8Array::view_mut_raw(data.as_mut_ptr() as *mut u8, ROOM_AREA) };
64
65 // copy the terrain buffer into the memory backing the Uint8Array - this is the
66 // boxed array, so this initializes it
67 terrain
68 .get_raw_buffer_to_array(&js_buffer)
69 .expect("terrain data to copy");
70 // data copied - explicitly drop the Uint8Array, so there's no chance it's used
71 // again
72 drop(js_buffer);
73 // we've got the data in our boxed array, change to the needed type
74 // SAFETY: `Box` has the same layout for sized types. `MaybeUninit<u8>` has the
75 // same layout as `u8`. The arrays are the same size. The `MaybeUninit<u8>` are
76 // all initialized because JS wrote to them.
77 LocalRoomTerrain::new_from_bits(unsafe {
78 std::mem::transmute::<Box<[MaybeUninit<u8>; ROOM_AREA]>, Box<[u8; ROOM_AREA]>>(data)
79 })
80 }
81}