Skip to main content

serializer/machine/
builder.rs

1//! DX-Machine in-place serialization builder
2
3use super::header::DxMachineHeader;
4use super::slot::{DxMachineSlot, MAX_INLINE_SIZE};
5
6/// Builder for in-place DX-Machine serialization using RKYV
7///
8/// This builder writes directly to a pre-allocated buffer without
9/// any intermediate allocations or copying. Serialization time is
10/// effectively zero since it's just memory writes.
11pub struct DxMachineBuilder<'a> {
12    /// The target buffer
13    buffer: &'a mut Vec<u8>,
14    /// Current write position for heap data
15    heap_cursor: usize,
16    /// Header (written at position 0)
17    header: DxMachineHeader,
18}
19
20impl<'a> DxMachineBuilder<'a> {
21    /// Create a new builder with buffer
22    ///
23    /// Initializes the header and reserves space for fixed fields + slots.
24    /// The `fixed_size` is the size of all primitive fields.
25    /// The `slot_count` is the number of variable-length fields.
26    #[inline]
27    pub fn new(buffer: &'a mut Vec<u8>, fixed_size: usize, slot_count: usize) -> Self {
28        let header = DxMachineHeader::new();
29
30        // Calculate heap start offset
31        let heap_offset = DxMachineHeader::size() + fixed_size + (slot_count * 16);
32
33        // Reserve space
34        buffer.clear();
35        buffer.reserve(heap_offset + 256); // Reserve extra for heap
36
37        // Write header placeholder
38        buffer.extend_from_slice(&[0u8; 4]);
39
40        // Initialize fixed section with zeros
41        buffer.resize(heap_offset, 0);
42
43        Self {
44            buffer,
45            heap_cursor: heap_offset,
46            header,
47        }
48    }
49
50    /// Write fixed field at offset (direct memory write)
51    #[inline]
52    #[allow(unsafe_code)]
53    pub fn write_fixed<T: Copy>(&mut self, offset: usize, value: T) {
54        // SAFETY: `value` is a live `T` reference and this creates a read-only
55        // byte view exactly `size_of::<T>()` long for immediate copying.
56        let bytes = unsafe {
57            std::slice::from_raw_parts(&value as *const T as *const u8, std::mem::size_of::<T>())
58        };
59
60        let start = DxMachineHeader::size() + offset;
61        self.buffer[start..start + bytes.len()].copy_from_slice(bytes);
62    }
63
64    /// Write u8 at offset
65    #[inline]
66    pub fn write_u8(&mut self, offset: usize, value: u8) {
67        self.buffer[DxMachineHeader::size() + offset] = value;
68    }
69
70    /// Write u16 at offset (little-endian)
71    #[inline]
72    pub fn write_u16(&mut self, offset: usize, value: u16) {
73        let start = DxMachineHeader::size() + offset;
74        self.buffer[start..start + 2].copy_from_slice(&value.to_le_bytes());
75    }
76
77    /// Write u32 at offset (little-endian)
78    #[inline]
79    pub fn write_u32(&mut self, offset: usize, value: u32) {
80        let start = DxMachineHeader::size() + offset;
81        self.buffer[start..start + 4].copy_from_slice(&value.to_le_bytes());
82    }
83
84    /// Write u64 at offset (little-endian)
85    #[inline]
86    pub fn write_u64(&mut self, offset: usize, value: u64) {
87        let start = DxMachineHeader::size() + offset;
88        self.buffer[start..start + 8].copy_from_slice(&value.to_le_bytes());
89    }
90
91    /// Write i8 at offset
92    #[inline]
93    pub fn write_i8(&mut self, offset: usize, value: i8) {
94        self.write_u8(offset, value as u8);
95    }
96
97    /// Write i16 at offset (little-endian)
98    #[inline]
99    pub fn write_i16(&mut self, offset: usize, value: i16) {
100        self.write_u16(offset, value as u16);
101    }
102
103    /// Write i32 at offset (little-endian)
104    #[inline]
105    pub fn write_i32(&mut self, offset: usize, value: i32) {
106        self.write_u32(offset, value as u32);
107    }
108
109    /// Write i64 at offset (little-endian)
110    #[inline]
111    pub fn write_i64(&mut self, offset: usize, value: i64) {
112        self.write_u64(offset, value as u64);
113    }
114
115    /// Write f32 at offset (little-endian)
116    #[inline]
117    pub fn write_f32(&mut self, offset: usize, value: f32) {
118        let start = DxMachineHeader::size() + offset;
119        self.buffer[start..start + 4].copy_from_slice(&value.to_le_bytes());
120    }
121
122    /// Write f64 at offset (little-endian)
123    #[inline]
124    pub fn write_f64(&mut self, offset: usize, value: f64) {
125        let start = DxMachineHeader::size() + offset;
126        self.buffer[start..start + 8].copy_from_slice(&value.to_le_bytes());
127    }
128
129    /// Write bool at offset
130    #[inline]
131    pub fn write_bool(&mut self, offset: usize, value: bool) {
132        self.write_u8(offset, value as u8);
133    }
134
135    /// Write string to slot (auto inline/heap optimization)
136    #[inline]
137    pub fn write_string(&mut self, slot_offset: usize, value: &str) {
138        self.write_bytes(slot_offset, value.as_bytes());
139    }
140
141    /// Write bytes to slot (auto inline/heap optimization)
142    #[inline]
143    pub fn write_bytes(&mut self, slot_offset: usize, bytes: &[u8]) {
144        let slot_pos = DxMachineHeader::size() + slot_offset;
145
146        if bytes.len() <= MAX_INLINE_SIZE {
147            // Inline: fits in slot
148            let mut slot = DxMachineSlot::new();
149            // SAFETY: We just checked bytes.len() <= MAX_INLINE_SIZE (14 bytes),
150            // so write_inline will always succeed
151            if slot.write_inline(bytes).is_err() {
152                // This should never happen given the length check above
153                return;
154            }
155            self.buffer[slot_pos..slot_pos + 16].copy_from_slice(&slot.data);
156        } else {
157            // Heap: write to heap section
158            let heap_start = self.heap_cursor - (DxMachineHeader::size() + slot_offset);
159            let offset = (self.heap_cursor - heap_start) as u32;
160
161            let slot = DxMachineSlot::heap_reference(offset, bytes.len() as u32);
162            self.buffer[slot_pos..slot_pos + 16].copy_from_slice(&slot.data);
163
164            // Write data to heap
165            self.buffer.extend_from_slice(bytes);
166            self.heap_cursor += bytes.len();
167            self.header.set_has_heap(true);
168        }
169    }
170
171    /// Write array to slot (similar to bytes)
172    #[inline]
173    #[allow(unsafe_code)]
174    pub fn write_array<T: Copy>(&mut self, slot_offset: usize, values: &[T]) {
175        // SAFETY: `values` is a live slice and this creates a read-only byte
176        // view exactly `size_of_val(values)` long for immediate copying.
177        let bytes = unsafe {
178            std::slice::from_raw_parts(values.as_ptr() as *const u8, std::mem::size_of_val(values))
179        };
180        self.write_bytes(slot_offset, bytes);
181    }
182
183    /// Finalize and return the serialized length
184    #[inline]
185    pub fn finish(self) -> usize {
186        // Write header at beginning
187        self.header.write_to(&mut self.buffer[0..4]);
188
189        // Shrink to actual size
190        self.buffer.truncate(self.heap_cursor);
191
192        self.heap_cursor
193    }
194
195    /// Get current heap cursor position
196    #[inline]
197    pub fn heap_position(&self) -> usize {
198        self.heap_cursor
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_builder_basic() {
208        let mut buffer = Vec::new();
209        let mut builder = DxMachineBuilder::new(&mut buffer, 8, 1); // 8 bytes fixed, 1 slot
210
211        builder.write_u64(0, 12345);
212        builder.write_string(8, "test");
213
214        let _size = builder.finish();
215
216        assert!(_size > 0);
217        assert_eq!(buffer[0], 0x5A); // Magic
218        assert_eq!(buffer[1], 0x44);
219    }
220
221    #[test]
222    fn test_builder_inline_string() {
223        let mut buffer = Vec::new();
224        let mut builder = DxMachineBuilder::new(&mut buffer, 0, 1);
225
226        builder.write_string(0, "Hello"); // 5 bytes, inline
227
228        let _size = builder.finish();
229
230        // Check slot is inline
231        let slot_data = &buffer[4..20];
232        assert_eq!(slot_data[0], 5); // Length
233        assert_eq!(slot_data[15], 0x00); // Inline marker
234        assert_eq!(&slot_data[1..6], b"Hello");
235    }
236
237    #[test]
238    fn test_builder_heap_string() {
239        let mut buffer = Vec::new();
240        let mut builder = DxMachineBuilder::new(&mut buffer, 0, 1);
241
242        let long_str = "This is a very long string that exceeds 14 bytes";
243        builder.write_string(0, long_str);
244
245        let size = builder.finish();
246
247        // Check slot is heap reference
248        let slot_data = &buffer[4..20];
249        assert_eq!(slot_data[15], 0xFF); // Heap marker
250
251        // Check heap data exists
252        assert!(size > 20);
253    }
254
255    #[test]
256    #[allow(clippy::approx_constant)] // Using 3.14 intentionally for test data
257    fn test_builder_multiple_fields() {
258        let mut buffer = Vec::new();
259        let mut builder = DxMachineBuilder::new(&mut buffer, 17, 2);
260
261        builder.write_u64(0, 999);
262        builder.write_u32(8, 42);
263        builder.write_bool(12, true);
264        builder.write_f32(13, 3.14);
265        builder.write_string(17, "name");
266        builder.write_string(33, "email");
267
268        let _size = builder.finish();
269        assert!(_size > 0);
270    }
271
272    #[test]
273    fn test_builder_primitive_types() {
274        let mut buffer = Vec::new();
275        let mut builder = DxMachineBuilder::new(&mut buffer, 30, 0);
276
277        builder.write_u8(0, 255);
278        builder.write_i8(1, -128);
279        builder.write_u16(2, 65535);
280        builder.write_i16(4, -32768);
281        builder.write_u32(6, 4294967295);
282        builder.write_i32(10, -2147483648);
283        builder.write_u64(14, u64::MAX);
284        builder.write_i64(22, i64::MIN);
285
286        builder.finish();
287
288        // Verify values are written correctly
289        assert_eq!(buffer[4], 255); // u8
290        assert_eq!(buffer[5] as i8, -128); // i8
291    }
292}