74 lines
2.5 KiB
Rust
74 lines
2.5 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
//! Authoritative chunk storage and generation logic for the server.
|
|
|
|
use rayon::prelude::*;
|
|
use shared::{
|
|
generator::VoxelGenerator,
|
|
world::{Chunk, ChunkPos, EntityPos},
|
|
};
|
|
use std::collections::HashMap;
|
|
|
|
/// The server's authoritative representation of the world.
|
|
pub struct ServerWorld {
|
|
generator: VoxelGenerator,
|
|
chunks: HashMap<ChunkPos, Chunk>,
|
|
}
|
|
|
|
impl ServerWorld {
|
|
/// Initializes a new authoritative server world with the provided generator.
|
|
#[must_use]
|
|
pub fn new(generator: VoxelGenerator) -> Self {
|
|
Self {
|
|
generator,
|
|
chunks: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Retrieves a reference to the chunk at the given position, generating it if necessary.
|
|
pub fn get_chunk(&mut self, pos: ChunkPos) -> &Chunk {
|
|
self.chunks
|
|
.entry(pos)
|
|
.or_insert_with(|| self.generator.generate_chunk(pos))
|
|
}
|
|
|
|
/// Generates and caches all chunks in a cylindrical region around the given position.
|
|
pub fn load_around(&mut self, center: EntityPos, radius: i32) {
|
|
// The chunk anchor is the exact streaming key; no lossy world-to-chunk conversion is needed.
|
|
let center = center.chunk;
|
|
|
|
// Phase 1: collect the positions inside the cylinder that are not yet resident
|
|
let mut pending = Vec::new();
|
|
for x in center.x - radius..=center.x + radius {
|
|
for z in center.z - radius..=center.z + radius {
|
|
for y in center.y - radius / 2..=center.y + radius / 2 {
|
|
let dx = x - center.x;
|
|
let dz = z - center.z;
|
|
|
|
if (dx * dx + dz * dz) <= radius * radius {
|
|
let pos = ChunkPos::new(x, y, z);
|
|
if !self.chunks.contains_key(&pos) {
|
|
pending.push(pos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Phase 2: generate the missing chunks in parallel
|
|
let generator = &self.generator;
|
|
let generated: Vec<(ChunkPos, Chunk)> = pending
|
|
.into_par_iter()
|
|
.map(|pos| (pos, generator.generate_chunk(pos)))
|
|
.collect();
|
|
|
|
// Phase 3: move the finished chunks into the map
|
|
let count = generated.len();
|
|
for (pos, chunk) in generated {
|
|
self.chunks.insert(pos, chunk);
|
|
}
|
|
|
|
tracing::info!(chunks = count, center = ?center, radius, "generated chunk cylinder");
|
|
}
|
|
}
|