feat(shared): add chunk subscribe and delivery message types

This commit is contained in:
Serkyo 2026-07-21 21:57:55 +02:00
parent ce79449a6c
commit b3f10d8f12
3 changed files with 91 additions and 1 deletions

View file

@ -2,8 +2,9 @@
//! Network protocol types and constants.
//!
//! The module is split by stream purpose, starting with the [`control`]-stream handshake and disconnect messages. Control-stream types are re-exported here so callers continue to refer to `shared::protocol::<Type>` regardless of the internal layout.
//! The module is split by stream purpose: [`control`]-stream handshake and disconnect messages, and the [`chunk`]-sync request/delivery messages. Control-stream types are re-exported here so callers continue to refer to `shared::protocol::<Type>` regardless of the internal layout, while the chunk types stay namespaced under `shared::protocol::chunk` to keep the two protocols visually distinct.
pub mod chunk;
mod control;
pub use control::{

View file

@ -0,0 +1,38 @@
// SPDX-License-Identifier: AGPL-3.0-only
//! Chunk-sync message types carried between server and client.
use crate::world::{ChunkData, ChunkPos};
use serde::{Deserialize, Serialize};
/// A client's request for the chunks it wants resident, expressed as a center and radius.
// TODO: per-chunk request/ack + flow control for the fuller protocol.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChunkSubscribe {
/// Chunk-space center the client wants chunks around (derived from its camera/player).
pub center: ChunkPos,
/// Load radius in chunks. The server clamps this to a server-side maximum.
pub radius: u16,
}
/// One chunk delivered to the client.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ChunkMessage {
/// Full chunk payload at a position.
// TODO: LOD tier selection so a chunk can be delivered at a coarser stream tier.
Chunk {
/// Position of the delivered chunk.
pos: ChunkPos,
/// Serializable chunk contents (reuses the save/diff representation).
data: ChunkData,
},
/// The server has dropped this chunk from the client's set; the client should unload it. This is the server-authoritative counterpart to the client's own radius-based unload: the server can force a discard even when the chunk is still within the client's radius.
Drop {
/// Position the client should discard.
pos: ChunkPos,
},
}
#[cfg(test)]
#[path = "../tests/protocol_chunk.rs"]
mod tests;

View file

@ -0,0 +1,51 @@
// SPDX-License-Identifier: AGPL-3.0-only
use super::*;
use crate::world::BlockId;
/// Round-trips a `ChunkSubscribe` through postcard and asserts the decoded value is identical.
fn roundtrip_subscribe(msg: &ChunkSubscribe) -> Result<(), postcard::Error> {
let bytes = postcard::to_stdvec(msg)?;
let decoded: ChunkSubscribe = postcard::from_bytes(&bytes)?;
assert_eq!(msg, &decoded);
Ok(())
}
/// Round-trips a `ChunkMessage` through postcard and asserts the decoded value is identical.
fn roundtrip_message(msg: &ChunkMessage) -> Result<(), postcard::Error> {
let bytes = postcard::to_stdvec(msg)?;
let decoded: ChunkMessage = postcard::from_bytes(&bytes)?;
assert_eq!(msg, &decoded);
Ok(())
}
#[test]
fn roundtrip_chunk_subscribe() -> Result<(), postcard::Error> {
// Negative coordinates exercise the signed ChunkPos fields under serialization.
let msg = ChunkSubscribe {
center: ChunkPos::new(-4, 0, 7),
radius: 12,
};
roundtrip_subscribe(&msg)
}
#[test]
fn roundtrip_chunk_message_chunk() -> Result<(), postcard::Error> {
// A non-empty ChunkData: private fields force construction via new + set.
let mut data = ChunkData::new(ChunkPos::new(1, 2, 3), 0);
data.set(0, BlockId(1));
data.set(42, BlockId(1));
let msg = ChunkMessage::Chunk {
pos: ChunkPos::new(1, 2, 3),
data,
};
roundtrip_message(&msg)
}
#[test]
fn roundtrip_chunk_message_drop() -> Result<(), postcard::Error> {
let msg = ChunkMessage::Drop {
pos: ChunkPos::new(0, -1, 0),
};
roundtrip_message(&msg)
}