1//! Wrappers around `Box<[u8; LEN]>` and similar types with a custom `bincode::Decode`
2//! implementation that deserializes directly into heap memory.
3//!
4//! This exists because the implementation that `#[derive(Decode)]` generates for `Box<[u8; LEN]>`
5//! deserializes into stack memory and then moves to the heap, which is problematic when deserializing
6//! large arrays (particularly on Windows).
7//!
8//! For types other than `u8`, these implementations also feature significantly more efficient
9//! `bincode::Encode` and `bincode::Decode` implementations than what bincode's derive macros
10//! would produce.
11
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};
24
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}