Added ChunkBlocks abstraction for ChunkData

ChunkBlocks makes it easy & safer to access blocks of a chunk.
It also makes it possible to consistently handle the heightmap updates in 1 place.
This commit is contained in:
DaniD3v 2024-08-25 12:17:58 +02:00
parent 1d1dd2ed6c
commit 52c34caaa6
2 changed files with 97 additions and 20 deletions

View File

@ -16,13 +16,13 @@ impl<'a> ClientPacket for CChunkData<'a> {
buf.put_i32(self.0.position.z); buf.put_i32(self.0.position.z);
let heightmap_nbt = let heightmap_nbt =
fastnbt::to_bytes_with_opts(&self.0.heightmaps, fastnbt::SerOpts::network_nbt()) fastnbt::to_bytes_with_opts(&self.0.blocks.heightmap, fastnbt::SerOpts::network_nbt())
.unwrap(); .unwrap();
// Heightmaps // Heightmaps
buf.put_slice(&heightmap_nbt); buf.put_slice(&heightmap_nbt);
let mut data_buf = ByteBuffer::empty(); let mut data_buf = ByteBuffer::empty();
self.0.blocks.chunks(16 * 16 * 16).for_each(|chunk| { self.0.blocks.iter_subchunks().for_each(|chunk| {
let block_count = chunk let block_count = chunk
.iter() .iter()
.dedup() .dedup()

View File

@ -1,12 +1,13 @@
use std::cmp::max; use std::cmp::max;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Index;
use fastnbt::LongArray; use fastnbt::LongArray;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
block::block_registry::BlockId, block::block_registry::BlockId,
coordinates::{ChunkCoordinates, ChunkRelativeBlockCoordinates}, coordinates::{ChunkCoordinates, ChunkRelativeBlockCoordinates, Height},
level::WorldError, level::WorldError,
WORLD_HEIGHT, WORLD_HEIGHT,
}; };
@ -16,9 +17,19 @@ const SUBCHUNK_VOLUME: usize = CHUNK_AREA * 16;
const CHUNK_VOLUME: usize = CHUNK_AREA * WORLD_HEIGHT; const CHUNK_VOLUME: usize = CHUNK_AREA * WORLD_HEIGHT;
pub struct ChunkData { pub struct ChunkData {
pub blocks: Box<[BlockId; CHUNK_VOLUME]>, pub blocks: ChunkBlocks,
pub position: ChunkCoordinates, pub position: ChunkCoordinates,
pub heightmaps: ChunkHeightmaps, }
pub struct ChunkBlocks {
// TODO make this a Vec that doesn't store the upper layers that only contain air
// The packet relies on this ordering -> leave it like this for performance
/// Ordering: yzx (y being the most significant)
blocks: Box<[BlockId; CHUNK_VOLUME]>,
/// See `https://minecraft.fandom.com/wiki/Heightmap` for more info
pub heightmap: ChunkHeightmaps,
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
@ -59,6 +70,74 @@ struct ChunkNbt {
heightmaps: ChunkHeightmaps, heightmaps: ChunkHeightmaps,
} }
// TODO @DaniD3v implement once Fre is done with heightmaps
// impl Default for ChunkBlocks {
// fn default() -> Self {
// Self {
// blocks: Box::new([BlockId::default(); CHUNK_VOLUME]),
// heightmap: todo!(),
// }
// }
// }
impl ChunkBlocks {
pub fn empty_with_heightmap(heightmap: ChunkHeightmaps) -> Self {
Self {
blocks: Box::new([BlockId::default(); CHUNK_VOLUME]),
heightmap,
}
}
/// Sets the given block in the chunk, returning the old block
pub fn set_block(
&mut self,
position: ChunkRelativeBlockCoordinates,
block: BlockId,
) -> BlockId {
// TODO @LUK_ESC? update the heightmap
self.set_block_no_heightmap_update(position, block)
}
/// Sets the given block in the chunk, returning the old block
/// Contrary to `set_block` this does not update the heightmap.
///
/// Only use this if you know you don't need to update the heightmap
/// or if you manually set the heightmap in `empty_with_heightmap`
pub fn set_block_no_heightmap_update(
&mut self,
position: ChunkRelativeBlockCoordinates,
block: BlockId,
) -> BlockId {
std::mem::replace(&mut self.blocks[Self::convert_index(position)], block)
}
pub fn iter_subchunks(&self) -> impl Iterator<Item = &[BlockId; SUBCHUNK_VOLUME]> {
self.blocks
.chunks(SUBCHUNK_VOLUME)
.map(|subchunk| subchunk.try_into().unwrap())
}
fn convert_index(index: ChunkRelativeBlockCoordinates) -> usize {
// % works for negative numbers as intended.
index.y.get_absolute() as usize * CHUNK_AREA + *index.z as usize * 16 + *index.x as usize
}
#[allow(dead_code)]
fn calculate_heightmap(&self) -> ChunkHeightmaps {
// figure out how LongArray is formatted
// figure out how to find out if block is motion blocking
todo!()
}
}
impl Index<ChunkRelativeBlockCoordinates> for ChunkBlocks {
type Output = BlockId;
fn index(&self, index: ChunkRelativeBlockCoordinates) -> &Self::Output {
&self.blocks[Self::convert_index(index)]
}
}
impl ChunkData { impl ChunkData {
pub fn from_bytes(chunk_data: Vec<u8>, at: ChunkCoordinates) -> Result<Self, WorldError> { pub fn from_bytes(chunk_data: Vec<u8>, at: ChunkCoordinates) -> Result<Self, WorldError> {
let chunk_data = match fastnbt::from_bytes::<ChunkNbt>(chunk_data.as_slice()) { let chunk_data = match fastnbt::from_bytes::<ChunkNbt>(chunk_data.as_slice()) {
@ -67,7 +146,7 @@ impl ChunkData {
}; };
// this needs to be boxed, otherwise it will cause a stack-overflow // this needs to be boxed, otherwise it will cause a stack-overflow
let mut blocks = Box::new([BlockId::default(); CHUNK_VOLUME]); let mut blocks = ChunkBlocks::empty_with_heightmap(chunk_data.heightmaps);
let mut block_index = 0; // which block we're currently at let mut block_index = 0; // which block we're currently at
for section in chunk_data.sections.into_iter() { for section in chunk_data.sections.into_iter() {
@ -107,7 +186,18 @@ impl ChunkData {
let index = (block >> (i * block_bit_size)) & mask; let index = (block >> (i * block_bit_size)) & mask;
let block = palette[index as usize]; let block = palette[index as usize];
blocks[block_index] = block; // TODO allow indexing blocks directly so we can just use block_index and save some time?
// this is fine because we initalized the heightmap of `blocks`
// from the cached value in the world file
blocks.set_block_no_heightmap_update(
ChunkRelativeBlockCoordinates {
z: ((block_index % CHUNK_AREA) / 16).into(),
y: Height::from_absolute((block_index / CHUNK_AREA) as u16),
x: (block_index % 16).into(),
},
block,
);
block_index += 1; block_index += 1;
// if `SUBCHUNK_VOLUME `is not divisible by `blocks_in_pallete` the block_data // if `SUBCHUNK_VOLUME `is not divisible by `blocks_in_pallete` the block_data
@ -122,19 +212,6 @@ impl ChunkData {
Ok(ChunkData { Ok(ChunkData {
blocks, blocks,
position: at, position: at,
heightmaps: chunk_data.heightmaps,
}) })
} }
/// Sets the given block in the chunk, returning the old block
pub fn set_block(
&mut self,
at: ChunkRelativeBlockCoordinates,
block_id: BlockId,
) -> Result<BlockId, WorldError> {
Ok(std::mem::replace(
&mut self.blocks
[at.y.get_absolute() as usize * CHUNK_AREA + (*at.z * 16 + *at.x) as usize],
block_id,
))
}
} }