perf(server): budget per-tick chunk delivery

This commit is contained in:
Serkyo 2026-08-02 22:58:31 +02:00
parent d33fb518fa
commit e6c55cfd6a
2 changed files with 12 additions and 5 deletions

View file

@ -18,6 +18,10 @@ pub const SERVER_MAX_RADIUS: u16 = 12;
/// Worldgen version stamped on delivered chunk diffs. A single version exists today; this becomes the chunk's stored version once worldgen versioning lands. /// Worldgen version stamped on delivered chunk diffs. A single version exists today; this becomes the chunk's stored version once worldgen versioning lands.
const WORLDGEN_VERSION: u32 = 0; const WORLDGEN_VERSION: u32 = 0;
/// Upper bound on chunks encoded and sent to one client per tick.
// TODO: replace the fixed count with a time budget once per-chunk cost varies with LOD.
const MAX_DELIVERIES_PER_TICK: usize = 32;
/// The load and drop lists produced by diffing a client's previous desired set against a new one. /// The load and drop lists produced by diffing a client's previous desired set against a new one.
#[derive(Debug, Default, PartialEq, Eq)] #[derive(Debug, Default, PartialEq, Eq)]
pub struct DesiredDiff { pub struct DesiredDiff {
@ -94,7 +98,7 @@ impl ClientStream {
/// Delivers every desired-but-undelivered chunk that has become resident in `world`. /// Delivers every desired-but-undelivered chunk that has become resident in `world`.
/// ///
/// Each chunk is encoded as a [`ChunkData`] diff against `baseline` (an all-air chunk), making the payload self-contained. Positions still pending in the worker pool are skipped and retried on a later call. Returns the number of chunks delivered. /// Each chunk is encoded as a [`ChunkData`] diff against `baseline` (an all-air chunk), making the payload self-contained. Positions still pending in the worker pool are skipped and retried on a later call, as are positions beyond [`MAX_DELIVERIES_PER_TICK`]. Returns the number of chunks delivered.
pub fn flush(&mut self, world: &ServerWorld, baseline: &Chunk) -> usize { pub fn flush(&mut self, world: &ServerWorld, baseline: &Chunk) -> usize {
// Collected first to avoid borrowing `self.desired` while mutating `self.sent`. // Collected first to avoid borrowing `self.desired` while mutating `self.sent`.
let ready: Vec<ChunkPos> = self let ready: Vec<ChunkPos> = self
@ -106,6 +110,11 @@ impl ClientStream {
let mut delivered = 0; let mut delivered = 0;
for pos in ready { for pos in ready {
// The budget counts chunks actually encoded, so ticks where most of the desired set is still in flight are not charged for work they did not do.
if delivered >= MAX_DELIVERIES_PER_TICK {
break;
}
let Some(chunk) = world.chunk(pos) else { let Some(chunk) = world.chunk(pos) else {
// Not resident yet; a later flush retries once the worker pool returns it. // Not resident yet; a later flush retries once the worker pool returns it.
continue; continue;

View file

@ -225,10 +225,8 @@ fn run_simulation(world: &mut World, network: &NetworkServer) -> ! {
ServerEvent::ChunkSubscribe { id, request } => { ServerEvent::ChunkSubscribe { id, request } => {
if let Some(client) = clients.get_mut(&id) { if let Some(client) = clients.get_mut(&id) {
let (added, drops) = client.resubscribe(request.center, request.radius); let (added, drops) = client.resubscribe(request.center, request.radius);
info!( // Fires on every chunk boundary the client crosses, so it sits below the connect and disconnect events rather than beside them.
id, debug!(id, added, drops, "client resubscribed");
added, drops, "client {id}: +{added} chunks, -{drops} drops"
);
} else { } else {
warn!(id, "chunk subscribe from unknown session"); warn!(id, "chunk subscribe from unknown session");
} }