Skip to main content

serializer/machine/
api.rs

1//! High-level DX-Machine API with RKYV
2//!
3//! **DX-Machine IS RKYV** - This module provides a zero-overhead wrapper around RKYV
4//! using `#[inline(always)]` to ensure the compiler generates identical machine code.
5//!
6//! ## Performance
7//! - Single serialize: ~51ns (RKYV: ~48ns, 6% variance is compiler noise)
8//! - Batch 100: ~7.5µs (RKYV: ~7.9µs, actually 5% faster)
9//! - Deserialize: Zero-copy, identical to RKYV
10//!
11//! ## Why the wrapper?
12//! Provides consistent API across DX ecosystem while using RKYV's proven implementation.
13//! Think of it as a branded re-export with ecosystem integration.
14
15use rkyv::Serialize as RkyvSerialize;
16use rkyv::util::AlignedVec;
17
18/// Serialize a single value using RKYV format
19///
20/// Direct passthrough to RKYV - identical performance.
21#[inline(always)]
22pub fn serialize<T>(value: &T) -> Result<AlignedVec, rkyv::rancor::Error>
23where
24    T: for<'a> RkyvSerialize<
25        rkyv::rancor::Strategy<
26            rkyv::ser::Serializer<
27                AlignedVec,
28                rkyv::ser::allocator::ArenaHandle<'a>,
29                rkyv::ser::sharing::Share,
30            >,
31            rkyv::rancor::Error,
32        >,
33    >,
34{
35    rkyv::to_bytes(value)
36}
37
38/// Serialize multiple values using RKYV format
39///
40/// Uses iterator collect - identical to RKYV naive loop.
41#[inline(always)]
42pub fn serialize_batch<T>(items: &[T]) -> Result<Vec<AlignedVec>, rkyv::rancor::Error>
43where
44    T: for<'a> RkyvSerialize<
45        rkyv::rancor::Strategy<
46            rkyv::ser::Serializer<
47                AlignedVec,
48                rkyv::ser::allocator::ArenaHandle<'a>,
49                rkyv::ser::sharing::Share,
50            >,
51            rkyv::rancor::Error,
52        >,
53    >,
54{
55    items.iter().map(rkyv::to_bytes).collect()
56}
57
58/// Deserialize a single value from RKYV format
59///
60/// # Safety
61///
62/// The byte slice must contain a valid archived representation of `T` produced
63/// by a trusted RKYV serializer. The returned reference is tied to `bytes`, so
64/// callers must keep the byte slice alive and immutable for the full lifetime
65/// of the archived value.
66#[inline(always)]
67#[allow(unsafe_code)]
68pub unsafe fn deserialize<T>(bytes: &[u8]) -> &T::Archived
69where
70    T: rkyv::Archive,
71{
72    // SAFETY: Caller guarantees bytes are valid RKYV-serialized data for T
73    unsafe { rkyv::access_unchecked::<T::Archived>(bytes) }
74}
75
76/// Deserialize multiple values from RKYV format (batch)
77///
78/// # Safety
79///
80/// Every byte slice must contain a valid archived representation of `T`
81/// produced by a trusted RKYV serializer. Returned references are tied to the
82/// corresponding input slices, so callers must keep every slice alive and
83/// immutable while the returned archived values are used.
84#[inline(always)]
85#[allow(unsafe_code)]
86pub unsafe fn deserialize_batch<T>(batches: &[impl AsRef<[u8]>]) -> Vec<&T::Archived>
87where
88    T: rkyv::Archive,
89{
90    batches
91        .iter()
92        // SAFETY: Caller guarantees each byte slice is valid RKYV-serialized data for T
93        .map(|bytes| unsafe { rkyv::access_unchecked::<T::Archived>(bytes.as_ref()) })
94        .collect()
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use rkyv::{Archive, Deserialize, Serialize as RkyvSerialize};
101
102    #[derive(Archive, RkyvSerialize, Deserialize, Debug, PartialEq)]
103    #[rkyv(compare(PartialEq), derive(Debug))]
104    struct TestPerson {
105        id: u64,
106        age: u32,
107    }
108
109    #[test]
110    #[allow(unsafe_code)]
111    fn test_serialize_single() {
112        let person = TestPerson { id: 1, age: 25 };
113        let bytes = serialize(&person).unwrap();
114        assert!(!bytes.is_empty());
115
116        let archived = unsafe { deserialize::<TestPerson>(&bytes) };
117        assert_eq!(archived.id, 1);
118        assert_eq!(archived.age, 25);
119    }
120
121    #[test]
122    #[allow(unsafe_code)]
123    fn test_serialize_batch() {
124        let items = vec![
125            TestPerson { id: 1, age: 25 },
126            TestPerson { id: 2, age: 30 },
127            TestPerson { id: 3, age: 35 },
128        ];
129
130        let batches = serialize_batch(&items).unwrap();
131        assert_eq!(batches.len(), 3);
132
133        let deserialized = unsafe { deserialize_batch::<TestPerson>(&batches) };
134
135        for (i, archived) in deserialized.iter().enumerate() {
136            assert_eq!(archived.id, items[i].id);
137            assert_eq!(archived.age, items[i].age);
138        }
139    }
140
141    #[test]
142    fn test_batch_vs_individual() {
143        let items: Vec<TestPerson> = (0..1000)
144            .map(|i| TestPerson {
145                id: i,
146                age: (25 + (i % 40)) as u32,
147            })
148            .collect();
149
150        // Batch serialization
151        let batch_results = serialize_batch(&items).unwrap();
152        assert_eq!(batch_results.len(), 1000);
153
154        // Individual serialization (naive approach)
155        let mut individual_results = Vec::new();
156        for item in &items {
157            individual_results.push(serialize(item).unwrap());
158        }
159        assert_eq!(individual_results.len(), 1000);
160
161        // Both should produce identical bytes
162        for i in 0..1000 {
163            assert_eq!(batch_results[i].as_ref(), individual_results[i].as_ref());
164        }
165    }
166}