Skip to main content

hybrid_array/
wincode.rs

1//! Support for serializing and deserializing `Array` using wincode.
2
3use crate::{Array, ArraySize};
4use core::mem;
5use core::mem::MaybeUninit;
6use core::mem::transmute;
7use wincode::ReadResult;
8use wincode::SchemaRead;
9use wincode::SchemaWrite;
10use wincode::TypeMeta;
11use wincode::WriteResult;
12use wincode::ZeroCopy;
13use wincode::io::Reader;
14use wincode::io::Writer;
15
16pub(crate) struct SliceDropGuard<T> {
17    ptr: *mut MaybeUninit<T>,
18    initialized_len: usize,
19}
20
21impl<T> SliceDropGuard<T> {
22    pub(crate) fn new(ptr: *mut MaybeUninit<T>) -> Self {
23        Self {
24            ptr,
25            initialized_len: 0,
26        }
27    }
28
29    #[inline(always)]
30    #[allow(clippy::arithmetic_side_effects)]
31    pub(crate) fn inc_len(&mut self) {
32        self.initialized_len += 1;
33    }
34}
35
36impl<T> Drop for SliceDropGuard<T> {
37    #[inline(always)]
38    fn drop(&mut self) {
39        unsafe {
40            core::ptr::drop_in_place(core::ptr::slice_from_raw_parts_mut(
41                self.ptr.cast::<T>(),
42                self.initialized_len,
43            ));
44        }
45    }
46}
47
48// SAFETY:
49// - Array<T, U> where T: ZeroCopy is trivially zero-copy. The length is constant,
50//   so there is no length encoding.
51unsafe impl<T, U: ArraySize> ZeroCopy for Array<T, U> where T: ZeroCopy {}
52
53impl<'de, T, U> SchemaRead<'de> for Array<T, U>
54where
55    T: SchemaRead<'de>,
56    U: ArraySize,
57{
58    type Dst = Array<T::Dst, U>;
59
60    const TYPE_META: TypeMeta = const {
61        match T::TYPE_META {
62            TypeMeta::Static { size, zero_copy } => TypeMeta::Static {
63                size: U::USIZE * size,
64                zero_copy,
65            },
66            TypeMeta::Dynamic => TypeMeta::Dynamic,
67        }
68    };
69
70    #[inline]
71    fn read(reader: &mut impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
72        if let TypeMeta::Static {
73            zero_copy: true, ..
74        } = T::TYPE_META
75        {
76            // SAFETY: `T::Dst` is zero-copy eligible (no invalid bit patterns, no layout requirements, no endianness checks, etc.).
77            unsafe { reader.copy_into_t(dst)? };
78            return Ok(());
79        }
80
81        // SAFETY: MaybeUninit<Array<T::Dst, U>> trivially converts to Array<MaybeUninit<T::Dst>, U>.
82        let dst = unsafe {
83            transmute::<&mut MaybeUninit<Self::Dst>, &mut Array<MaybeUninit<T::Dst>, U>>(dst)
84        };
85        let base = dst.as_mut_ptr();
86        let mut guard = SliceDropGuard::<T::Dst>::new(base);
87        if let TypeMeta::Static { size, .. } = Self::TYPE_META {
88            // SAFETY: `Self::TYPE_META` specifies a static size, which is `U::USIZE * static_size_of(T)`.
89            // `U::USIZE` reads of `T` will consume `size` bytes, fully consuming the trusted window.
90            let reader = &mut unsafe { reader.as_trusted_for(size) }?;
91            for i in 0..U::USIZE {
92                let slot = unsafe { &mut *base.add(i) };
93                T::read(reader, slot)?;
94                guard.inc_len();
95            }
96        } else {
97            for i in 0..U::USIZE {
98                let slot = unsafe { &mut *base.add(i) };
99                T::read(reader, slot)?;
100                guard.inc_len();
101            }
102        }
103        mem::forget(guard);
104        Ok(())
105    }
106}
107
108impl<T, U> SchemaWrite for Array<T, U>
109where
110    T: SchemaWrite,
111    T::Src: Sized,
112    U: ArraySize,
113{
114    type Src = Array<T::Src, U>;
115
116    const TYPE_META: TypeMeta = const {
117        match T::TYPE_META {
118            TypeMeta::Static { size, zero_copy } => TypeMeta::Static {
119                size: U::USIZE * size,
120                zero_copy,
121            },
122            TypeMeta::Dynamic => TypeMeta::Dynamic,
123        }
124    };
125
126    #[inline]
127    #[allow(clippy::arithmetic_side_effects)]
128    fn size_of(value: &Self::Src) -> WriteResult<usize> {
129        if let TypeMeta::Static { size, .. } = Self::TYPE_META {
130            return Ok(size);
131        }
132        // Extremely unlikely a type-in-memory's size will overflow usize::MAX.
133        value
134            .iter()
135            .map(T::size_of)
136            .try_fold(0usize, |acc, x| x.map(|x| acc + x))
137    }
138
139    #[inline]
140    fn write(writer: &mut impl Writer, value: &Self::Src) -> WriteResult<()> {
141        match Self::TYPE_META {
142            TypeMeta::Static {
143                zero_copy: true, ..
144            } => {
145                // SAFETY: `T::Src` is zero-copy eligible (no invalid bit patterns, no layout requirements, no endianness checks, etc.).
146                unsafe { writer.write_slice_t(value)? };
147            }
148            TypeMeta::Static {
149                size,
150                zero_copy: false,
151            } => {
152                // SAFETY: `Self::TYPE_META` specifies a static size, which is `U::USIZE * static_size_of(T)`.
153                // `U::USIZE` writes of `T` will write `size` bytes, fully initializing the trusted window.
154                let writer = &mut unsafe { writer.as_trusted_for(size) }?;
155                for item in value {
156                    T::write(writer, item)?;
157                }
158                writer.finish()?;
159            }
160            TypeMeta::Dynamic => {
161                for item in value {
162                    T::write(writer, item)?;
163                }
164            }
165        }
166
167        Ok(())
168    }
169}