use serde::{Deserialize, Serialize};
use super::MapFrame;
pub const MAP_SNAPSHOT_FORMAT: u16 = 2;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct MapSnapshot {
pub players: Vec<MapPlayer>,
pub altars: Vec<MapAltar>,
pub markers: Vec<MapPoint>,
pub frame: Option<MapFrame>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MapPlayer {
pub key: crate::PlayerKey,
pub name: String,
pub position: Option<[f64; 3]>,
pub waypoint: Option<[f64; 3]>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MapAltar {
pub position: [f64; 3],
pub level: u8,
pub zone: [[i64; 3]; 2],
pub base: Option<u32>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MapPoint {
pub position: [f64; 3],
pub marker_type: Option<u32>,
pub label: Option<u32>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum SnapshotError {
#[error("snapshot format {0}, this reader reads {MAP_SNAPSHOT_FORMAT}")]
Format(u16),
#[error("malformed snapshot")]
Malformed,
#[error("the snapshot does not encode")]
Encode,
}
impl MapSnapshot {
pub fn to_bytes(&self) -> Result<Vec<u8>, SnapshotError> {
let bytes = MAP_SNAPSHOT_FORMAT.to_le_bytes().to_vec();
postcard::to_extend(self, bytes).map_err(|_| SnapshotError::Encode)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SnapshotError> {
let (format, body) = bytes
.split_first_chunk::<2>()
.ok_or(SnapshotError::Malformed)?;
let format = u16::from_le_bytes(*format);
if format != MAP_SNAPSHOT_FORMAT {
return Err(SnapshotError::Format(format));
}
match postcard::take_from_bytes(body) {
Ok((snapshot, [])) => Ok(snapshot),
_ => Err(SnapshotError::Malformed),
}
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
fn snapshot() -> impl Strategy<Value = MapSnapshot> {
let position = prop::array::uniform3(-1.0e5..1.0e5f64);
let player = (
any::<u64>(),
".{0,64}",
prop::option::of(position.clone()),
prop::option::of(position.clone()),
)
.prop_map(|(key, name, position, waypoint)| MapPlayer {
key: crate::PlayerKey::new(key),
name,
position,
waypoint,
});
let altar = (
position.clone(),
any::<u8>(),
any::<[[i64; 3]; 2]>(),
any::<Option<u32>>(),
)
.prop_map(|(position, level, zone, base)| MapAltar {
position,
level,
zone,
base,
});
let point = (position, any::<Option<u32>>(), any::<Option<u32>>()).prop_map(
|(position, marker_type, label)| MapPoint {
position,
marker_type,
label,
},
);
let frame = (
prop::array::uniform2(-1.0e5..1.0e5f64),
prop::array::uniform2(1.0..1.0e5f64),
1.0..64.0f64,
)
.prop_map(|(origin, size, metres_per_cell)| MapFrame {
origin,
size,
metres_per_cell,
});
(
prop::collection::vec(player, 0..8),
prop::collection::vec(altar, 0..8),
prop::collection::vec(point, 0..32),
prop::option::of(frame),
)
.prop_map(|(players, altars, markers, frame)| MapSnapshot {
players,
altars,
markers,
frame,
})
}
proptest! {
#[test]
fn every_snapshot_reads_back_as_written(snapshot in snapshot()) {
let bytes = snapshot.to_bytes().unwrap();
prop_assert_eq!(MapSnapshot::from_bytes(&bytes).unwrap(), snapshot);
}
#[test]
fn a_cut_snapshot_is_malformed(snapshot in snapshot(), cut in 1usize..64) {
let bytes = snapshot.to_bytes().unwrap();
let kept = bytes.len().saturating_sub(cut).max(2);
prop_assume!(kept < bytes.len());
prop_assert_eq!(MapSnapshot::from_bytes(&bytes[..kept]), Err(SnapshotError::Malformed));
}
}
#[test]
fn the_format_is_pinned_byte_for_byte() {
let snapshot = MapSnapshot {
players: vec![MapPlayer {
key: crate::PlayerKey::new(1),
name: "A".to_owned(),
position: Some([0.5, 0.0, -1.0]),
waypoint: None,
}],
altars: vec![MapAltar {
position: [0.0; 3],
level: 3,
zone: [[-1, 0, -1], [1, 2, 1]],
base: Some(0),
}],
markers: vec![MapPoint {
position: [8.0, 0.0, 8.0],
marker_type: Some(5),
label: None,
}],
frame: Some(MapFrame {
origin: [0.0, 0.0],
size: [10240.0, 10240.0],
metres_per_cell: 8.0,
}),
};
let bytes = snapshot.to_bytes().unwrap();
let hex = bytes.iter().fold(String::new(), |mut hex, byte| {
use core::fmt::Write as _;
let _infallible = write!(hex, "{byte:02x}");
hex
});
insta::assert_snapshot!(hex);
}
#[test]
fn trailing_bytes_are_malformed() {
let mut bytes = MapSnapshot::default().to_bytes().unwrap();
bytes.push(0);
assert_eq!(
MapSnapshot::from_bytes(&bytes),
Err(SnapshotError::Malformed)
);
assert_eq!(MapSnapshot::from_bytes(&[1]), Err(SnapshotError::Malformed));
}
}