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: [`Stream::sim_output_with`] and
19/// [`Location::sim_input_with`] use the codec value only to infer its type. Codecs should
20/// therefore be unit structs; any state in the value is ignored.
21///
22/// [`Stream::sim_output_with`]: crate::prelude::Stream::sim_output_with
23/// [`Location::sim_input_with`]: crate::location::Location::sim_input_with
24///
25/// # Defining a custom codec
26///
27/// Generated dylib code refers to the codec by its definition path. The codec must therefore:
28///
29/// * be public through to the crate root, and
30/// * live outside `#[cfg(test)]` and integration-test targets.
31///
32/// Its serialization library and `hydro_lang` with the `sim` feature must be regular
33/// dependencies, not dev-dependencies.
34///
35/// ```
36/// use hydro_lang::sim::codec::SimCodec;
37///
38/// pub struct Message(u32);
39///
40/// pub struct MessageCodec;
41///
42/// impl SimCodec<Message> for MessageCodec {
43/// fn encode(value: &Message) -> Vec<u8> {
44/// value.0.to_le_bytes().to_vec()
45/// }
46///
47/// fn decode(bytes: &[u8]) -> Message {
48/// Message(u32::from_le_bytes(bytes.try_into().unwrap()))
49/// }
50/// }
51/// ```
52pub trait SimCodec<T> {
53 /// Encodes `value` for transport across the simulation dylib boundary.
54 fn encode(value: &T) -> Vec<u8>;
55
56 /// Decodes a value received across the simulation dylib boundary.
57 fn decode(bytes: &[u8]) -> T;
58}
59
60/// The default simulation codec, using [`bincode`].
61#[derive(Clone, Copy, Debug, Default)]
62pub struct BincodeCodec;
63
64impl<T: Serialize + DeserializeOwned> SimCodec<T> for BincodeCodec {
65 fn encode(value: &T) -> Vec<u8> {
66 bincode::serialize(value).unwrap()
67 }
68
69 fn decode(bytes: &[u8]) -> T {
70 bincode::deserialize(bytes).unwrap()
71 }
72}
73
74#[cfg(stageleft_runtime)]
75pub(crate) fn staged_serialize<T, C: SimCodec<T>>() -> syn::Expr {
76 let root = get_this_crate();
77 let t_type = quote_type::<T>();
78 let codec_type = quote_type::<C>();
79
80 parse_quote! {
81 #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<#t_type, _>(
82 |data| {
83 #root::runtime_support::dfir_rs::bytes::Bytes::from(
84 <#codec_type as #root::__staged::sim::codec::SimCodec<#t_type>>::encode(&data)
85 )
86 }
87 )
88 }
89}
90
91#[cfg(stageleft_runtime)]
92pub(crate) fn staged_deserialize<T, C: SimCodec<T>>() -> syn::Expr {
93 let root = get_this_crate();
94 let t_type = quote_type::<T>();
95 let codec_type = quote_type::<C>();
96
97 parse_quote! {
98 |res| {
99 let bytes = res.unwrap();
100 <#codec_type as #root::__staged::sim::codec::SimCodec<#t_type>>::decode(&bytes)
101 }
102 }
103}