use cyclone_runtime::{from_bytes, to_bytes, Decode, DecodeError, Encode, Reader, Writer};
#[derive(Debug, PartialEq)]
struct Vector3 {
x: f32,
y: f32,
z: f32,
}
impl Encode for Vector3 {
fn encode(&self, writer: &mut Writer) {
writer.write_f32(self.x);
writer.write_f32(self.y);
writer.write_f32(self.z);
}
}
impl Decode for Vector3 {
fn decode(reader: &mut Reader<'_>) -> Result<Self, DecodeError> {
Ok(Vector3 {
x: reader.read_f32()?,
y: reader.read_f32()?,
z: reader.read_f32()?,
})
}
}
#[derive(Debug, PartialEq)]
struct GameMessage {
player_id: u32,
player_name: String,
position: Vector3,
health: u32,
is_alive: bool,
}
impl Encode for GameMessage {
fn encode(&self, writer: &mut Writer) {
writer.write_u32(self.player_id);
writer.write_string(&self.player_name);
self.position.encode(writer);
writer.write_u32(self.health);
writer.write_bool(self.is_alive);
}
}
impl Decode for GameMessage {
fn decode(reader: &mut Reader<'_>) -> Result<Self, DecodeError> {
Ok(GameMessage {
player_id: reader.read_u32()?,
player_name: reader.read_string()?,
position: Vector3::decode(reader)?,
health: reader.read_u32()?,
is_alive: reader.read_bool()?,
})
}
}
fn sample() -> GameMessage {
GameMessage {
player_id: 42,
player_name: "Knight".to_owned(),
position: Vector3 { x: 10.5, y: 20.3, z: -5.1 },
health: 100,
is_alive: true,
}
}
const SAMPLE_BYTES: &[u8] = &[
0x2A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4B, 0x6E, 0x69, 0x67, 0x68, 0x74, 0x00, 0x00, 0x28, 0x41, 0x66, 0x66, 0xA2, 0x41, 0x33, 0x33, 0xA3, 0xC0, 0x64, 0x00, 0x00, 0x00, 0x01,
];
#[test]
fn to_bytes_matches_the_specification() {
assert_eq!(to_bytes(&sample()), SAMPLE_BYTES);
}
#[test]
fn from_bytes_recovers_the_value() {
assert_eq!(from_bytes::<GameMessage>(SAMPLE_BYTES).expect("decode"), sample());
}
#[test]
fn round_trip_through_the_helpers() {
let bytes = to_bytes(&sample());
let value: GameMessage = from_bytes(&bytes).expect("decode");
assert_eq!(value, sample());
assert_eq!(to_bytes(&value), bytes);
}
#[test]
fn nested_model_is_inlined() {
let position = Vector3 { x: 1.5, y: 2.5, z: 3.5 };
let mut through_trait = Writer::new();
position.encode(&mut through_trait);
let mut by_hand = Writer::new();
by_hand.write_f32(1.5);
by_hand.write_f32(2.5);
by_hand.write_f32(3.5);
assert_eq!(through_trait.as_slice(), by_hand.as_slice());
assert_eq!(through_trait.len(), 12);
}
#[test]
fn from_bytes_propagates_decode_errors() {
assert_eq!(
from_bytes::<Vector3>(&[0x00, 0x00, 0x00]),
Err(DecodeError::UnexpectedEof { needed: 4, remaining: 3 })
);
}
#[test]
fn from_bytes_ignores_trailing_bytes() {
let mut bytes = SAMPLE_BYTES.to_vec();
bytes.extend_from_slice(&[0xAA, 0xBB]);
assert_eq!(from_bytes::<GameMessage>(&bytes).expect("decode"), sample());
let mut reader = Reader::new(&bytes);
GameMessage::decode(&mut reader).expect("decode");
assert_eq!(reader.remaining(), 2);
}
#[test]
fn to_bytes_adds_nothing() {
let value = sample();
let mut writer = Writer::new();
value.encode(&mut writer);
assert_eq!(to_bytes(&value), writer.into_bytes());
assert_eq!(to_bytes(&value), to_bytes(&value));
}