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}