use crate::error::{Error, Result};
use std::marker::PhantomData;
type HighSer<'a> = rkyv::api::high::HighSerializer<
rkyv::util::AlignedVec,
rkyv::ser::allocator::ArenaHandle<'a>,
rkyv::rancor::Error,
>;
type HighVal<'a> = rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>;
pub fn encode_rkyv<T>(value: &T) -> Result<Vec<u8>>
where
T: for<'a> rkyv::Serialize<HighSer<'a>>,
{
rkyv::to_bytes::<rkyv::rancor::Error>(value)
.map(|b| b.to_vec())
.map_err(|e| Error::serialization(format!("rkyv encode failed: {e}")))
}
pub fn decode_rkyv<T>(bytes: &[u8]) -> Result<T>
where
T: rkyv::Archive,
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<HighVal<'a>>
+ rkyv::Deserialize<T, rkyv::rancor::Strategy<rkyv::de::Pool, rkyv::rancor::Error>>,
{
rkyv::from_bytes::<T, rkyv::rancor::Error>(bytes)
.map_err(|e| Error::serialization(format!("rkyv decode failed: {e}")))
}
pub fn decode_rkyv_ref<T>(bytes: &[u8]) -> Result<&T::Archived>
where
T: rkyv::Archive,
T::Archived: rkyv::Portable + for<'a> rkyv::bytecheck::CheckBytes<HighVal<'a>>,
{
rkyv::access::<T::Archived, rkyv::rancor::Error>(bytes)
.map_err(|e| Error::serialization(format!("rkyv zero-copy access failed: {e}")))
}
pub struct ZeroCopyMessage<T> {
bytes: Vec<u8>,
_marker: PhantomData<T>,
}
impl<T> ZeroCopyMessage<T> {
pub fn new(value: &T) -> Result<Self>
where
T: for<'a> rkyv::Serialize<HighSer<'a>>,
{
Ok(Self {
bytes: encode_rkyv(value)?,
_marker: PhantomData,
})
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self>
where
T: rkyv::Archive,
T::Archived: rkyv::Portable + for<'a> rkyv::bytecheck::CheckBytes<HighVal<'a>>,
{
rkyv::access::<T::Archived, rkyv::rancor::Error>(&bytes)
.map_err(|e| Error::serialization(format!("rkyv validation failed: {e}")))?;
Ok(Self {
bytes,
_marker: PhantomData,
})
}
pub fn decode(&self) -> Result<T>
where
T: rkyv::Archive,
T::Archived: for<'a> rkyv::bytecheck::CheckBytes<HighVal<'a>>
+ rkyv::Deserialize<T, rkyv::rancor::Strategy<rkyv::de::Pool, rkyv::rancor::Error>>,
{
decode_rkyv(&self.bytes)
}
pub fn decode_ref(&self) -> Result<&T::Archived>
where
T: rkyv::Archive,
T::Archived: rkyv::Portable + for<'a> rkyv::bytecheck::CheckBytes<HighVal<'a>>,
{
decode_rkyv_ref::<T>(&self.bytes)
}
pub fn len(&self) -> usize {
self.bytes.len()
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
}
impl<T> std::fmt::Debug for ZeroCopyMessage<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ZeroCopyMessage")
.field("size", &self.bytes.len())
.finish()
}
}
#[derive(Debug, Clone, Copy)]
pub struct SerializationLatency {
pub encode_ns: u64,
pub decode_ns: u64,
pub decode_ref_ns: u64,
pub payload_bytes: usize,
}
pub fn measure_latency<T>(value: &T) -> Result<SerializationLatency>
where
T: rkyv::Archive + for<'a> rkyv::Serialize<HighSer<'a>>,
T::Archived: rkyv::Portable
+ rkyv::Deserialize<T, rkyv::rancor::Strategy<rkyv::de::Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<HighVal<'a>>,
{
use std::time::Instant;
let start = Instant::now();
let bytes = encode_rkyv(value)?;
let encode_ns = start.elapsed().as_nanos() as u64;
let payload_bytes = bytes.len();
let start = Instant::now();
let _owned: T = decode_rkyv(&bytes)?;
let decode_ns = start.elapsed().as_nanos() as u64;
let start = Instant::now();
let _ref: &T::Archived = decode_rkyv_ref::<T>(&bytes)?;
let decode_ref_ns = start.elapsed().as_nanos() as u64;
Ok(SerializationLatency {
encode_ns,
decode_ns,
decode_ref_ns,
payload_bytes,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct SimpleMessage {
id: u64,
label: String,
flag: bool,
}
#[derive(Debug, Clone, PartialEq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct InnerData {
values: Vec<u32>,
name: String,
}
#[derive(Debug, Clone, PartialEq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct NestedMessage {
id: u64,
inner: InnerData,
extra: Option<String>,
}
#[test]
fn roundtrip_simple_struct() {
let original = SimpleMessage {
id: 42,
label: "hello".to_string(),
flag: true,
};
let bytes = encode_rkyv(&original).expect("encode failed");
let restored: SimpleMessage = decode_rkyv(&bytes).expect("decode failed");
assert_eq!(restored, original);
}
#[test]
fn roundtrip_nested_struct() {
let original = NestedMessage {
id: 99,
inner: InnerData {
values: vec![1, 2, 3, 4, 5],
name: "nested".to_string(),
},
extra: Some("optional data".to_string()),
};
let bytes = encode_rkyv(&original).expect("encode failed");
let restored: NestedMessage = decode_rkyv(&bytes).expect("decode failed");
assert_eq!(restored, original);
}
#[test]
fn zero_copy_ref_access() {
let original = SimpleMessage {
id: 7,
label: "zero-copy".to_string(),
flag: false,
};
let bytes = encode_rkyv(&original).expect("encode failed");
let archived: &<SimpleMessage as rkyv::Archive>::Archived =
decode_rkyv_ref::<SimpleMessage>(&bytes).expect("zero-copy access failed");
assert_eq!(archived.id, original.id);
assert_eq!(archived.label.as_str(), original.label.as_str());
assert_eq!(archived.flag, original.flag);
}
#[test]
fn zero_copy_message_wrapper_roundtrip() {
let original = NestedMessage {
id: 1,
inner: InnerData {
values: vec![10, 20],
name: "wrapper-test".to_string(),
},
extra: None,
};
let msg = ZeroCopyMessage::new(&original).expect("new failed");
assert!(!msg.is_empty());
let restored: NestedMessage = msg.decode().expect("decode failed");
assert_eq!(restored, original);
}
#[test]
fn zero_copy_message_ref_access() {
let original = SimpleMessage {
id: 123,
label: "ref-access".to_string(),
flag: true,
};
let msg = ZeroCopyMessage::new(&original).expect("new failed");
let archived: &<SimpleMessage as rkyv::Archive>::Archived =
msg.decode_ref().expect("decode_ref failed");
assert_eq!(archived.id, original.id);
assert_eq!(archived.label.as_str(), original.label.as_str());
}
#[test]
fn zero_copy_message_from_bytes_roundtrip() {
let original = SimpleMessage {
id: 55,
label: "from-bytes".to_string(),
flag: false,
};
let msg = ZeroCopyMessage::new(&original).expect("new failed");
let raw = msg.into_bytes();
let msg2 = ZeroCopyMessage::from_bytes(raw).expect("from_bytes failed");
let restored: SimpleMessage = msg2.decode().expect("decode failed");
assert_eq!(restored, original);
}
#[test]
fn measure_latency_does_not_panic() {
let value = NestedMessage {
id: 1,
inner: InnerData {
values: (0..1000).collect(),
name: "bench".to_string(),
},
extra: Some("data".to_string()),
};
let latency = measure_latency(&value).expect("measure_latency failed");
assert!(latency.encode_ns > 0);
assert!(latency.decode_ns > 0);
assert!(latency.decode_ref_ns > 0);
assert!(latency.payload_bytes > 0);
}
}