feat(server): gate startup on initial region streaming

This commit is contained in:
Serkyo 2026-07-07 22:29:50 +02:00
parent 19fdec44d1
commit ca39d36a81
2 changed files with 31 additions and 0 deletions

View file

@ -86,6 +86,25 @@ fn main() {
let mut schedule = Schedule::default(); let mut schedule = Schedule::default();
schedule.add_systems(stream_chunks); schedule.add_systems(stream_chunks);
// Loading phase: dispatch the initial region and wait for the worker pool to finish before granting control.
info!("Streaming initial region");
loop {
schedule.run(&mut world);
let server_world = world.resource::<ServerWorld>();
let resident = server_world.loaded_count();
let in_flight = server_world.in_flight_count();
// Loading progress is simply the resident fraction of all known chunks.
let total = resident + in_flight;
debug!(resident, in_flight, total, "loading progress");
// The region is ready once at least one chunk has been generated and none remain in flight.
if server_world.streaming_idle() && resident > 0 {
break;
}
}
info!("Initial region ready; granting player control");
// Drive several ticks, marching the dummy one chunk along +X between each. The manual movement stands in for network-driven player input and exists only to exercise load/unload as the anchor moves. // Drive several ticks, marching the dummy one chunk along +X between each. The manual movement stands in for network-driven player input and exists only to exercise load/unload as the anchor moves.
for step in 0..5 { for step in 0..5 {
info!(step, "tick"); info!(step, "tick");

View file

@ -104,6 +104,18 @@ impl ServerWorld {
self.chunks.len() self.chunks.len()
} }
/// Number of chunks dispatched to the worker pool but not yet returned.
#[must_use]
pub fn in_flight_count(&self) -> usize {
self.in_flight.len()
}
/// Returns `true` when the worker pool has no outstanding generation work, i.e. every dispatched chunk has been returned. A loading gate can poll this to decide when an initial region has finished streaming.
#[must_use]
pub fn streaming_idle(&self) -> bool {
self.in_flight.is_empty()
}
/// Reconciles resident chunks against a desired set without blocking the caller: finished chunks are drained from the worker pool, resident chunks absent from `desired` are evicted, and still-missing chunks are dispatched to the pool. /// Reconciles resident chunks against a desired set without blocking the caller: finished chunks are drained from the worker pool, resident chunks absent from `desired` are evicted, and still-missing chunks are dispatched to the pool.
pub fn reconcile(&mut self, desired: &HashSet<ChunkPos>) -> StreamStats { pub fn reconcile(&mut self, desired: &HashSet<ChunkPos>) -> StreamStats {
// Drain: absorb every chunk the workers have finished since the last pass. // Drain: absorb every chunk the workers have finished since the last pass.