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}