Wrappers around Box<[u8; LEN]> and similar types with a custom bincode::Decode implementation that deserializes directly into heap memory.

This exists because the implementation that #[derive(Decode)] generates for Box<[u8; LEN]> deserializes into stack memory and then moves to the heap, which is problematic when deserializing large arrays (particularly on Windows).

For types other than u8, these implementations also feature significantly more efficient bincode::Encode and bincode::Decode implementations than what bincode's derive macros would produce.

12use crate::frontend::Color;
13use bincode::de::read::Reader;
14use bincode::de::{BorrowDecoder, Decoder};
15use bincode::enc::Encoder;
16use bincode::enc::write::Writer;
17use bincode::error::{DecodeError, EncodeError};
18use bincode::{BorrowDecode, Decode, Encode};
19use bytemuck::Pod;
20use rand::distr::StandardUniform;
21use rand::prelude::Distribution;
22use std::fmt::Debug;
23use std::ops::{Deref, DerefMut};
25#[derive(Debug, Clone)]
26pub struct BoxedArray<T, const LEN: usize>(Box<[T; LEN]>);
27
28impl<T: Debug + Default + Copy, const LEN: usize> Default for BoxedArray<T, LEN> {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl<T: Debug + Default + Copy, const LEN: usize> BoxedArray<T, LEN> {
35    #[must_use]
36    #[allow(clippy::missing_panics_doc)]
37    pub fn new() -> Self {
38        Self(vec![T::default(); LEN].into_boxed_slice().try_into().unwrap())
39    }
40}
41
42impl<T: Debug + Default + Copy, const LEN: usize> BoxedArray<T, LEN>
43where
44    StandardUniform: Distribution<T>,
45{
46    #[must_use]
47    pub fn new_random() -> Self {
48        let mut array = Self::new();
49        array.fill_with(rand::random);
50        array
51    }
52}
53
54impl<T, const LEN: usize> From<Box<[T; LEN]>> for BoxedArray<T, LEN> {
55    fn from(value: Box<[T; LEN]>) -> Self {
56        Self(value)
57    }
58}
59
60impl<T, const LEN: usize> From<BoxedArray<T, LEN>> for Box<[T; LEN]> {
61    fn from(value: BoxedArray<T, LEN>) -> Self {
62        value.0
63    }
64}
65
66impl<T, const LEN: usize> Deref for BoxedArray<T, LEN> {
67    type Target = Box<[T; LEN]>;
68
69    fn deref(&self) -> &Self::Target {
70        &self.0
71    }
72}
73
74impl<T, const LEN: usize> DerefMut for BoxedArray<T, LEN> {
75    fn deref_mut(&mut self) -> &mut Self::Target {
76        &mut self.0
77    }
78}
79
80impl<T: Pod, const LEN: usize> Encode for BoxedArray<T, LEN> {
81    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
82        // This is _significantly_ faster than the #[derive(Encode)] implementation for non-u8 types
83        let bytes = bytemuck::cast_slice(self.0.as_slice());
84        encoder.writer().write(bytes)
85    }
86}
87
88impl<T: Debug + Default + Copy + Pod, Context, const LEN: usize> Decode<Context>
89    for BoxedArray<T, LEN>
90{
91    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
92        // Similarly, this is _significantly_ faster than the #[derive(Decode)] implementation for non-u8 types
93        let mut array = vec![T::default(); LEN];
94        decoder.reader().read(bytemuck::cast_slice_mut(&mut array))?;
95
96        Ok(Self(array.into_boxed_slice().try_into().unwrap()))
97    }
98}
99
100impl<'de, T: Debug + Default + Copy + Pod, Context, const LEN: usize> BorrowDecode<'de, Context>
101    for BoxedArray<T, LEN>
102{
103    fn borrow_decode<D: BorrowDecoder<'de, Context = Context>>(
104        decoder: &mut D,
105    ) -> Result<Self, DecodeError> {
106        Self::decode(decoder)
107    }
108}
109
110pub type BoxedByteArray<const LEN: usize> = BoxedArray<u8, LEN>;
111pub type BoxedWordArray<const LEN: usize> = BoxedArray<u16, LEN>;
112pub type BoxedColorArray<const LEN: usize> = BoxedArray<Color, LEN>;
113
114#[derive(Debug, Clone)]
115pub struct Boxed2DArray<T, const ROWS: usize, const COLS: usize>(Box<[[T; COLS]; ROWS]>);
116
117impl<T: Debug + Default + Copy, const ROWS: usize, const COLS: usize> Default
118    for Boxed2DArray<T, ROWS, COLS>
119{
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125impl<T: Debug + Default + Copy, const ROWS: usize, const COLS: usize> Boxed2DArray<T, ROWS, COLS> {
126    #[must_use]
127    pub fn new() -> Self {
128        // SAFETY: Memory is fully filled with T::default() before calling assume_init()
129        // Total allocation length is (ROWS * COLS * size_of::<T>()), and it is only accessed through
130        // a *mut T with offset strictly less than (ROWS * COLS)
131        unsafe {
132            let mut array = Box::<[[T; COLS]; ROWS]>::new_uninit();
133            let ptr = array.as_mut_ptr().cast::<T>();
134            for i in 0..ROWS * COLS {
135                ptr.add(i).write(T::default());
136            }
137            Self(array.assume_init())
138        }
139    }
140}
141
142impl<T, const ROWS: usize, const COLS: usize> From<Box<[[T; COLS]; ROWS]>>
143    for Boxed2DArray<T, ROWS, COLS>
144{
145    fn from(value: Box<[[T; COLS]; ROWS]>) -> Self {
146        Self(value)
147    }
148}
149
150impl<T, const ROWS: usize, const COLS: usize> From<Boxed2DArray<T, ROWS, COLS>>
151    for Box<[[T; COLS]; ROWS]>
152{
153    fn from(value: Boxed2DArray<T, ROWS, COLS>) -> Self {
154        value.0
155    }
156}
157
158impl<T, const ROWS: usize, const COLS: usize> Deref for Boxed2DArray<T, ROWS, COLS> {
159    type Target = Box<[[T; COLS]; ROWS]>;
160
161    fn deref(&self) -> &Self::Target {
162        &self.0
163    }
164}
165
166impl<T, const ROWS: usize, const COLS: usize> DerefMut for Boxed2DArray<T, ROWS, COLS> {
167    fn deref_mut(&mut self) -> &mut Self::Target {
168        &mut self.0
169    }
170}
171
172impl<T: Pod, const ROWS: usize, const COLS: usize> Encode for Boxed2DArray<T, ROWS, COLS> {
173    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
174        let writer = encoder.writer();
175        for row in self.as_slice() {
176            writer.write(bytemuck::cast_slice(row))?;
177        }
178
179        Ok(())
180    }
181}
182
183impl<T: Debug + Default + Copy + Pod, Context, const ROWS: usize, const COLS: usize> Decode<Context>
184    for Boxed2DArray<T, ROWS, COLS>
185{
186    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
187        let mut array = Self::new();
188        let reader = decoder.reader();
189        for row in array.as_mut_slice() {
190            reader.read(bytemuck::cast_slice_mut(row))?;
191        }
192
193        Ok(array)
194    }
195}
196
197impl<'de, T: Debug + Default + Copy + Pod, Context, const ROWS: usize, const COLS: usize>
198    BorrowDecode<'de, Context> for Boxed2DArray<T, ROWS, COLS>
199{
200    fn borrow_decode<D: BorrowDecoder<'de, Context = Context>>(
201        decoder: &mut D,
202    ) -> Result<Self, DecodeError> {
203        Self::decode(decoder)
204    }
205}
206
207pub type Boxed2DWordArray<const ROWS: usize, const COLS: usize> = Boxed2DArray<u16, ROWS, COLS>;
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    // Test should be run with miri:
214    //   $ cargo +nightly miri test -p jgenesis-common
215    #[test]
216    fn new_boxed_2d_array() {
217        let array: Boxed2DArray<Color, 10, 10> = Boxed2DArray::new();
218
219        for row in array.as_slice() {
220            for &color in row {
221                assert_eq!(color, Color::default());
222            }
223        }
224    }
225}