Skip to main content

hydro_lang/sim/
codec.rs

1//! Serialization codecs for simulation inputs and outputs.
2
3use serde::Serialize;
4use serde::de::DeserializeOwned;
5#[cfg(stageleft_runtime)]
6use stageleft::quote_type;
7#[cfg(stageleft_runtime)]
8use syn::parse_quote;
9
10#[cfg(stageleft_runtime)]
11use crate::staging_util::get_this_crate;
12
13/// A serialization codec for values crossing the simulation dylib boundary.
14///
15/// The test and simulation dylib exchange only encoded bytes, so custom codecs do not need to
16/// use [`serde`]. See [`BincodeCodec`] for the default.
17///
18/// The codec is selected purely at the type level, as a type parameter of
19/// [`Stream::sim_output_with`], [`Location::sim_input_with`] and their cluster
20/// counterparts (e.g. `stream.sim_output_with::<MyCodec>()`). No codec value is ever
21/// constructed, so a codec is typically a unit struct, but any type will do.
22///
23/// [`Stream::sim_output_with`]: crate::prelude::Stream::sim_output_with
24/// [`Location::sim_input_with`]: crate::location::Location::sim_input_with
25///
26/// # Defining a custom codec
27///
28/// Generated dylib code refers to the codec by its definition path. The codec must therefore:
29///
30/// * be public through to the crate root, and
31/// * live outside `#[cfg(test)]` and integration-test targets.
32///
33/// Its serialization library and `hydro_lang` with the `sim` feature must be regular
34/// dependencies, not dev-dependencies.
35///
36/// ```
37/// use hydro_lang::sim::codec::SimCodec;
38///
39/// pub struct Message(u32);
40///
41/// pub struct MessageCodec;
42///
43/// impl SimCodec<Message> for MessageCodec {
44///     fn encode(value: &Message) -> Vec<u8> {
45///         value.0.to_le_bytes().to_vec()
46///     }
47///
48///     fn decode(bytes: &[u8]) -> Message {
49///         Message(u32::from_le_bytes(bytes.try_into().unwrap()))
50///     }
51/// }
52/// ```
53pub trait SimCodec<T> {
54    /// Encodes `value` for transport across the simulation dylib boundary.
55    fn encode(value: &T) -> Vec<u8>;
56
57    /// Decodes a value received across the simulation dylib boundary.
58    fn decode(bytes: &[u8]) -> T;
59}
60
61/// The default simulation codec, using [`bincode`].
62#[derive(Clone, Copy, Debug, Default)]
63pub struct BincodeCodec;
64
65impl<T: Serialize + DeserializeOwned> SimCodec<T> for BincodeCodec {
66    fn encode(value: &T) -> Vec<u8> {
67        bincode::serialize(value).unwrap()
68    }
69
70    fn decode(bytes: &[u8]) -> T {
71        bincode::deserialize(bytes).unwrap()
72    }
73}
74
75#[cfg(stageleft_runtime)]
76pub(crate) fn staged_serialize<T, C: SimCodec<T>>() -> syn::Expr {
77    let root = get_this_crate();
78    let t_type = quote_type::<T>();
79    let codec_type = quote_type::<C>();
80
81    parse_quote! {
82        #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<#t_type, _>(
83            |data| {
84                #root::runtime_support::dfir_rs::bytes::Bytes::from(
85                    <#codec_type as #root::__staged::sim::codec::SimCodec<#t_type>>::encode(&data)
86                )
87            }
88        )
89    }
90}
91
92#[cfg(stageleft_runtime)]
93pub(crate) fn staged_deserialize<T, C: SimCodec<T>>() -> syn::Expr {
94    let root = get_this_crate();
95    let t_type = quote_type::<T>();
96    let codec_type = quote_type::<C>();
97
98    parse_quote! {
99        |res| {
100            let bytes = res.unwrap();
101            <#codec_type as #root::__staged::sim::codec::SimCodec<#t_type>>::decode(&bytes)
102        }
103    }
104}