Synvael/crates/shared/src/world/chunk_data.rs

94 lines
3 KiB
Rust

// SPDX-License-Identifier: AGPL-3.0-only
//! Sparse, baseline-relative chunk storage.
use super::{BlockId, Chunk, ChunkPos};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// A modified chunk stored as a sparse set of edits layered on top of a deterministic worldgen baseline.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChunkData {
/// The position of the chunk, identifying which baseline the edits are relative to.
pos: ChunkPos,
/// The worldgen version the baseline is pinned to, so the same baseline is reproduced across generator updates.
worldgen_version: u32,
/// The sparse edit set, mapping a local voxel index (`0..CHUNK_VOLUME`) to the block placed there.
edits: BTreeMap<u32, BlockId>,
}
impl ChunkData {
/// Initializes an unmodified chunk relative to the baseline at `pos` and `worldgen_version`.
#[must_use]
pub fn new(pos: ChunkPos, worldgen_version: u32) -> Self {
Self {
pos,
worldgen_version,
edits: BTreeMap::new(),
}
}
/// Returns the position identifying which baseline the edits are relative to.
#[must_use]
pub fn pos(&self) -> ChunkPos {
self.pos
}
/// Returns the worldgen version the baseline is pinned to.
#[must_use]
pub fn worldgen_version(&self) -> u32 {
self.worldgen_version
}
/// Records `block` at the voxel `local_index`, overwriting any prior edit there.
pub fn set(&mut self, local_index: u32, block: BlockId) {
self.edits.insert(local_index, block);
}
/// Returns `true` when no edits are stored, i.e. the chunk matches its baseline and may be omitted from persistence.
#[must_use]
pub fn is_unmodified(&self) -> bool {
self.edits.is_empty()
}
/// Reconstructs the dense [`Chunk`] by applying the stored edits on top of `baseline`.
#[must_use]
pub fn materialize(&self, baseline: &Chunk) -> Chunk {
let mut chunk = baseline.clone();
for (&local_index, &block) in &self.edits {
chunk.blocks[local_index as usize] = block;
}
chunk
}
/// Computes the sparse delta of `current` against `baseline`, storing only the voxels that differ.
#[must_use]
pub fn from_diff(
pos: ChunkPos,
worldgen_version: u32,
baseline: &Chunk,
current: &Chunk,
) -> ChunkData {
let mut data = ChunkData::new(pos, worldgen_version);
for (i, (&base, &cur)) in baseline
.blocks
.iter()
.zip(current.blocks.iter())
.enumerate()
{
if base != cur {
#[expect(
clippy::cast_possible_truncation,
reason = "i ranges over 0..CHUNK_VOLUME, which fits in u32"
)]
data.set(i as u32, cur);
}
}
data
}
}
#[cfg(test)]
#[path = "../tests/chunk_data.rs"]
mod tests;