Skip to main content

flatland_protocol/
codec.rs

1use serde::{de::DeserializeOwned, Serialize};
2
3use crate::Envelope;
4
5#[derive(Debug, thiserror::Error)]
6pub enum CodecError {
7    #[error("postcard encode failed: {0}")]
8    PostcardEncode(postcard::Error),
9    #[error("postcard decode failed: {0}")]
10    PostcardDecode(postcard::Error),
11    #[error("bincode encode failed: {0}")]
12    BincodeEncode(bincode::Error),
13    #[error("bincode decode failed: {0}")]
14    BincodeDecode(bincode::Error),
15}
16
17pub trait Codec {
18    fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, CodecError>;
19    fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, CodecError>;
20}
21
22/// Default on-wire codec for game traffic (compact, serde-native).
23#[derive(Debug, Clone, Copy, Default)]
24pub struct PostcardCodec;
25
26impl Codec for PostcardCodec {
27    fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, CodecError> {
28        postcard::to_allocvec(value).map_err(CodecError::PostcardEncode)
29    }
30
31    fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, CodecError> {
32        postcard::from_bytes(bytes).map_err(CodecError::PostcardDecode)
33    }
34}
35
36/// Alternative codec used in benchmarks and for comparison.
37#[derive(Debug, Clone, Copy, Default)]
38pub struct BincodeCodec;
39
40impl Codec for BincodeCodec {
41    fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, CodecError> {
42        bincode::serialize(value).map_err(CodecError::BincodeEncode)
43    }
44
45    fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, CodecError> {
46        bincode::deserialize(bytes).map_err(CodecError::BincodeDecode)
47    }
48}
49
50pub fn encode_envelope<T: Serialize>(codec: &impl Codec, payload: T) -> Result<Vec<u8>, CodecError> {
51    codec.encode(&Envelope::new(payload))
52}
53
54pub fn decode_envelope<T: DeserializeOwned>(
55    codec: &impl Codec,
56    bytes: &[u8],
57) -> Result<Envelope<T>, CodecError> {
58    codec.decode(bytes)
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::types::{EntityState, TickDelta, Transform, WorldCoord};
65
66    fn sample_tick_delta() -> TickDelta {
67        TickDelta {
68            tick: 42,
69            entities: (0..15)
70                .map(|i| EntityState {
71                    id: i + 1,
72                    transform: Transform {
73                        position: WorldCoord::surface(i as f32, i as f32 * 0.5),
74                        yaw: 1.25,
75                        velocity: crate::types::Velocity2D { vx: 0.5, vy: -0.2 },
76                    },
77                })
78                .collect(),
79        }
80    }
81
82    #[test]
83    fn postcard_roundtrip_tick_delta() {
84        let codec = PostcardCodec;
85        let original = Envelope::new(sample_tick_delta());
86        let bytes = codec.encode(&original).expect("encode");
87        let decoded: Envelope<TickDelta> = codec.decode(&bytes).expect("decode");
88        assert_eq!(original, decoded);
89    }
90
91    #[test]
92    fn bincode_roundtrip_tick_delta() {
93        let codec = BincodeCodec;
94        let original = Envelope::new(sample_tick_delta());
95        let bytes = codec.encode(&original).expect("encode");
96        let decoded: Envelope<TickDelta> = codec.decode(&bytes).expect("decode");
97        assert_eq!(original, decoded);
98    }
99}