diff --git a/crates/level/Cargo.toml b/crates/level/Cargo.toml index 3980cc10..34e8ff19 100644 --- a/crates/level/Cargo.toml +++ b/crates/level/Cargo.toml @@ -21,6 +21,7 @@ vek = "0.17" [dev-dependencies] rand = "0.8" copy_dir = "0.1.3" +anyhow = "1.0.95" [[test]] diff --git a/crates/level/src/level/chunk.rs b/crates/level/src/level/chunk.rs deleted file mode 100644 index 6a110c74..00000000 --- a/crates/level/src/level/chunk.rs +++ /dev/null @@ -1,539 +0,0 @@ -use crate::level::level::LevelModificationProvider; -use crate::level::sub_chunk::SubChunkTrait; -use crate::level::world_block::WorldBlockTrait; -use bedrockrs_shared::world::dimension::Dimension; -use std::fmt::Debug; -use thiserror::Error; -use vek::{Vec2, Vec3}; - -/// Specifies the type of filter used when filling a region in the world. -/// -/// # Type Parameters -/// - `UserBlockType`: A block type that implements the `WorldBlockTrait`. -pub enum FillFilter { - /// Fills the entire region unconditionally, overwriting all blocks. - Blanket, - - /// Replaces only the blocks that match the specified type. - /// - /// # Parameters - /// - `UserBlockType`: The block type to be replaced. - Replace(UserBlockType), - - /// Avoids overwriting blocks that match the specified type. - /// - /// # Parameters - /// - `UserBlockType`: The block type to avoid. - Avoid(UserBlockType), - - /// Uses a custom precedence function to determine whether a block should be replaced. - /// - /// # Parameters - /// - A boxed function with the following parameters: - /// - `&UserBlockType`: The current block being evaluated. - /// - `Vec3`: The local coordinates of the block within the current subchunk. - /// - `Vec2`: The world-space XZ coordinates of the chunk. - /// - `i8`: The subchunk y. - /// - /// # Returns - /// - `bool`: `true` to allow replacing the block, `false` to skip it. - Precedence(Box, Vec2, i8) -> bool>), -} - -impl FillFilter { - pub fn may_place(&self, target: &T, position: Vec3, xz: Vec2, y: i8) -> bool { - match self { - FillFilter::Blanket => true, - FillFilter::Replace(v) => v == target, - FillFilter::Avoid(v) => v != target, - FillFilter::Precedence(f) => f(target, position, xz, y), - } - } -} - -#[derive(Error, Debug)] -pub enum FillError { - #[error("Attempted to fill Subchunk {0} and got none back")] - MissingSubchunk(i8), - #[error("Attempted to read block at x: {0}, y {1}, z: {2} and got None")] - BlockIndexDidntReturn(u8, u8, u8), -} - -pub trait LevelChunkTrait: Sized -where - >::UserSubchunk: SubChunkTrait, - >::UserBlock: WorldBlockTrait, - ::UserSubChunkType: SubChunkTrait, - <>::UserSubchunk as SubChunkTrait>::BlockType: - WorldBlockTrait, -{ - type UserLevel; // = UserLevel; - type UserBlock; // = UserLevel::UserBlockType; - type UserSubchunk; // = UserLevel::UserSubChunkType; - type UserState; // = UserLevel::UserState; - type Err; - - /// Loads the chunk from the world based on the specified min and max subchunk indices, - /// XZ coordinates, and dimension. - /// - /// # Parameters - /// - `min_max`: Minimum and maximum subchunk indices. - /// - `xz`: The XZ coordinate of the chunk in the world. - /// - `dim`: The dimension where the chunk resides. - /// - `level`: The level data to load the chunk into. - /// - /// # Returns - /// - `Ok(Self)`: The loaded chunk. - /// - `Err(Self::Err)`: An error occurred during loading. - fn load_from_world( - min_max: Vec2, - xz: Vec2, - dim: Dimension, - level: &mut Self::UserLevel, - ) -> Result; - - /// Writes the chunk back into the world, optionally overriding its position and dimension. - /// - /// # Parameters - /// - `level`: The level data where the chunk will be written. - /// - `xz_override`: Optional override for the chunk's XZ coordinates. - /// - `dim_override`: Optional override for the chunk's dimension. - /// - /// # Returns - /// - `Ok(())`: Successfully written to the world. - /// - `Err(Self::Err)`: An error occurred during writing. - fn write_to_world( - self, - level: &mut Self::UserLevel, - xz_override: Option>, - dim_override: Option, - ) -> Result<(), Self::Err>; - - /// Retrieves a mutable reference to the block at the specified XZ and Y coordinates within the chunk. - /// - /// # Parameters - /// - `xz`: The XZ coordinate of the block within the chunk. - /// - `y`: The Y coordinate of the block. - /// - /// # Returns - /// - `Some(&mut Self::UserBlock)`: A mutable reference to the block if it exists. - /// - `None`: No block exists at the specified coordinates. - fn get_block_at_mut(&mut self, xz: Vec2, y: i16) -> Option<&mut Self::UserBlock>; - - /// Retrieves an immutable reference to the block at the specified XZ and Y coordinates within the chunk. - /// - /// # Parameters - /// - `xz`: The XZ coordinate of the block within the chunk. - /// - `y`: The Y coordinate of the block. - /// - /// # Returns - /// - `Some(&Self::UserBlock)`: An immutable reference to the block if it exists. - /// - `None`: No block exists at the specified coordinates. - fn get_block_at(&self, xz: Vec2, y: i16) -> Option<&Self::UserBlock>; - - /// Sets the block at the specified XZ and Y coordinates within the chunk. - /// - /// # Parameters - /// - `block`: The block to set. - /// - `xz`: The XZ coordinate of the block within the chunk. - /// - `y`: The Y coordinate of the block. - /// - /// # Returns - /// - `Ok(())`: Successfully set the block. - /// - `Err(Self::UserSubchunk::Err)`: An error occurred while setting the block. - fn set_block_at( - &mut self, - block: Self::UserBlock, - xz: Vec2, - y: i16, - ) -> Result<(), ::Err>; - - /// Returns the minimum and maximum subchunk indices for this chunk. - /// - /// # Returns - /// - `Vec2`: A vector containing the minimum and maximum subchunk indices. - fn min_max(&self) -> Vec2; - - /// Retrieves an immutable reference to the subchunk at the specified Y index. - /// - /// # Parameters - /// - `y`: The Y index of the subchunk. - /// - /// # Returns - /// - `Some(&Self::UserSubchunk)`: An immutable reference to the subchunk if it exists. - /// - `None`: No subchunk exists at the specified index. - fn get_subchunk(&self, y: i8) -> Option<&Self::UserSubchunk>; - - /// Retrieves a mutable reference to the subchunk at the specified Y index. - /// - /// # Parameters - /// - `y`: The Y index of the subchunk. - /// - /// # Returns - /// - `Some(&mut Self::UserSubchunk)`: A mutable reference to the subchunk if it exists. - /// - `None`: No subchunk exists at the specified index. - fn get_subchunk_mut(&mut self, y: i8) -> Option<&mut Self::UserSubchunk>; - - /// Gets the position of the chunk in world space as XZ coordinates. - /// - /// # Returns - /// - `Vec2`: The chunk's position in world space. - fn pos(&self) -> Vec2; -} - -#[cfg(feature = "default-impl")] -pub mod default_impl { - use super::*; - use crate::level::file_interface::RawWorldTrait; - use crate::level::level::LevelError; - use crate::level::sub_chunk::{SubChunkDecoder, SubChunkTrait}; - use crate::level::world_block::WorldBlockTrait; - use std::marker::PhantomData; - use std::mem::MaybeUninit; - use std::ops::{Deref, DerefMut}; - use std::slice::{Iter, IterMut}; - use std::vec::Vec; - - #[allow(dead_code)] - pub struct LevelChunk< - UserState, - UserSubChunkType, - UserLevelInterface: LevelModificationProvider, - > { - bounds: Vec2, - xz: Vec2, - dim: Dimension, - sections: Vec, - phantom_data: PhantomData, - _phantom_data: PhantomData, - } - - impl Deref - for LevelChunk - { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.sections - } - } - - impl DerefMut - for LevelChunk - { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.sections - } - } - - impl< - UserState, - UserBlockType: WorldBlockTrait, - UserSubChunkType: SubChunkTrait, - UserLevelInterface: LevelModificationProvider, - > LevelChunk - { - // This function computes the true index of a subchunk in the section array. - // E.g., with a bounds of -4 to 20 subchunks in size if a raw_index of -4 is passed in it will perform - // -4 + 4 equaling 0 meaning the true index of a subchunk at y -4 is 0; this logic extends to any y level - // y 2 would do 2 + 4 meaning the true index of a subchunk at y 2 is 6 - fn true_index(min_max: Vec2, raw_index: i8) -> i8 { - #[cfg(debug_assertions)] - if raw_index < min_max.x || raw_index > min_max.y { - panic!("Y out of bounds"); - } - raw_index + min_max.x.abs() - } - - // The need for this function comes from the fact that when subchunks are in -y the y index is inverted. - // This means that 20 -9 20 is near the bottom of the subchunk were as 20 9 20 is near the top of the subchunk (this is assuming we are using world space coords). - // The Y can be thought more of as an offset from the base. - // The base in this context is 0 if the subchunk is positive y and 16 if the subchunk is negative - fn real_y(raw: i8) -> i8 { - if raw >= 0 { - raw - } else { - 16 + raw - // This looks a little confusing so ill explain. - // raw is a negative number, meaning 16 + raw is basically 16 - abs(raw) - } - } - - /// Returns an iterator over all subchunks in this chunk, ordered from low to high. - /// - /// # Returns - /// - `Iter<'_, UserSubChunkType>`: An iterator over the subchunks. - pub fn iter(&self) -> Iter<'_, UserSubChunkType> { - self.sections.iter() - } - - /// Returns a mutable iterator over all subchunks in this chunk, ordered from low to high. - /// - /// # Returns - /// - `IterMut<'_, UserSubChunkType>`: A mutable iterator over the subchunks. - pub fn iter_mut(&mut self) -> IterMut<'_, UserSubChunkType> { - self.sections.iter_mut() - } - - /// Fetches an immutable reference to the subchunk at the specified Y level. - /// - /// # Parameters - /// - `idx`: The Y level of the desired subchunk. - /// - /// # Returns - /// - `Some(&UserSubChunkType)`: An immutable reference to the subchunk if it exists. - /// - `None`: No subchunk exists at the given Y level. - pub fn get_subchunk(&self, idx: i8) -> Option<&UserSubChunkType> { - self.sections - .get(Self::true_index(self.bounds, idx) as usize) - } - - /// Fetches a mutable reference to the subchunk at the specified Y level. - /// - /// # Parameters - /// - `idx`: The Y level of the desired subchunk. - /// - /// # Returns - /// - `Some(&mut UserSubChunkType)`: A mutable reference to the subchunk if it exists. - /// - `None`: No subchunk exists at the given Y level. - pub fn get_subchunk_mut(&mut self, idx: i8) -> Option<&mut UserSubChunkType> { - self.sections - .get_mut(Self::true_index(self.bounds, idx) as usize) - } - - /// Sets the subchunk at the specified Y level. - /// - /// # Parameters - /// - `y`: The Y level where the subchunk will be set. - /// - `chnk`: The subchunk to set at the specified Y level. - pub fn set_subchunk(&mut self, y: i8, mut chnk: UserSubChunkType) { - chnk.set_y(y); - self[Self::real_y(y) as usize] = chnk; - } - - /// Creates a new chunk filled with empty subchunks. - /// - /// # Parameters - /// - `xz`: The XZ coordinates of the chunk in world space. - /// - `min_max`: The minimum and maximum subchunk indices. - /// - `dim`: The dimension where the chunk resides. - /// - `state`: The state object used to create empty subchunks. - /// - /// # Returns - /// - `Self`: A new chunk with empty subchunks. - pub fn empty( - xz: Vec2, - min_max: Vec2, - dim: Dimension, - state: &mut UserState, - ) -> Self { - let ret_subchunks = (min_max.x..min_max.y) - .map(|y| UserSubChunkType::empty(y, state)) - .collect::>(); - Self { - xz, - bounds: min_max, - sections: ret_subchunks, - dim, - phantom_data: PhantomData, - _phantom_data: PhantomData, - } - } - } - - impl< - UserState, - UserBlockType: WorldBlockTrait, - UserSubChunkType: SubChunkTrait, - UserLevelInterface: LevelModificationProvider< - UserBlockType = UserBlockType, - UserState = UserState, - UserSubChunkType = UserSubChunkType, - Error = LevelError<<::UserWorldInterface as RawWorldTrait>::Err, - <::UserSubChunkDecoder as SubChunkDecoder>::Err, - ::Err,>, - >, - > LevelChunk - where - ::UserWorldInterface: RawWorldTrait, - ::UserSubChunkDecoder: SubChunkDecoder, - ::UserBlockType: WorldBlockTrait, - ::Err: Debug, - <::UserWorldInterface as RawWorldTrait>::Err: Debug, - <::UserSubChunkDecoder as SubChunkDecoder>::Err: Debug, - { - /// Fills the entire chunk with the specified block, applying the given `FillFilter`. - /// - /// # Parameters - /// - `block`: The block to fill the chunk with. - /// - `filter`: The filter defining the conditions for filling blocks. - /// - /// # Returns - /// - `Ok(&mut Self)`: Successfully filled the chunk. - /// - `Err(FillError)`: An error occurred, such as a missing subchunk or invalid block index. - /// - /// # Errors - /// - `FillError::MissingSubchunk(y_level)`: Subchunk at the specified Y level is missing. - /// - `FillError::BlockIndexDidntReturn(x, y, z)`: Block index is invalid or did not return a block. - /// - /// # Behavior - /// Iterates through all blocks in all subchunks, applying the `FillFilter`: - /// - `Blanket`: Fills all blocks unconditionally. - /// - `Replace(mask)`: Replaces only blocks matching the given mask. - /// - `Avoid(mask)`: Fills blocks that do not match the mask. - /// - `Precedence(func)`: Uses a custom function to determine if a block should be replaced. - - pub fn fill_chunk(&mut self, block: UserBlockType, filter: FillFilter) -> Result<&mut Self, FillError> { - let pos = self.pos(); - for y_level in self.bounds.x..self.bounds.y { - let subchunk = self.get_subchunk_mut(y_level) - .ok_or(FillError::MissingSubchunk(y_level))?; - for z in 0..16u8 { - for y in 0..16u8 { - for x in 0..16u8 { - let blk = subchunk - .get_block((x, y, z).into()) - .ok_or(FillError::BlockIndexDidntReturn(x, y, z))?; - if filter.may_place(blk, (x, y, z).into(), pos, y_level) { - subchunk.set_block((x, y, z).into(), block.clone()).unwrap() - } - } - } - } - } - Ok(self) - } - } - impl< - UserState, - UserBlockType: WorldBlockTrait, - UserSubChunkType: SubChunkTrait, - UserLevelInterface: LevelModificationProvider< - UserBlockType = UserBlockType, - UserState = UserState, - UserSubChunkType = UserSubChunkType, - Error = LevelError<<::UserWorldInterface as RawWorldTrait>::Err, - <::UserSubChunkDecoder as SubChunkDecoder>::Err, - ::Err,>, - >, - > LevelChunkTrait for LevelChunk - where - ::UserWorldInterface: RawWorldTrait, - ::UserSubChunkDecoder: SubChunkDecoder, - ::UserBlockType: WorldBlockTrait, - ::Err: Debug, - <::UserWorldInterface as RawWorldTrait>::Err: Debug, - <::UserSubChunkDecoder as SubChunkDecoder>::Err: Debug, - { - type UserLevel = UserLevelInterface; - type UserBlock = UserBlockType; - type UserSubchunk = UserSubChunkType; - type UserState = UserState; - type Err = LevelError< - ::Err, - ::Err, - ::Err, - >; - - fn load_from_world( - min_max: Vec2, - xz: Vec2, - dim: Dimension, - level: &mut Self::UserLevel, - ) -> Result { - let mut subchunk_list: Vec> = (min_max.x..min_max.y) - .map(|_| MaybeUninit::uninit() ) - .collect(); - for y in min_max.x..min_max.y { - let subchunk = level.get_sub_chunk(xz, y, dim); - match subchunk { - Ok(subchunk) => { - let idx = Self::true_index(min_max, y) as usize; - subchunk_list[idx].write(subchunk); - } - Err(err) => { - for r in min_max.x..y { - // Safety: We are only dropping subchunks which came before this one meaning they have to be init - unsafe { - subchunk_list[Self::true_index(min_max, r) as usize] - .assume_init_drop(); - } - } - return Err(err); - } - } - } - // Safety: Since `MaybeUninit` is a ZST the ABI of the two types is the same - Ok(Self { - bounds: min_max, - xz, - dim, - sections: unsafe { std::mem::transmute(subchunk_list) }, - phantom_data: PhantomData, - _phantom_data: PhantomData - }) - } - - fn write_to_world( - self, - level: &mut Self::UserLevel, - xz_override: Option>, - dim_override: Option, - ) -> Result<(), Self::Err> { - for sub_chnk in self.sections { - level.set_subchunk( - xz_override.unwrap_or(self.xz), - sub_chnk.get_y(), - dim_override.unwrap_or(self.dim), - sub_chnk, - )?; - } - Ok(()) - } - - fn get_block_at_mut(&mut self, xz: Vec2, y: i16) -> Option<&mut Self::UserBlock> { - self.sections - .get_mut(Self::true_index(self.bounds, (y / 16) as i8) as usize)? - .get_block_mut((xz.x, Self::real_y((y / 16) as i8) as u8, xz.y).into()) - } - - fn get_block_at(&self, xz: Vec2, y: i16) -> Option<&Self::UserBlock> { - self.sections - .get(Self::true_index(self.bounds, (y / 16) as i8) as usize)? - .get_block((xz.x, Self::real_y((y / 16) as i8) as u8, xz.y).into()) - } - - fn set_block_at( - &mut self, - block: Self::UserBlock, - xz: Vec2, - y: i16, - ) -> Result<(), ::Err> { - self.sections - .get_mut(Self::true_index(self.bounds, (y / 16) as i8) as usize) - .unwrap() /*TODO: Figure out a way to report this error back to the user*/ - .set_block( - (xz.x, Self::real_y((y / 16) as i8) as u8, xz.y).into(), - block, - ) - } - - fn min_max(&self) -> Vec2 { - self.bounds - } - - fn get_subchunk(&self, y: i8) -> Option<&Self::UserSubchunk> { - self.sections.get(Self::true_index(self.bounds, y) as usize) - } - - fn get_subchunk_mut(&mut self, y: i8) -> Option<&mut Self::UserSubchunk> { - self.sections - .get_mut(Self::true_index(self.bounds, y) as usize) - } - - fn pos(&self) -> Vec2 { - self.xz - } - - - } -} diff --git a/crates/level/src/level/chunk_cache.rs b/crates/level/src/level/chunk_cache.rs deleted file mode 100644 index c341cd0f..00000000 --- a/crates/level/src/level/chunk_cache.rs +++ /dev/null @@ -1,27 +0,0 @@ -use bedrockrs_shared::world::dimension::Dimension; -use vek::Vec2; - -#[derive(Debug, PartialEq, Hash, Eq, Clone)] -pub struct SubchunkCacheKey { - pub xz: Vec2, - pub y: i8, - pub dim: Dimension, -} - -#[derive(Debug, PartialEq, Hash, Eq, Clone)] -pub struct ChunkCacheKey { - xz: Vec2, - sequence_id: usize, -} - -impl SubchunkCacheKey { - pub fn new(xz: Vec2, y: i8, dim: Dimension) -> Self { - Self { xz, y, dim } - } -} - -impl ChunkCacheKey { - pub fn new(xz: Vec2, sequence_id: usize) -> Self { - Self { xz, sequence_id } - } -} diff --git a/crates/level/src/level/db_interface/bedrock_key.rs b/crates/level/src/level/db_interface/bedrock_key.rs index 8f157e7a..5f09b031 100644 --- a/crates/level/src/level/db_interface/bedrock_key.rs +++ b/crates/level/src/level/db_interface/bedrock_key.rs @@ -3,23 +3,23 @@ use crate::level::db_interface::key_level::KeyTypeTag; use bedrockrs_shared::world::dimension::Dimension; use byteorder::{LittleEndian, WriteBytesExt}; use std::io::Cursor; -use vek::Vec2; +use vek::{Vec2, Vec3}; #[derive(Debug)] pub struct ChunkKey { - xz: Vec2, - dim: Dimension, - key_type: KeyTypeTag, - y_index: Option, + pub xz: Vec2, + pub dim: Dimension, + pub key_type: KeyTypeTag, + pub y_index: Option, } impl ChunkKey { - pub fn new_subchunk(xz: Vec2, dim: Dimension, y_index: i8) -> Self { + pub fn new_sub_chunk(xyz: Vec3, dim: Dimension) -> Self { Self { - xz, + xz: (xyz.x, xyz.z).into(), dim, key_type: KeyTypeTag::SubChunkPrefix, - y_index: Some(y_index), + y_index: Some(xyz.y as i8), } } diff --git a/crates/level/src/level/db_interface/rusty.rs b/crates/level/src/level/db_interface/rusty.rs index defe29b6..e844e012 100644 --- a/crates/level/src/level/db_interface/rusty.rs +++ b/crates/level/src/level/db_interface/rusty.rs @@ -1,6 +1,7 @@ use crate::level::db_interface::bedrock_key::ChunkKey; use crate::level::db_interface::db::LevelDBKey; use crate::level::db_interface::key_level::KeyTypeTag; +use crate::level::db_interface::key_level::KeyTypeTag::SubChunkPrefix; use crate::level::file_interface::RawWorldTrait; use bedrockrs_shared::world::dimension::Dimension; use byteorder::{LittleEndian, ReadBytesExt}; @@ -10,7 +11,6 @@ use rusty_leveldb::compressor::NoneCompressor; use rusty_leveldb::{Compressor, CompressorList, LdbIterator, Options, Status, WriteBatch, DB}; use std::collections::HashSet; use std::io::Cursor; -use std::marker::PhantomData; use std::path::Path; use std::rc::Rc; use thiserror::Error; @@ -71,9 +71,8 @@ pub fn mcpe_options(compression_level: u8) -> Options { } const COMPRESSION_LEVEL: u8 = CompressionLevel::DefaultLevel as u8; -pub struct RustyDBInterface { - db: DB, - phantom_data: PhantomData, +pub struct RustyDBInterface { + pub db: DB, } #[derive(Debug, Error)] @@ -82,9 +81,9 @@ pub enum DBError { DatabaseError(#[from] Status), } -impl RustyDBInterface { - fn build_key_batch(subchunk_batch_info: Vec, data: &mut Vec) -> WriteBatch { - let count = subchunk_batch_info +impl RustyDBInterface { + fn build_key_batch(sub_chunk_batch_info: Vec, data: &mut Vec) -> WriteBatch { + let count = sub_chunk_batch_info .iter() .map(|ele| ele.estimate_size()) .sum(); @@ -94,7 +93,7 @@ impl RustyDBInterface { let mut batch = WriteBatch::default(); - for key in subchunk_batch_info { + for key in sub_chunk_batch_info { let start = buff.position(); key.write_key(&mut buff); @@ -113,58 +112,76 @@ impl RustyDBInterface { } } -impl Drop for RustyDBInterface { +impl Drop for RustyDBInterface { fn drop(&mut self) { self.db.close().unwrap(); } } -impl RawWorldTrait for RustyDBInterface { +impl RawWorldTrait for RustyDBInterface { type Err = DBError; - type UserState = UserState; + type ConstructionInformation = (); + + fn open( + path: Box, + create_if_missing: bool, + _: &mut Self::ConstructionInformation, + ) -> Result { + let mut opts = mcpe_options(COMPRESSION_LEVEL); + opts.create_if_missing = create_if_missing; + let db = DB::open(path, opts)?; + Ok(Self { db }) + } + + fn close(&mut self) -> Result<(), Self::Err> { + self.db.close()?; + Ok(()) + } + + fn flush(&mut self) -> Result<(), Self::Err> { + self.db.flush().map_err(|ele| Self::Err::DatabaseError(ele)) + } fn write_bytes_to_key( &mut self, chunk_info: ChunkKey, chunk_bytes: &[u8], - _: &mut Self::UserState, ) -> Result<(), Self::Err> { let mut batch = WriteBatch::default(); batch.put(&Self::build_key(&chunk_info), chunk_bytes); Ok(self.db.write(batch, false)?) } - fn get_bytes_from_key( - &mut self, - chunk_info: ChunkKey, - _: &mut Self::UserState, - ) -> Result>, Self::Err> { + fn get_bytes_from_key(&mut self, chunk_info: ChunkKey) -> Result>, Self::Err> { Ok(self.db.get(&Self::build_key(&chunk_info))) } - fn delete_bytes_at_key( + fn delete_bytes_at_key(&mut self, chunk_info: ChunkKey) -> Result<(), Self::Err> { + Ok(self.db.delete(&Self::build_key(&chunk_info))?) + } + + fn set_sub_chunk_raw( &mut self, chunk_info: ChunkKey, - _: &mut Self::UserState, + chunk_bytes: &[u8], ) -> Result<(), Self::Err> { - Ok(self.db.delete(&Self::build_key(&chunk_info))?) + self.write_bytes_to_key(chunk_info, chunk_bytes) } - fn write_subchunk_batch( + fn write_sub_chunk_batch( &mut self, - subchunk_batch_info: Vec<(ChunkKey, Vec)>, - _: &mut Self::UserState, + sub_chunk_batch_info: Vec<(ChunkKey, Vec)>, ) -> Result<(), Self::Err> { let mut data: Vec = vec![ 0; - subchunk_batch_info + sub_chunk_batch_info .iter() .map(|(info, _)| info.estimate_size()) .sum() ]; let mut buff: Cursor<&mut [u8]> = Cursor::new(&mut data); let mut batch = WriteBatch::default(); - for (key, _) in &subchunk_batch_info { + for (key, _) in &sub_chunk_batch_info { let start = buff.position(); key.write_key(&mut buff); @@ -178,13 +195,12 @@ impl RawWorldTrait for RustyDBInterface { Ok(self.db.write(batch, false)?) } - fn write_subchunk_marker_batch( + fn write_sub_chunk_marker_batch( &mut self, - subchunk_batch_info: Vec, - _: &mut Self::UserState, + sub_chunk_batch_info: Vec, ) -> Result<(), Self::Err> { let mut data: Vec = Vec::new(); - let batch = Self::build_key_batch(subchunk_batch_info, &mut data); + let batch = Self::build_key_batch(sub_chunk_batch_info, &mut data); Ok(self.db.write(batch, false)?) } @@ -192,32 +208,11 @@ impl RawWorldTrait for RustyDBInterface { let mut key_bytes: Vec = vec![0; key.estimate_size()]; let mut buff: Cursor<&mut [u8]> = Cursor::new(&mut key_bytes); key.write_key(&mut buff); - key_bytes - } - fn new( - path: Box, - create_if_missing: bool, - _: &mut Self::UserState, - ) -> Result { - let mut opts = mcpe_options(COMPRESSION_LEVEL); - opts.create_if_missing = create_if_missing; - let db = DB::open(path, opts)?; - Ok(Self { - db, - phantom_data: PhantomData, - }) - } - - fn close(&mut self) -> Result<(), Self::Err> { - self.db.close()?; - Ok(()) + key_bytes } - fn generated_chunks( - &mut self, - _: &mut Self::UserState, - ) -> Result)>, Self::Err> { + fn generated_chunks(&mut self) -> Result)>, Self::Err> { let mut out_set = HashSet::new(); let mut iter = self.db.new_iter()?; @@ -228,7 +223,7 @@ impl RawWorldTrait for RustyDBInterface { iter.current(&mut key, data); let len = key.len(); - let mut cursor = Cursor::new(&mut key); + let mut cursor = Cursor::new(&key); if len == 9 || len == 13 { // Does a little hack to make sure it isn't reading a key that it doesn't want to @@ -249,6 +244,7 @@ impl RawWorldTrait for RustyDBInterface { } else { Dimension::Overworld }; + out_set.insert((dim, (x, y).into())); } } diff --git a/crates/level/src/level/error.rs b/crates/level/src/level/error.rs new file mode 100644 index 00000000..a6bab32f --- /dev/null +++ b/crates/level/src/level/error.rs @@ -0,0 +1,81 @@ +use std::fmt::Debug; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SubChunkError { + #[error("Failed To Get Layer: {0}")] + LayerError(u8), +} + +#[derive(Error, Debug)] +pub enum SubChunkSerDeError { + #[error(transparent)] + WorldError(WorldError), + #[error(transparent)] + SerDeError(SerDeError), +} + +#[derive(Error, Debug)] +pub enum WholeLevelError { + #[error(transparent)] + TranslationError(TranslationError), + #[error(transparent)] + WorldError(WorldError), + #[error(transparent)] + SerDeError(SerDeError), + #[error(transparent)] + SubChunkError(#[from] SubChunkError), +} + +#[derive(Error, Debug)] +pub enum WorldPipelineError< + WorldError: Debug, + Decode: Debug, + Encode: Debug, + TranslationError: Debug, +> { + #[error(transparent)] + TranslationError(TranslationError), + #[error(transparent)] + WorldError(WorldError), + #[error(transparent)] + DecodeError(Decode), + #[error(transparent)] + EncoderError(Encode), + #[error(transparent)] + SubChunkError(#[from] SubChunkError), +} + +impl + WorldPipelineError +{ + pub fn from_decode(other: WholeLevelError) -> Self { + match other { + WholeLevelError::TranslationError(r) => Self::TranslationError(r), + WholeLevelError::WorldError(r) => Self::WorldError(r), + WholeLevelError::SerDeError(r) => Self::DecodeError(r), + WholeLevelError::SubChunkError(r) => Self::SubChunkError(r), + } + } + + pub fn from_encode(other: WholeLevelError) -> Self { + match other { + WholeLevelError::TranslationError(r) => Self::TranslationError(r), + WholeLevelError::WorldError(r) => Self::WorldError(r), + WholeLevelError::SerDeError(r) => Self::EncoderError(r), + WholeLevelError::SubChunkError(r) => Self::SubChunkError(r), + } + } +} + +impl + From> + for WholeLevelError +{ + fn from(value: SubChunkSerDeError) -> Self { + match value { + SubChunkSerDeError::WorldError(e) => WholeLevelError::WorldError(e), + SubChunkSerDeError::SerDeError(e) => WholeLevelError::SerDeError(e), + } + } +} diff --git a/crates/level/src/level/file_interface.rs b/crates/level/src/level/file_interface.rs index 15330a30..ad08d7db 100644 --- a/crates/level/src/level/file_interface.rs +++ b/crates/level/src/level/file_interface.rs @@ -3,7 +3,7 @@ use bedrockrs_shared::world::dimension::Dimension; use std::collections::HashSet; use std::ops::Range; use std::path::Path; -use vek::Vec2; +use vek::{Vec2, Vec3}; pub struct DatabaseBatchHolder { collective: Vec, @@ -32,110 +32,79 @@ impl DatabaseBatchHolder { pub trait RawWorldTrait: Sized { type Err; - type UserState; + type ConstructionInformation; + + fn open( + path: Box, + create_if_missing: bool, + information: &mut Self::ConstructionInformation, + ) -> Result; + + fn close(&mut self) -> Result<(), Self::Err>; + + fn flush(&mut self) -> Result<(), Self::Err>; fn write_bytes_to_key( &mut self, chunk_info: ChunkKey, chunk_bytes: &[u8], - state: &mut Self::UserState, ) -> Result<(), Self::Err>; - fn get_bytes_from_key( - &mut self, - chunk_info: ChunkKey, - state: &mut Self::UserState, - ) -> Result>, Self::Err>; + fn get_bytes_from_key(&mut self, chunk_info: ChunkKey) -> Result>, Self::Err>; - fn delete_bytes_at_key( - &mut self, - chunk_info: ChunkKey, - state: &mut Self::UserState, - ) -> Result<(), Self::Err>; + fn delete_bytes_at_key(&mut self, chunk_info: ChunkKey) -> Result<(), Self::Err>; - fn set_subchunk_raw( + fn set_sub_chunk_raw( &mut self, chunk_info: ChunkKey, chunk_bytes: &[u8], - state: &mut Self::UserState, ) -> Result<(), Self::Err> { - self.write_bytes_to_key(chunk_info, chunk_bytes, state) + self.write_bytes_to_key(chunk_info, chunk_bytes) } - fn get_subchunk_raw( - &mut self, - chunk_info: ChunkKey, - state: &mut Self::UserState, - ) -> Result>, Self::Err> { - self.get_bytes_from_key(chunk_info, state) + fn get_sub_chunk_raw(&mut self, chunk_info: ChunkKey) -> Result>, Self::Err> { + self.get_bytes_from_key(chunk_info) } - fn chunk_exists( - &mut self, - chunk_info: ChunkKey, - state: &mut Self::UserState, - ) -> Result { - Ok(self.get_bytes_from_key(chunk_info, state)?.is_some()) + fn chunk_exists(&mut self, chunk_info: ChunkKey) -> Result { + Ok(self.get_bytes_from_key(chunk_info)?.is_some()) } - fn write_subchunk_batch( + fn write_sub_chunk_batch( &mut self, - subchunk_batch_info: Vec<(ChunkKey, Vec)>, - state: &mut Self::UserState, + sub_chunk_batch_info: Vec<(ChunkKey, Vec)>, ) -> Result<(), Self::Err>; - fn write_subchunk_marker_batch( + fn write_sub_chunk_marker_batch( &mut self, - subchunk_batch_info: Vec, - state: &mut Self::UserState, + sub_chunk_batch_info: Vec, ) -> Result<(), Self::Err>; - fn mark_exist_chunk( - &mut self, - chunk_info: ChunkKey, - state: &mut Self::UserState, - ) -> Result<(), Self::Err> { - self.write_bytes_to_key(chunk_info, &[], state) + fn mark_exist_chunk(&mut self, chunk_info: ChunkKey) -> Result<(), Self::Err> { + // If that 41 is removed, the world ends. The sun explodes and the universe ends + self.write_bytes_to_key(chunk_info, &[41]) } fn build_key(key: &ChunkKey) -> Vec; - fn new( - path: Box, - create_if_missing: bool, - state: &mut Self::UserState, - ) -> Result; - - fn close(&mut self) -> Result<(), Self::Err>; - - fn generated_chunks( - &mut self, - state: &mut Self::UserState, - ) -> Result)>, Self::Err>; + fn generated_chunks(&mut self) -> Result)>, Self::Err>; fn delete_chunk( &mut self, xz: Vec2, dimension: Dimension, - subchunk_range: Vec2, - state: &mut Self::UserState, + sub_chunk_range: Vec2, ) -> Result<(), Self::Err> { - for y in subchunk_range.x..=subchunk_range.y { - self.delete_subchunk(xz, dimension, y, state)? + for y in sub_chunk_range.x..=sub_chunk_range.y { + self.delete_sub_chunk((xz.x, y as i32, xz.y).into(), dimension)? } - self.delete_bytes_at_key(ChunkKey::chunk_marker(xz, dimension), state)?; + self.delete_bytes_at_key(ChunkKey::chunk_marker(xz, dimension))?; Ok(()) } - fn delete_subchunk( - &mut self, - xz: Vec2, - dimension: Dimension, - y: i8, - state: &mut Self::UserState, - ) -> Result<(), Self::Err> { - self.delete_bytes_at_key(ChunkKey::new_subchunk(xz, dimension, y), state) + fn delete_sub_chunk(&mut self, xyz: Vec3, dimension: Dimension) -> Result<(), Self::Err> { + self.delete_bytes_at_key(ChunkKey::new_sub_chunk(xyz, dimension)) } } diff --git a/crates/level/src/level/level.rs b/crates/level/src/level/level.rs index 4f4e7857..9326a9d1 100644 --- a/crates/level/src/level/level.rs +++ b/crates/level/src/level/level.rs @@ -1,20 +1,17 @@ -use crate::level::chunk::LevelChunkTrait; -use crate::level::chunk_cache::SubchunkCacheKey; use crate::level::db_interface::bedrock_key::ChunkKey; use crate::level::file_interface::RawWorldTrait; -use crate::level::sub_chunk::{SubChunkDecoder, SubChunkTrait}; -use crate::level::world_block::WorldBlockTrait; -use crate::level_try; -use crate::types::clear_cache::ClearCacheContainer; +use crate::level::sub_chunk::{ + SerDeStore, SerDeStoreRef, SerDeTrait, SubChunk, SubChunkDecoder, SubChunkDecoderError, + SubChunkEncoder, SubChunkSerDe, SubChunkTrait, SubChunkTraitExtended, SubChunkTransition, +}; +use crate::level::world_block::LevelBlock; use bedrockrs_shared::world::dimension::Dimension; use std::collections::hash_set::Iter; use std::collections::HashSet; use std::fmt::Debug; -use std::io::Cursor; -use std::marker::PhantomData; use std::path::Path; -use thiserror::Error; -use vek::Vec2; +use std::time::{SystemTime, UNIX_EPOCH}; +use vek::{Vec2, Vec3}; /// This is used when filtering chunks. /// `ChunkSelectionFilter::Dimension` is used to just check if the dimension is the same. @@ -24,7 +21,7 @@ pub enum ChunkSelectionFilter { Filter(Box) -> bool>), } -/// This is used when filtering subchunks. +/// This is used when filtering sub chunks. /// /// `SubchunkSelectionFilter::Dimension` is used to just check if the dimension is the same. /// @@ -36,7 +33,7 @@ pub enum SubchunkSelectionFilter { } impl ChunkSelectionFilter { - pub fn poll(&mut self, chunk_dim: Dimension, pos: Vec2) -> bool { + pub fn valid(&mut self, chunk_dim: Dimension, pos: Vec2) -> bool { match self { ChunkSelectionFilter::Dimension(dim) => dim == &chunk_dim, ChunkSelectionFilter::Filter(func) => func(chunk_dim, pos), @@ -44,16 +41,6 @@ impl ChunkSelectionFilter { } } -#[derive(Error, Debug)] -pub enum LevelError { - #[error(transparent)] - DatabaseError(DataBaseError), - #[error(transparent)] - SubChunkDecodeError(SubChunkDecodeError), - #[error(transparent)] - SubChunkError(SubChunkError), -} - #[derive(Debug)] pub struct LevelConfiguration { pub sub_chunk_range: Vec2, @@ -71,608 +58,678 @@ impl Default for LevelConfiguration { } } +#[derive(Debug)] +pub struct SetBlockConfig { + pub position: Vec3, + pub block: LevelBlock, + pub dim: Dimension, + pub layer: u8, +} + +#[derive(Debug)] +pub struct GetBlockConfig { + pub position: Vec3, + pub dim: Dimension, + pub layer: u8, +} + +#[derive(Debug)] +pub struct FillConfig { + pub from: Vec3, + pub to: Vec3, + pub block: LevelBlock, + pub dim: Dimension, + pub layer: u8, + pub data_version: u8, +} + #[allow(dead_code)] -pub struct Level< - UserState, - UserWorldInterface: RawWorldTrait, - UserBlockType: WorldBlockTrait, - UserSubChunkType: SubChunkTrait, - UserSubChunkDecoder: SubChunkDecoder, -> { +pub struct Level +where + ::Err: Debug, +{ db: UserWorldInterface, - state: UserState, config: LevelConfiguration, - cached_sub_chunks: ClearCacheContainer, chunk_existence: HashSet<(Dimension, Vec2)>, - _block_type_marker: PhantomData, - _decoder_marker: PhantomData, } #[allow(dead_code)] -impl< - UserState, - UserWorldInterface: RawWorldTrait, - UserBlockType: WorldBlockTrait, - UserSubChunkType: SubChunkTrait, - UserSubChunkDecoder: SubChunkDecoder, - > Level +impl Level where - ::Err: Debug, - ::Err: Debug, ::Err: Debug, { /// Simple function used to open the world - pub fn open( + pub fn open( path: Box, config: LevelConfiguration, - mut state: UserState, - ) -> Result< - Self, - LevelError, - > { - let db = level_try!(DatabaseError, { + information: &mut ConstructionInformation, + ) -> Result + where + UserWorldInterface: RawWorldTrait, + { + let db = { let val = - UserWorldInterface::new(path.clone(), config.create_db_if_missing, &mut state); + UserWorldInterface::open(path.clone(), config.create_db_if_missing, information); if let Ok(v) = val { Ok(v) } else { - UserWorldInterface::new( + UserWorldInterface::open( { let mut buff = path.into_path_buf(); buff.push("db"); buff.into_boxed_path() }, config.create_db_if_missing, - &mut state, + information, ) } - }); + }?; let mut this = Self { db, - state, config, - cached_sub_chunks: ClearCacheContainer::with_threshold(1024), chunk_existence: HashSet::new(), - _block_type_marker: PhantomData, - _decoder_marker: PhantomData, }; - this.chunk_existence = level_try!(DatabaseError, this.db.generated_chunks(&mut this.state)); + this.chunk_existence = this.db.generated_chunks()?; Ok(this) } - /// # Safety - /// This function is marked as `unsafe` because it allows the caller to bypass the caching systems. - /// If modifications are made directly to the underlying database, the cache may become desynchronized, - /// potentially leading to inconsistent. - /// - /// # When Safe to Use - /// It is safe to use this function if you can guarantee that no information held in the cache - /// will be modified or invalidated by your changes. - pub unsafe fn underlying_world_interface(&mut self) -> &mut UserWorldInterface { + /// Provides a mut ref to the underlying database implementation + pub fn underlying_world_interface(&mut self) -> &mut UserWorldInterface { &mut self.db } - pub fn remove_chunk( + // Fills blocks into the world in the boundaries given. + // This function works on a global scale meaning it works on/over sub chunk boundaries + // This uses the default Encoders and Decoders. If custom ones are needed use [`Level::fill_ex`]. + // pub fn fill( + // &mut self, + // config: FillConfig, + // ) -> Result< + // (), + // WorldPipelineError< + // UserWorldInterface::Err, + // ::Err, + // ::Err, + // ::Err, + // >, + // > { + // self.fill_ex(config, &mut SubChunkSerDe, &mut SubChunkSerDe) + // } + + // Fills blocks into the world in the boundaries given. + // This function works on a global scale meaning it works on/over sub chunk boundaries + // This uses custom Encoders and Decoders + // pub fn fill_ex( + // &mut self, + // config: FillConfig, + // decoder: &mut Decoder, + // encoder: &mut Encoder, + // ) -> Result< + // (), + // WorldPipelineError< + // UserWorldInterface::Err, + // Decoder::Err, + // Encoder::Err, + // ::Err, + // >, + // > + // where + // Decoder::Err: Debug, + // Encoder::Err: Debug, + // { + // // This gets the `to` and `from` into a state where `from` will always be the bottom left of the selection and `to` will be the top right + // let (from, to) = bounds_left_optimized(config.to, config.from); + // + // let (min, local_min) = LevelBlock::block_pos_to_sub_chunk(from); + // let (max, local_max) = LevelBlock::block_pos_to_sub_chunk(to); + // + // let block = config.block; + // + // for x in min.x..=max.x { + // for z in min.z..=max.z { + // for y in min.y..=max.y { + // if in_range(min.x, max.x, x) + // && in_range(min.y, max.y, y) + // && in_range(min.z, max.z, z) + // { + // // Since this whole sub chunk must be replaced, + // // we will just create a new one + // // to cut down on the overhead of reading it + // let pos = (x, y, z).into(); + // + // let data = + // SubChunkTransition::full(pos, config.data_version, block.clone()); + // + // self.set_sub_chunk_raw(encoder, data, pos, config.dim) + // .map_err(|err| WorldPipelineError::from_encode(err.into()))?; + // } else { + // // Now we need to find where this sub chunk lies in the boundary + // + // let invert_x = x == max.x; + // let invert_y = y == max.y; + // let invert_z = z == max.z; + // + // let x_range = if invert_x { + // 0..=local_max.x + // } else { + // local_min.x..=15 + // }; + // + // let y_range = if invert_y { + // local_min.y..=15 + // } else { + // 0..=local_max.y + // }; + // + // let z_range = if invert_z { + // local_min.z..=15 + // } else { + // 0..=local_max.z + // }; + // + // // This is slightly convoluted to allow for fewer allocations, + // // by generating a sub chunk directly if it didn't exist in the database + // // [`Result::unwrap_or_else`] is used over [`Result::unwrap_or`] to bypass the overhead of creating the default even when it's not needed + // let mut data = self + // .get_sub_chunk_raw::( + // (x, y, z).into(), + // config.dim, + // decoder, + // ) + // .map_err(|err| WorldPipelineError::from_decode(err.into()))? + // .map(|transition| { + // SubChunk::decode_from_transition(transition, config.dim, &mut ()) + // .unwrap() // This is a safe call to unwrap + // // because `SubChunk::decode_from_translation` + // // never fails + // }) + // .unwrap_or_else(|| { + // { + // SubChunk::empty((x, y, z).into(), config.dim).to_init() + // } + // }); + // + // for x in x_range { + // for z in z_range.clone() { + // for y in y_range.clone() { + // data.set_block((x, y, z).into(), block.clone()) + // .map_err(|err| WorldPipelineError::SubChunkError(err))?; + // } + // } + // } + // + // let translation = data.to_transition().unwrap(); // Safe because this can't fail + // self.set_sub_chunk_raw(encoder, translation, (x, y, z).into(), config.dim) + // .map_err(|err| WorldPipelineError::from_encode(err.into()))?; + // } + // } + // } + // } + // Ok(()) + // } + + /// # Warning + /// This function is incredibly expensive to call due to having to deserialize the sub chunk. Only call this for 1 off cases of getting blocks + /// + /// # Description + /// Gets a block at a specific position in the world. + /// # Info + /// This function uses the default decoder implementation. + /// If a custom Decoder is needed please use [`Level::get_block_ex`]. + pub fn get_block( &mut self, - xz: Vec2, - dimension: Dimension, - ) -> Result<(), UserWorldInterface::Err> { - self.remove_chunk_ex(xz, dimension, self.config.sub_chunk_range.clone()) + config: GetBlockConfig, + ) -> Result< + Option, + WholeLevelError< + UserWorldInterface::Err, + ::Err, + ::Err, + >, + > { + self.get_block_ex::( + config.position, + config.dim, + config.layer, + &mut SubChunkSerDe, + ) } - pub fn remove_chunk_ex( + /// # Warning + /// This function is incredibly expensive to call due to having to deserialize the sub chunk. Only call this for 1 off cases of getting blocks + /// + /// # Description + /// Gets a block at a specific position in the world. + /// # Info + /// This function uses a custom decoder. + /// If a custom layer is not needed and default decoding is fine use [`Level::get_block`]. + pub fn get_block_ex( &mut self, - xz: Vec2, - dimension: Dimension, - subchunk_range: Vec2, - ) -> Result<(), UserWorldInterface::Err> { - self.db - .delete_chunk(xz, dimension, subchunk_range, &mut self.state) - } + pos: Vec3, + dim: Dimension, + layer: u8, + decoder: &mut Decoder, + ) -> Result< + Option, + WholeLevelError::Err>, + > + where + Decoder::Err: Debug, + { + let (sub_chunk_pos, local_pos) = LevelBlock::block_pos_to_sub_chunk(pos); + + let data = if let Some(data) = self.get_sub_chunk_ex::( + sub_chunk_pos, + dim, + SerDeStoreRef::new(decoder, &mut ()), + )? { + data + } else { + let mut out = SubChunk::empty(sub_chunk_pos, dim); - pub fn remove_subchunk( - &mut self, - xz: Vec2, - dimension: Dimension, - y: i8, - ) -> Result<(), UserWorldInterface::Err> { - self.db.delete_subchunk(xz, dimension, y, &mut self.state) - } + if layer != 0 { + for _ in 1..=layer { + out.add_sub_layer() + } + } + out.set_active_layer(layer); - /// Checks if a given chunk exists - pub fn chunk_exists(&mut self, xz: Vec2, dimension: Dimension) -> bool { - self.chunk_existence.contains(&(dimension, xz)) + out + }; + + Ok(data.get_block(local_pos).cloned()) } - /// Must call before destruction - pub fn close( - mut self, + /// # Warning + /// This function is incredibly expensive to call due to having to deserialize and serialize the sub chunk. Only call this for 1 off cases of setting blocks + /// + /// # Description + /// Sets a block at a specific position in the world. + /// # Info + /// This function uses the default decoder and encoder implementations. + /// If a custom Decoder or Encoder is needed please use [`Level::set_block_ex`]. + pub fn set_block( + &mut self, + config: SetBlockConfig, ) -> Result< (), - LevelError, + WorldPipelineError< + UserWorldInterface::Err, + ::Err, + ::Err, + ::Err, + >, > { - level_try!(DatabaseError, self.flush_existence_buffer()); - level_try!(SubChunkDecodeError, self.cull()); - - // Must come after all the other closing steps - level_try!(DatabaseError, self.db.close()); - Ok(()) + self.set_block_ex::( + config.block, + config.position, + config.dim, + config.layer, + &mut SubChunkSerDe, + &mut SubChunkSerDe, + ) } - /// Returns all chunks (in the form of its key) that exist in the world - pub fn existence_chunks(&self) -> Iter<'_, (Dimension, Vec2)> { - self.chunk_existence.iter() - } + /// # Warning + /// This function is incredibly expensive to call due to having to deserialize and serialize the sub chunk. Only call this for 1 off cases of setting blocks + /// + /// # Description + /// Sets a block at a specific position in the world. + /// # Info + /// This function uses a custom Encoder and Decoder. + /// If a custom layer is not needed and default Encoding and Decoding is fine use [`Level::set_block`]. + pub fn set_block_ex( + &mut self, + block: LevelBlock, + pos: Vec3, + dim: Dimension, + layer: u8, + decoder: &mut Decoder, + encoder: &mut Encoder, + ) -> Result< + (), + WorldPipelineError< + UserWorldInterface::Err, + Decoder::Err, + Encoder::Err, + ::Err, + >, + > + where + Decoder::Err: Debug, + Encoder::Err: Debug, + { + let (sub_chunk_pos, local_pos) = LevelBlock::block_pos_to_sub_chunk(pos); + let mut data = if let Some(data) = self + .get_sub_chunk_ex::( + sub_chunk_pos, + dim, + SerDeStoreRef::new(decoder, &mut ()), + ) + .map_err(|e| WorldPipelineError::from_decode(e))? + { + data + } else { + let mut out = SubChunk::empty(sub_chunk_pos, dim); - /// Fetches all chunk keys that satisfy the filter's constraints - pub fn get_chunk_keys(&mut self, mut filter: ChunkSelectionFilter) -> Vec> { - self.chunk_existence - .iter() - .filter_map(|(chunk_dim, pos)| { - if filter.poll(chunk_dim.clone(), *pos) { - Some(*pos) - } else { - None + if layer != 0 { + for _ in 1..=layer { + out.add_sub_layer() } - }) - .collect() + } + out.set_active_layer(layer); + + out + }; + + data.set_block(local_pos, block)?; + + self.set_sub_chunk_ex::( + &data, + encoder, + data.position(), + data.dimension(), + ) + .map_err(|e| WorldPipelineError::from_encode(e)) } - /// Fetches all chunks that satisfy the filter - pub fn get_chunks< - T: LevelChunkTrait< - Self, - UserLevel = Level< - UserState, - UserWorldInterface, - UserBlockType, - UserSubChunkType, - UserSubChunkDecoder, - >, - >, - >( + /// High level function to fetch a sub chunk that contains a specific block. + /// If the sub chunk doesn't exist this will return None + pub fn get_sub_chunk_block_position( &mut self, - mut filter: ChunkSelectionFilter, - min_max: Vec2, - ) -> Result, T::Err> + pos: Vec3, + dim: Dimension, + config: impl SerDeTrait, + ) -> Result< + Option, + WholeLevelError, + > where - <, - >>::UserSubchunk as SubChunkTrait>::BlockType: WorldBlockTrait, + SubChunkType::Err: Debug, { - let positions: Vec<_> = self - .chunk_existence - .iter() - .filter_map(|(chunk_dim, pos)| { - if filter.poll(*chunk_dim, *pos) { - Some((*chunk_dim, *pos)) - } else { - None - } - }) - .collect(); + let (sub_chunk_pos, _) = LevelBlock::block_pos_to_sub_chunk(pos); + self.get_sub_chunk_block_position_ex::( + sub_chunk_pos, + dim, + config, + ) + } - positions - .into_iter() - .map(|(dim, pos)| T::load_from_world(min_max, pos, dim, self)) - .collect() + pub fn get_sub_chunk_block_position_ex( + &mut self, + pos: Vec3, + dim: Dimension, + config: impl SerDeTrait, + ) -> Result< + Option, + WholeLevelError, + > + where + Decoder::Err: Debug, + SubChunkType::Err: Debug, + { + let (sub_chunk_pos, _) = LevelBlock::block_pos_to_sub_chunk(pos); + self.get_sub_chunk_ex::(sub_chunk_pos, dim, config) } - /// Fetches a subchunk at a given xyz and dimension + /// High level function to fetch a sub chunk directly from the database. + /// If the sub chunk doesn't exist, this will return None pub fn get_sub_chunk( &mut self, - xz: Vec2, - y: i8, + pos: Vec3, dim: Dimension, - ) -> Result< - UserSubChunkType, - LevelError, - > { - if self.config.rw_cache { - if let Some(chunk) = self - .cached_sub_chunks - .get(&SubchunkCacheKey::new(xz, y, dim)) - { - return Ok(chunk.state_clone(&mut self.state)); - } - } - let raw_bytes = level_try!( - DatabaseError, - self.db - .get_subchunk_raw(ChunkKey::new_subchunk(xz, dim, y), &mut self.state) - ); - let out = match raw_bytes { - None => Ok::< - (i8, Option), - LevelError< - UserWorldInterface::Err, - UserSubChunkDecoder::Err, - UserSubChunkType::Err, - >, - >((y, None)), - Some(bytes) => { - if bytes.len() < 100 { - // This happens when there is no layers - let out = (y, None); - Ok(out) - } else { - let mut bytes = Cursor::new(bytes); - let data = level_try!( - SubChunkDecodeError, - UserSubChunkDecoder::decode_bytes_as_chunk(&mut bytes, &mut self.state) - ); - let out = ( - y, - Some(level_try!( - SubChunkError, - UserSubChunkType::decode_from_raw(data, &mut self.state) - )), - ); - Ok(out) - } - } - }?; - if self.config.rw_cache { - if let Some(data) = &out.1 { - let new = data.state_clone(&mut self.state); - self.cached_sub_chunks - .insert(SubchunkCacheKey::new(xz, y, dim), new); - } - } - if let None = &out.1 { - Ok(UserSubChunkType::empty(out.0, self.state())) - } else { - Ok(out.1.unwrap()) - } + ) -> Result, WholeLevelError> + { + self.get_sub_chunk_ex(pos, dim, SerDeStore::::default()) } - /// Sets a subchunk at the given xyz and dimension - pub fn set_sub_chunk( + pub fn get_sub_chunk_ex( &mut self, - data: UserSubChunkType, - xz: Vec2, - y: i8, + pos: Vec3, dim: Dimension, + mut config: impl SerDeTrait, ) -> Result< - (), - LevelError, - > { - if self.config.rw_cache { - self.cached_sub_chunks - .insert(SubchunkCacheKey::new(xz, y, dim), data); - level_try!(SubChunkDecodeError, self.perform_flush()); - } else { - let raw = level_try!( - SubChunkDecodeError, - UserSubChunkDecoder::write_as_bytes( - level_try!(SubChunkError, data.to_raw(y, &mut self.state)), - false, - &mut self.state, - ) - ); - let key = ChunkKey::new_subchunk(xz, dim, y); - level_try!( - DatabaseError, - self.db.set_subchunk_raw(key, &raw, &mut self.state) - ); - self.handle_exist(xz, dim); - } - Ok(()) + Option, + WholeLevelError, + > + where + Decoder::Err: Debug, + SubChunkType::Err: Debug, + { + let ser = self.get_sub_chunk_raw::(pos, dim, config.serde())?; + + let ser = match ser { + None => return Ok(None), + Some(e) => e, + }; + + Ok(Some( + SubChunkType::decode_from_transition(ser, dim, config.info()) + .map_err(|ele| WholeLevelError::TranslationError(ele))?, + )) } - /// Sets a whole chunk in the saved position of the chunk and the saved dimension. - /// `xz_override` lets the xz position be replaced if copying the chunk - /// `dim_override` lets the dimension of the chunk be changed if copying the chunk - pub fn set_chunk_ex< - UserChunkType: LevelChunkTrait< - Self, - UserLevel = Level< - UserState, - UserWorldInterface, - UserBlockType, - UserSubChunkType, - UserSubChunkDecoder, - >, - >, - >( + pub fn get_sub_chunk_raw( &mut self, - chnk: UserChunkType, - xz_override: Option>, - dim_override: Option, - ) -> Result<(), UserChunkType::Err> + pos: Vec3, + dim: Dimension, + decoder: &mut Decoder, + ) -> Result, SubChunkSerDeError> where - <, - >>::UserSubchunk as SubChunkTrait>::BlockType: WorldBlockTrait, + Decoder::Err: Debug, { - chnk.write_to_world(self, xz_override, dim_override) + let bytes = self + .db + .get_sub_chunk_raw(ChunkKey::new_sub_chunk(pos, dim)) + .map_err(|ele| SubChunkSerDeError::WorldError(ele))?; + + let bytes = match bytes { + None => return Ok(None), + Some(e) => e, + }; + + Ok(Some( + decoder + .decode_bytes_as_sub_chunk(&mut std::io::Cursor::new(bytes), (pos.x, pos.z).into()) + .map_err(|ele| SubChunkSerDeError::SerDeError(ele))?, + )) } - /// Sets a whole chunk in the saved position of the chunk and the saved dimension. - pub fn set_chunk< - UserChunkType: LevelChunkTrait< - Self, - UserLevel = Level< - UserState, - UserWorldInterface, - UserBlockType, - UserSubChunkType, - UserSubChunkDecoder, - >, - >, - >( + /// High level function to write a sub chunk directly into the database. + /// Writes the sub chunk at its current dimension and position. + /// If a dimension or position override is required, please call [`Level::set_sub_chunk_ex`] + pub fn set_sub_chunk( &mut self, - chnk: UserChunkType, - ) -> Result<(), UserChunkType::Err> + sub_chunk: &SubChunkType, + ) -> Result<(), WholeLevelError> where - <, - >>::UserSubchunk as SubChunkTrait>::BlockType: WorldBlockTrait, - <, - >>::UserSubchunk as SubChunkTrait>::BlockType: WorldBlockTrait, + SubChunkType::Err: Debug, { - self.set_chunk_ex(chnk, None, None) + self.set_sub_chunk_ex( + sub_chunk, + &mut SubChunkSerDe::default(), + sub_chunk.position(), + sub_chunk.dimension(), + ) } - /// Fetches a chunk from the world at the given xz and dimension and with the given bounds - /// ### Note: - /// `min_max` is the min and max subchunks not blocks - pub fn get_chunk_ex< - UserChunkType: LevelChunkTrait< - Self, - UserLevel = Self, - Err = LevelError< - UserWorldInterface::Err, - UserSubChunkDecoder::Err, - UserSubChunkType::Err, - >, - >, + /// Slightly lower level function to write a sub chunk directly into the database. + /// Writes the sub chunk at the position and dimension provided. + /// If you do not need to override this, please use [`Level::set_sub_chunk`] instead + pub fn set_sub_chunk_ex< + SubChunkType: SubChunkTraitExtended, + SubChunkEncoderType: SubChunkEncoder, >( &mut self, - xz: Vec2, + sub_chunk: &SubChunkType, + encoder: &mut SubChunkEncoderType, + pos: Vec3, dim: Dimension, - min_max: Vec2, - ) -> Result + ) -> Result< + (), + WholeLevelError, + > where - <, - >>::UserSubchunk as SubChunkTrait>::BlockType: WorldBlockTrait, + SubChunkType::Err: Debug, + SubChunkEncoderType::Err: Debug, { - UserChunkType::load_from_world(min_max, xz, dim, self) + let intermediate = sub_chunk + .to_transition() + .map_err(|ele| WholeLevelError::TranslationError(ele))?; + Ok(self.set_sub_chunk_raw(encoder, intermediate, pos, dim)?) } - /// Fetches a chunk from the world at the given xz and dimension and with the given bounds - /// ### Note: - /// `min_max` is the min and max subchunks not blocks - pub fn get_chunk< - UserChunkType: LevelChunkTrait< - Self, - UserLevel = Self, - Err = LevelError< - UserWorldInterface::Err, - UserSubChunkDecoder::Err, - UserSubChunkType::Err, - >, - >, - >( + /// Lowest level setting function for a sub chunk. + /// This function takes a transition and writes it into the database directly. + /// This is a powerful function which can lead to invalid data being written into the database + pub fn set_sub_chunk_raw( &mut self, - xz: Vec2, + encoder: &mut SubChunkEncoderType, + intermediate: SubChunkTransition, + pos: Vec3, dim: Dimension, - ) -> Result + ) -> Result<(), SubChunkSerDeError> where - <, - >>::UserSubchunk as SubChunkTrait>::BlockType: WorldBlockTrait, + SubChunkEncoderType::Err: Debug, { - self.get_chunk_ex(xz, dim, self.config.sub_chunk_range.clone()) + let bytes = encoder + .write_as_bytes(intermediate, false) + .map_err(|ele| SubChunkSerDeError::SerDeError(ele))?; + + self.db + .set_sub_chunk_raw(ChunkKey::new_sub_chunk(pos, dim), &bytes) + .map_err(|ele| SubChunkSerDeError::WorldError(ele))?; + + self.handle_exist((pos.x, pos.z).into(), dim); + Ok(()) } - fn handle_exist(&mut self, xz: Vec2, dim: Dimension) { - self.chunk_existence.insert((dim, xz)); + /// Used to delete a whole chunk from the world. This clears the data from the database. It only removes data in the configured range of the level. + /// If clearing inside or outside the range which the level covers is needed see [`Level::remove_chunk_ex`] + pub fn remove_chunk( + &mut self, + xz: Vec2, + dimension: Dimension, + ) -> Result<(), UserWorldInterface::Err> { + self.remove_chunk_ex(xz, dimension, self.config.sub_chunk_range.clone()) } - fn perform_flush(&mut self) -> Result<(), UserSubChunkDecoder::Err> { - let mut batch_info: Vec<(ChunkKey, Vec)> = Vec::new(); - let mut exist_info: Vec = Vec::new(); - self.cached_sub_chunks.cull(|user_key, data| { - let raw = UserSubChunkDecoder::write_as_bytes( - data.to_raw(user_key.y, &mut self.state).unwrap(), - false, - &mut self.state, - )?; - let key = ChunkKey::new_subchunk(user_key.xz, user_key.dim, user_key.y); - - batch_info.push((key, raw)); - exist_info.push(ChunkKey::chunk_marker(user_key.xz, user_key.dim)); - Ok(()) - })?; - if !batch_info.is_empty() { - self.db - .write_subchunk_batch(batch_info, &mut self.state) - .unwrap() - } - if !exist_info.is_empty() { - self.db - .write_subchunk_marker_batch(exist_info, &mut self.state) - .unwrap() - } - Ok(()) + /// Used to delete a chunk from the world. This clears data from the database. This removes all sub chunks in the range specified. + /// If you do not need to delete only a section of information use [`Level::remove_chunk`] + pub fn remove_chunk_ex( + &mut self, + xz: Vec2, + dimension: Dimension, + sub_chunk_range: Vec2, + ) -> Result<(), UserWorldInterface::Err> { + self.chunk_existence.remove(&(dimension, xz)); + self.db.delete_chunk(xz, dimension, sub_chunk_range) } - fn cull(&mut self) -> Result<(), UserSubChunkDecoder::Err> { - let mut batch_info: Vec<(ChunkKey, Vec)> = Vec::new(); - let mut exist_info: Vec = Vec::new(); - self.cached_sub_chunks.clear(|user_key, data| { - let raw = UserSubChunkDecoder::write_as_bytes( - data.to_raw(user_key.y, &mut self.state).unwrap(), - false, - &mut self.state, - )?; - let key = ChunkKey::new_subchunk(user_key.xz, user_key.dim, user_key.y); - - batch_info.push((key, raw)); - exist_info.push(ChunkKey::chunk_marker(user_key.xz, user_key.dim)); - Ok(()) - })?; - if !batch_info.is_empty() { - self.db - .write_subchunk_batch(batch_info, &mut self.state) - .unwrap() - } - if !exist_info.is_empty() { - self.db - .write_subchunk_marker_batch(exist_info, &mut self.state) - .unwrap() - } - Ok(()) + /// Removes a sub chunk from the database + pub fn remove_sub_chunk( + &mut self, + xyz: Vec3, + dimension: Dimension, + ) -> Result<(), UserWorldInterface::Err> { + self.db.delete_sub_chunk(xyz, dimension) } - fn flush_existence_buffer(&mut self) -> Result<(), UserWorldInterface::Err> { - for (dim, pos) in &self.chunk_existence { - self.db - .mark_exist_chunk(ChunkKey::chunk_marker(*pos, *dim), &mut self.state)? - } - Ok(()) + /// Checks if a chunk exists and returns the result + pub fn chunk_exists(&mut self, xz: Vec2, dimension: Dimension) -> bool { + self.chunk_existence.contains(&(dimension, xz)) } -} -pub trait LevelModificationProvider { - type UserState; - type UserWorldInterface; - type UserBlockType; - type UserSubChunkType; - type UserSubChunkDecoder; - type Error; + /// Closes the current level and saves all information to the DB + pub fn close(mut self) -> Result<(), UserWorldInterface::Err> { + self.close_internal() + } - fn get_sub_chunk( - &mut self, - xz: Vec2, - y: i8, - dim: Dimension, - ) -> Result; + /// Returns all chunks (in the form of its key) that exist in the world + pub fn existence_chunks(&self) -> Iter<'_, (Dimension, Vec2)> { + self.chunk_existence.iter() + } - fn set_subchunk( + /// This function WIPES all chunks that exist in the target dimension. This doesn't mean reset. This deletes them + /// This function only effects sub chunks in the current level range. If you only want to delete sections of the chunk please call [`Level::clear_ex`] + pub fn clear(&mut self, dim: Dimension) -> Result<(), UserWorldInterface::Err> { + self.clear_ex(dim, self.config.sub_chunk_range) + } + + /// This function WIPES all chunks that exist in the target dimension. This doesn't mean reset. This deletes them + /// This function + pub fn clear_ex( &mut self, - xz: Vec2, - y: i8, dim: Dimension, - chnk: Self::UserSubChunkType, - ) -> Result<(), Self::Error>; + sub_chunk_range: Vec2, + ) -> Result<(), UserWorldInterface::Err> { + self.get_chunk_keys(ChunkSelectionFilter::Dimension(dim)) + .drain(..) + .try_for_each(|ele| self.remove_chunk_ex(ele, dim, sub_chunk_range)) + } - fn state(&mut self) -> &mut Self::UserState; - fn chunk_exists(&mut self, xz: Vec2, dimension: Dimension) -> bool; -} + /// Fetches all chunk keys that satisfy the filter's constraints + pub fn get_chunk_keys(&mut self, mut filter: ChunkSelectionFilter) -> Vec> { + self.chunk_existence + .iter() + .filter_map(|(chunk_dim, pos)| { + if filter.valid(chunk_dim.clone(), *pos) { + Some(*pos) + } else { + None + } + }) + .collect() + } -impl< - UserState, - UserWorldInterface: RawWorldTrait, - UserBlockType: WorldBlockTrait, - UserSubChunkType: SubChunkTrait, - UserSubChunkDecoder: SubChunkDecoder, - > LevelModificationProvider - for Level -where - ::Err: Debug, - ::Err: Debug, - ::Err: Debug, -{ - type UserState = UserState; - type UserWorldInterface = UserWorldInterface; - type UserBlockType = UserBlockType; - type UserSubChunkType = UserSubChunkType; - type UserSubChunkDecoder = UserSubChunkDecoder; - type Error = - LevelError; - - fn get_sub_chunk( - &mut self, - xz: Vec2, - y: i8, - dim: Dimension, - ) -> Result { - self.get_sub_chunk(xz, y, dim) + pub fn flush(&mut self) -> Result<(), UserWorldInterface::Err> { + self.flush_existence_buffer()?; + self.db.flush() } - fn set_subchunk( - &mut self, - xz: Vec2, - y: i8, - dim: Dimension, - chnk: Self::UserSubChunkType, - ) -> Result<(), Self::Error> { - self.set_sub_chunk(chnk, xz, y, dim) + // Internal functions which mainly handle DB layer interactions + fn close_internal(&mut self) -> Result<(), UserWorldInterface::Err> { + self.flush()?; + + // Must come after all the other closing steps + self.db.close() } - fn state(&mut self) -> &mut Self::UserState { - &mut self.state + fn handle_exist(&mut self, xz: Vec2, dim: Dimension) { + self.chunk_existence.insert((dim, xz)); + } + fn flush_existence_buffer(&mut self) -> Result<(), UserWorldInterface::Err> { + for (dim, pos) in &self.chunk_existence { + self.db + .mark_exist_chunk(ChunkKey::chunk_marker(*pos, *dim))? + } + Ok(()) } +} - fn chunk_exists(&mut self, xz: Vec2, dimension: Dimension) -> bool { - self.chunk_exists(xz, dimension) +impl Drop for Level +where + ::Err: Debug, +{ + fn drop(&mut self) { + self.close_internal().unwrap(); } } #[cfg(feature = "default-impl")] +#[allow(unused_imports)] pub mod default_impl { use super::*; - use crate::level::chunk::default_impl::LevelChunk; use crate::level::db_interface::rusty::RustyDBInterface; - use crate::level::sub_chunk::default_impl::{SubChunk, SubChunkDecoderImpl}; - use crate::level::world_block::default_impl::WorldBlock; - - pub struct BedrockState {} - pub type RawInterface = RustyDBInterface; - pub type BedrockWorldBlock = WorldBlock; - pub type BedrockSubChunk = SubChunk; - pub type BedrockSubChunkDecoder = SubChunkDecoderImpl; - pub type BedrockLevel = Level< - BedrockState, - RawInterface, - BedrockWorldBlock, - BedrockSubChunk, - BedrockSubChunkDecoder, - >; - pub type BedrockChunk = LevelChunk; - pub type BedrockLevelError = LevelError< - ::Err, - ::Err, - ::Err, - >; + use crate::level::sub_chunk::{SerDeStore, SubChunk, SubChunkSerDe}; + use crate::level::world_block::LevelBlock; + + pub type LevelDbInterface = RustyDBInterface; + pub type LevelDbError = ::Err; + pub type BedrockLevel = Level; } + +use crate::level::error::{SubChunkSerDeError, WholeLevelError, WorldPipelineError}; +use crate::utility::miner::{bounds_left_optimized, in_range}; +#[cfg(feature = "default-impl")] +pub use default_impl::*; diff --git a/crates/level/src/level/mod.rs b/crates/level/src/level/mod.rs index 866ee024..ba143a16 100644 --- a/crates/level/src/level/mod.rs +++ b/crates/level/src/level/mod.rs @@ -1,6 +1,5 @@ -pub mod chunk; -pub mod chunk_cache; pub mod db_interface; +mod error; pub mod file_interface; pub mod level; pub mod sub_chunk; diff --git a/crates/level/src/level/sub_chunk.rs b/crates/level/src/level/sub_chunk.rs index 74ede1c2..bf9e6658 100644 --- a/crates/level/src/level/sub_chunk.rs +++ b/crates/level/src/level/sub_chunk.rs @@ -1,553 +1,531 @@ -use crate::level::world_block::WorldBlockTrait; -use std::fmt::Debug; -use std::io::Cursor; +pub use crate::level::error::SubChunkError; +use crate::level::world_block::LevelBlock; +use crate::utility::miner::idx_3_to_1; +use bedrockrs_shared::world::dimension::Dimension; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use nbtx::NbtError; +use std::fmt::{Debug, Formatter}; +use std::io::Write; +use std::io::{Cursor, Seek, SeekFrom}; +use std::mem::MaybeUninit; use thiserror::Error; -use vek::Vec3; +use vek::{Vec2, Vec3}; pub type BlockLayer = (Box<[u16; 4096]>, Vec); -/// Specifies the type of filter used when filling a region in the world. -/// -/// # Type Parameters -/// - `UserBlockType`: A block type that implements the `WorldBlockTrait`. -pub enum SubchunkFillFilter { - /// Fills the entire region unconditionally, overwriting all blocks. - Blanket, - - /// Replaces only the blocks that match the specified type. - /// - /// # Parameters - /// - `UserBlockType`: The block type to be replaced. - Replace(UserBlockType), - - /// Avoids overwriting blocks that match the specified type. - /// - /// # Parameters - /// - `UserBlockType`: The block type to avoid. - Avoid(UserBlockType), - - /// Uses a custom precedence function to determine whether a block should be replaced. - /// - /// # Parameters - /// - A boxed function with the following parameters: - /// - `&UserBlockType`: The current block being evaluated. - /// - `Vec3`: The local coordinates of the block within the current subchunk. - /// - `i8`: The subchunk y. - /// - /// # Returns - /// - `bool`: `true` to allow replacing the block, `false` to skip it. - Precedence(Box, i8) -> bool>), +#[allow(dead_code)] +pub struct SubChunkTransition { + pub position: Vec3, + pub data_version: u8, + pub layers: Vec>, } - -impl SubchunkFillFilter { - pub fn may_place(&self, target: &T, position: Vec3, y: i8) -> bool { - match self { - SubchunkFillFilter::Blanket => true, - SubchunkFillFilter::Replace(v) => v == target, - SubchunkFillFilter::Avoid(v) => v != target, - SubchunkFillFilter::Precedence(f) => f(target, position, y), +impl Debug for SubChunkTransition { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "SubChunkTransition(position ({}), data_version ({}), layer count ({}), ", + self.position, + self.data_version, + self.layers.len() + )?; + for l in &self.layers { + write!(f, "Layer block count ({})", l.1.len())?; } - } -} - -#[derive(Error, Debug)] -pub enum SubchunkFillError { - #[error("Attempted to read block at x: {0}, y {1}, z: {2} and got None")] - BlockIndexDidntReturn(u8, u8, u8), - #[error(transparent)] - SubchunkError(T), -} - -#[allow(dead_code)] -pub struct SubchunkTransitionalData { - y_level: i8, - data_version: u8, - layers: Vec>, + write!(f, ")") + } } -impl SubchunkTransitionalData { - pub fn new(y_level: i8, layer_count: usize) -> Self { +impl SubChunkTransition { + pub fn new(position: Vec3, layer_count: usize, version: u8) -> Self { Self { - y_level, - data_version: 9, + position, + data_version: version, layers: Vec::with_capacity(layer_count), } } - pub fn new_layer(&mut self, data: (Box<[u16; 4096]>, Vec)) { + pub fn new_layer(&mut self, data: (Box<[u16; 4096]>, Vec)) { self.layers.push(data); } + + pub fn full(position: Vec3, version: u8, block: LevelBlock) -> Self { + let mut ret = Self::new(position, 1, version); + let buffer: BlockLayer = (Box::new([0u16; 4096]), vec![block]); + ret.layers.push(buffer); + ret + } + + pub fn air(position: Vec3, version: u8) -> Self { + Self::full(position, version, LevelBlock::air()) + } } -/// The trait which any decoder implementation must implement pub trait SubChunkDecoder { type Err; - type BlockType; - type UserState; - /// This function is responsible for decoding a stream of raw bytes into the intermediate structure used for creating subchunks - fn decode_bytes_as_chunk( + /// This function is responsible for decoding a stream of raw bytes into the intermediate structure used for creating sub chunks + fn decode_bytes_as_sub_chunk( + &mut self, bytes: &mut Cursor>, - state: &mut Self::UserState, - ) -> Result, Self::Err>; + xz: Vec2, + ) -> Result; +} + +pub trait SubChunkEncoder { + type Err; /// This function is responsible /// for encoding the raw block data into a vector of bytes which the database layer can then save. fn write_as_bytes( - chunk_state: SubchunkTransitionalData, + &mut self, + chunk_state: SubChunkTransition, network: bool, - state: &mut Self::UserState, ) -> Result, Self::Err>; } -/// The main trait that any subchunk type must implement -pub trait SubChunkTrait: Sized { - type Err; - type BlockType; - type UserState; +pub struct SerDeStore { + pub serde: SerDeType, + pub information: TransitionType, +} - /// This must create a valid empty state for the subchunk. - /// This may be just an empty optional, or it may be defaulting to air - fn empty(y_index: i8, state: &mut Self::UserState) -> Self; +pub struct SerDeStoreRef<'a, 'u, SerDeType, TransitionType> { + pub serde: &'a mut SerDeType, + pub information: &'u mut TransitionType, +} - /// This must create a valid "full" state for the subchunk from the transitional data - fn decode_from_raw( - data: SubchunkTransitionalData, - state: &mut Self::UserState, - ) -> Result; +impl<'a, 'u, SerDeType, TransitionType> SerDeStoreRef<'a, 'u, SerDeType, TransitionType> { + pub fn new(serde: &'a mut SerDeType, information: &'u mut TransitionType) -> Self { + Self { serde, information } + } +} - /// This must create a transitional state from the current subchunk information - fn to_raw( - &self, - y_level: i8, - state: &mut Self::UserState, - ) -> Result, Self::Err>; - - /// Gets the block at the current position on the active layer - fn get_block(&self, xyz: Vec3) -> Option<&Self::BlockType>; - /// Gets the block at the current position on the active layer - fn get_block_mut(&mut self, xyz: Vec3) -> Option<&mut Self::BlockType>; - /// Sets the block at the current position on the active layer - fn set_block(&mut self, xyz: Vec3, block: Self::BlockType) -> Result<(), Self::Err>; - - /// Gets the active layer - fn get_active_layer(&self) -> u8; - /// Sets the active layer - fn set_active_layer(&mut self, idx: u8); - - /// This generates a new empty sublayer for the subchunk. This may only apply to bedrock, and if so, just unimplement this function - fn add_sub_layer(&mut self, state: &mut Self::UserState); - /// This returns the count of all sublayers - fn get_sub_layer_count(&self) -> usize; - - /// Gets the subchunk Y - fn get_y(&self) -> i8; - /// Sets the subchunk Y - fn set_y(&mut self, y: i8) -> i8; - - /// This is used as a replacement for the normal clone functions. It allows access to the state - fn state_clone(&self, state: &mut Self::UserState) -> Self; - - /// This returns if the subchunk is just air if so nothing is written to the database if this isn't desired behavior just always return false - fn is_empty(&self) -> bool; +pub trait SerDeTrait { + type SerDe; + type Info; + fn serde(&mut self) -> &mut Self::SerDe; + fn info(&mut self) -> &mut Self::Info; } -#[cfg(feature = "default-impl")] -pub mod default_impl { - use super::*; - use crate::level::world_block::{BlockTransitionalState, WorldBlockTrait}; - use crate::types::miner::idx_3_to_1; - use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; - use nbtx::NbtError; - use std::io::{Cursor, Write}; - use std::marker::PhantomData; - use std::mem::MaybeUninit; - use thiserror::Error; - - pub struct SubChunkDecoderImpl { - _block_marker: PhantomData, - _state_marker: PhantomData, - } - - #[derive(Debug, Error)] - pub enum SubChunkDecoderError { - #[error("Missing Subchunk Version")] - SubChunkVersion, - #[error("Unknown Subchunk Version: {0}")] - UnknownVersion(u8), - #[error("Failed To Read Layer Count")] - LayerError, - #[error("Failed To Read Y Index")] - IndexError, - #[error("Failed To Read Palette Type")] - PaletteError, - #[error("Failed To Read Index Word")] - WordError, - #[error("Failed To Read Palette Count")] - PaletteCountError, - #[error("Failed To Slice NBT")] - SliceError, - #[error("Binary Error: {0}")] - BinaryError(#[from] std::io::Error), - #[error("NBT Error: {0}")] - NBTError(#[from] NbtError), - } - - impl, UserState> SubChunkDecoder - for SubChunkDecoderImpl - { - type Err = SubChunkDecoderError; - type BlockType = UserBlockType; - type UserState = UserState; - - fn decode_bytes_as_chunk( - bytes: &mut Cursor>, - state: &mut Self::UserState, - ) -> Result, Self::Err> { - let version = bytes.read_u8()?; - if version != 8 && version != 9 { - return Err(SubChunkDecoderError::UnknownVersion(version)); - } +impl SerDeTrait for SerDeStoreRef<'_, '_, SerDeType, TransitionType> { + type SerDe = SerDeType; + type Info = TransitionType; - let storage_layer_count = bytes.read_u8()?; - let y_index = bytes.read_i8()?; - - let mut transitiondata = - SubchunkTransitionalData::new(y_index, storage_layer_count as usize); - - for _ in 0..storage_layer_count { - let palette_type = bytes.read_u8()?; - let network = palette_type & 0x1 == 1; - let bits_per_block = palette_type >> 1; - let blocks_per_word = 32 / bits_per_block; - let word_count = (4096 + (blocks_per_word as i32) - 1) / (blocks_per_word as i32); - let mask = (1 << bits_per_block) - 1; - let mut pos = 0usize; - let mut block_indices = Box::new([0u16; 4096]); - - for _ in 0..word_count { - let mut word = bytes.read_u32::()?; - for _ in 0..blocks_per_word { - let index: u16 = (word & mask) as u16; - if pos == 4096 { - break; - } - block_indices[pos] = index; - word >>= bits_per_block; - pos += 1; - } - } + fn serde(&mut self) -> &mut Self::SerDe { + self.serde + } - let palette_count = bytes.read_u32::()?; - let mut blocks = Vec::with_capacity(palette_count as usize); - for _ in 0_usize..palette_count as usize { - if network { - blocks.push(Self::BlockType::from_transition( - nbtx::from_bytes::( - bytes, - )?, - state, - )); - } else { - blocks.push(Self::BlockType::from_transition( - nbtx::from_bytes::(bytes)?, - state, - )); - } - } - transitiondata.new_layer((block_indices, blocks)); - } - Ok(transitiondata) - } + fn info(&mut self) -> &mut Self::Info { + self.information + } +} - // TODO: Handle 0, 2, 3, 4 ,5 ,6 7, also handle 1 - fn write_as_bytes( - chunk_state: SubchunkTransitionalData, - network: bool, - state: &mut Self::UserState, - ) -> Result, Self::Err> { - let mut buffer = Cursor::>::new(vec![]); - buffer.write_u8(chunk_state.data_version)?; - buffer.write_u8(chunk_state.layers.len() as u8)?; - buffer.write_i8(chunk_state.y_level)?; - for layer in chunk_state.layers { - let bits_per_block = bits_needed_to_store(layer.1.len() as u32); - buffer.write_u8(bits_per_block << (1 + (network as u8)))?; - - let mut current_word = 0u32; - let mut bits_written = 0; - layer.0.iter().try_for_each(|element| { - let element = *element as u32; - if bits_written + bits_per_block > 32 { - buffer.write_u32::(current_word)?; - current_word = 0; - bits_written = 0; - } +impl SerDeTrait for SerDeStore { + type SerDe = SerDeType; + type Info = TransitionType; - current_word = current_word + (element << bits_written); - bits_written += bits_per_block; - Ok::<(), std::io::Error>(()) - })?; - if bits_written != 0 { - buffer.write_u32::(current_word)?; - } - buffer.write_u32::(layer.1.len() as u32)?; - for blk in layer.1 { - if network { - buffer.write(&nbtx::to_net_bytes(&blk.into_transition(state))?)? - } else { - buffer.write(&nbtx::to_le_bytes(&blk.into_transition(state))?)? - }; - } - } - Ok(buffer.into_inner()) - } + fn serde(&mut self) -> &mut Self::SerDe { + &mut self.serde } - pub struct SubChunk { - blocks: Vec>, - y_index: i8, - active_layer: u8, - is_empty: bool, - _state_tag: PhantomData, + fn info(&mut self) -> &mut Self::Info { + &mut self.information } +} - impl, UserState> - SubChunk - { - pub fn force_non_empty(&mut self) { - self.is_empty = false; +impl Default for SerDeStore { + fn default() -> Self { + Self { + serde: SerDeType::default(), + information: Transition::default(), } + } +} - pub fn force_empty(&mut self) { - self.is_empty = true; - } +/// The main trait that any sub chunk type must implement +pub trait SubChunkTrait: Sized { + type TransitionInformation; + type Err; + /// This must create a valid "full" state for the sub chunk from the transitional data + fn decode_from_transition( + data: SubChunkTransition, + dimension: Dimension, + info: &mut Self::TransitionInformation, + ) -> Result; - pub fn replace(&mut self, other: Self) { - self.blocks = other.blocks; - self.y_index = other.y_index; - self.active_layer = other.active_layer; - self.is_empty = other.is_empty; - } + /// This must create a transitional state from the current sub chunk information + fn to_transition(&self) -> Result; - pub fn full(y_index: i8, block: &UserBlockType) -> Self { - let mut val = Self { - blocks: Vec::with_capacity(1), - y_index, - active_layer: 0, - is_empty: true, - _state_tag: PhantomData, - }; - val.blocks - .push(Box::new(std::array::from_fn(|_| block.clone()))); - val + /// This returns if the sub chunk is just air if so nothing is written to the database if this isn't desired behavior just always return false + fn is_empty(&self) -> bool; +} + +/// An extension of the SubChunkTrait which must be implemented if the simple functions want to be called. Such as [`Level::write_sub_chunk`] +pub trait SubChunkTraitExtended: SubChunkTrait { + fn dimension(&self) -> Dimension; + fn position(&self) -> Vec3; +} + +#[derive(Default, Debug)] +pub struct SubChunkSerDe; + +#[derive(Debug, Error)] +pub enum SubChunkDecoderError { + #[error("Missing Subchunk Version")] + SubChunkVersion, + #[error("Unknown Subchunk Version: {0}")] + UnknownVersion(u8), + #[error("Failed To Read Layer Count")] + LayerError, + #[error("Failed To Read Y Index")] + IndexError, + #[error("Failed To Read Palette Type")] + PaletteError, + #[error("Failed To Read Index Word")] + WordError, + #[error("Failed To Read Palette Count")] + PaletteCountError, + #[error("Failed To Slice NBT")] + SliceError, + #[error("Binary Error: {0}")] + BinaryError(#[from] std::io::Error), + #[error("NBT Error: {0}")] + NBTError(#[from] NbtError), +} + +impl SubChunkDecoder for SubChunkSerDe { + type Err = SubChunkDecoderError; + + fn decode_bytes_as_sub_chunk( + &mut self, + bytes: &mut Cursor>, + xz: Vec2, + ) -> Result { + let version = bytes.read_u8()?; + if version != 8 && version != 9 { + return Err(SubChunkDecoderError::UnknownVersion(version)); } - pub fn fill( - &mut self, - block: &UserBlockType, - filter: SubchunkFillFilter, - ) -> Result<(), SubchunkFillError< as SubChunkTrait>::Err>> - { - for x in 0..16u8 { - for y in 0..16u8 { - for z in 0..16u8 { - let blk = self - .get_block((x, y, z).into()) - .ok_or(SubchunkFillError::BlockIndexDidntReturn(x, y, z))?; - if filter.may_place(blk, (x, y, z).into(), self.y_index) { - self.set_block((x, y, z).into(), block.clone()) - .map_err(|ele| SubchunkFillError::SubchunkError(ele))?; - } + let storage_layer_count = bytes.read_u8()?; + let y_index = bytes.read_i8()?; + + let mut transitiondata = SubChunkTransition::new( + Vec3::new(xz.x, y_index as i32, xz.y), + storage_layer_count as usize, + version, + ); + + for _ in 0..storage_layer_count { + let palette_type = bytes.read_u8()?; + let network = palette_type & 0x1 == 1; + let bits_per_block = palette_type >> 1; + + if bits_per_block == 0 { + break; // This signifies an empty sub chunk + } + + let blocks_per_word = 32 / bits_per_block; + let word_count = (4096 + (blocks_per_word as i32) - 1) / (blocks_per_word as i32); + let mask = (1 << bits_per_block) - 1; + let mut pos = 0usize; + let mut block_indices = Box::new([0u16; 4096]); + + for _ in 0..word_count { + let mut word = bytes.read_u32::()?; + for _ in 0..blocks_per_word { + let index: u16 = (word & mask) as u16; + if pos == 4096 { + break; } + block_indices[pos] = index; + word >>= bits_per_block; + pos += 1; } } - Ok(()) + let palette_count = bytes.read_u32::()?; + let mut blocks = Vec::with_capacity(palette_count as usize); + for _ in 0_usize..palette_count as usize { + if network { + blocks.push(nbtx::from_bytes::( + bytes, + )?); + } else { + blocks.push(nbtx::from_bytes::(bytes)?); + } + } + transitiondata.new_layer((block_indices, blocks)); } + Ok(transitiondata) } - #[derive(Debug, Error)] - pub enum SubChunkError { - #[error("Failed To Get Layer: {0}")] - LayerError(u8), - } +} - impl, UserState> SubChunkTrait - for SubChunk - { - type Err = SubChunkError; - type BlockType = UserBlockType; - type UserState = UserState; +impl SubChunkEncoder for SubChunkSerDe { + type Err = SubChunkDecoderError; - fn empty(y_index: i8, state: &mut Self::UserState) -> Self { - let mut val = Self { - blocks: Vec::with_capacity(1), - y_index, - active_layer: 0, - is_empty: true, - _state_tag: PhantomData, - }; - val.blocks.push(Box::new(std::array::from_fn(|_| { - Self::BlockType::air(state) - }))); - val - } - - fn decode_from_raw( - data: SubchunkTransitionalData, - _: &mut Self::UserState, - ) -> Result { - let mut layers: Vec; 4096]>> = (0..data.layers.len()) - .map(|_| Box::new([const { MaybeUninit::uninit() }; 4096])) - .collect(); - for (layer_index, (indices, blocks)) in data.layers.into_iter().enumerate() { - let layer: &mut Box<[MaybeUninit; 4096]> = &mut layers[layer_index]; - for whole_index in 0..4096usize { - layer[whole_index].write(Self::BlockType::from_other( - &blocks[indices[whole_index] as usize], - )); + // TODO: Handle 0, 2, 3, 4 ,5 ,6 7, also handle 1 + fn write_as_bytes( + &mut self, + chunk_state: SubChunkTransition, + network: bool, + ) -> Result, Self::Err> { + let mut buffer = Cursor::>::new(vec![]); + buffer.write_u8(chunk_state.data_version)?; + buffer.write_u8(chunk_state.layers.len() as u8)?; + buffer.write_i8(chunk_state.position.y as i8)?; + for layer in chunk_state.layers { + let bits_per_block = bits_needed_to_store(layer.1.len() as u32); + buffer.write_u8(bits_per_block << (1 + (network as u8)))?; + + let mut current_word = 0u32; + let mut bits_written = 0; + layer.0.iter().try_for_each(|element| { + let element = *element as u32; + if bits_written + bits_per_block > 32 { + buffer.write_u32::(current_word)?; + current_word = 0; + bits_written = 0; } - } - Ok(Self { - blocks: unsafe { std::mem::transmute(layers) }, - y_index: data.y_level, - active_layer: 0, - is_empty: false, - _state_tag: PhantomData, - }) - } - fn to_raw( - &self, - y_level: i8, - _: &mut Self::UserState, - ) -> Result, Self::Err> { - let mut layers: Vec> = - Vec::with_capacity(self.blocks.len()); - for layer in 0..self.blocks.len() { - layers.push(self.encode_single_layer(layer)); + current_word = current_word + (element << bits_written); + bits_written += bits_per_block; + Ok::<(), std::io::Error>(()) + })?; + if bits_written != 0 { + buffer.write_u32::(current_word)?; + } + buffer.write_u32::(layer.1.len() as u32)?; + for blk in layer.1 { + if network { + buffer.write(&nbtx::to_net_bytes(&blk)?)? + } else { + buffer.write(&nbtx::to_le_bytes(&blk)?)? + }; } - Ok(SubchunkTransitionalData { - layers, - data_version: 9, // TODO: Change this to be configurable - y_level, - }) } + Ok(buffer.into_inner()) + } +} - fn get_block(&self, xyz: Vec3) -> Option<&Self::BlockType> { - let layer: &[Self::BlockType; 4096] = self.blocks.get(self.active_layer as usize)?; - layer.get(idx_3_to_1::(xyz, 16u8, 16u8)) - } +#[derive(Clone, PartialEq)] +pub struct SubChunk { + blocks: Vec>, + position: Vec3, + dimension: Dimension, + active_layer: u8, + is_empty: bool, +} - fn get_block_mut(&mut self, xyz: Vec3) -> Option<&mut Self::BlockType> { - let layer: &mut [Self::BlockType; 4096] = - self.blocks.get_mut(self.active_layer as usize)?; - layer.get_mut(idx_3_to_1::(xyz, 16u8, 16u8)) - } +impl Debug for SubChunk { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "SubChunk(Blocks: (..., {} elements), position: {}, active_layer: {}, is_empty: {})", + self.blocks.len(), + self.position, + self.active_layer, + self.is_empty + ) + } +} - fn set_block(&mut self, xyz: Vec3, block: Self::BlockType) -> Result<(), Self::Err> { - let layer: &mut [Self::BlockType; 4096] = self - .blocks - .get_mut(self.active_layer as usize) - .ok_or(SubChunkError::LayerError(self.active_layer))?; - layer[idx_3_to_1::(xyz, 16u8, 16u8)] = block; - self.is_empty = false; - Ok(()) - } - fn get_active_layer(&self) -> u8 { - self.active_layer - } +impl SubChunk { + pub fn force_non_empty(&mut self) { + self.is_empty = false; + } - fn set_active_layer(&mut self, idx: u8) { - if idx as usize >= self.blocks.len() { - panic!( - "Selected subchunk index outside of valid range!, Layer Count: {}", - self.blocks.len() - ) - } - self.active_layer = idx; - } + pub fn force_empty(&mut self) { + self.is_empty = true; + } - fn add_sub_layer(&mut self, state: &mut Self::UserState) { - self.blocks.push(Box::new(std::array::from_fn(|_| { - Self::BlockType::air(state) - }))); - } + pub fn full(position: Vec3, dimension: Dimension, block: LevelBlock) -> Self { + let mut val = Self { + blocks: Vec::with_capacity(1), + position, + dimension, + active_layer: 0, + is_empty: false, + }; + val.blocks + .push(Box::new(std::array::from_fn(|_| block.clone()))); + val + } - fn get_sub_layer_count(&self) -> usize { - self.blocks.len() - } + pub fn empty(position: Vec3, dimension: Dimension) -> Self { + let mut val = Self { + blocks: Vec::with_capacity(1), + position, + dimension, + active_layer: 0, + is_empty: true, + }; + val.blocks + .push(Box::new(std::array::from_fn(|_| LevelBlock::air()))); + val + } - fn get_y(&self) -> i8 { - self.y_index - } + pub fn to_init(mut self) -> Self { + self.is_empty = false; + self + } - fn set_y(&mut self, y: i8) -> i8 { - self.y_index = y; - self.y_index - } + pub fn get_block(&self, xyz: Vec3) -> Option<&LevelBlock> { + let layer = self.blocks.get(self.active_layer as usize)?; + layer.get(idx_3_to_1::(xyz, 16u8, 16u8)) + } - fn state_clone(&self, _: &mut Self::UserState) -> Self { - Self { - blocks: self.blocks.clone(), - y_index: self.y_index, - active_layer: self.active_layer, - is_empty: self.is_empty, - _state_tag: PhantomData, - } - } + pub fn get_block_mut(&mut self, xyz: Vec3) -> Option<&mut LevelBlock> { + let layer = self.blocks.get_mut(self.active_layer as usize)?; + layer.get_mut(idx_3_to_1::(xyz, 16u8, 16u8)) + } - fn is_empty(&self) -> bool { - self.is_empty + pub fn set_block(&mut self, xyz: Vec3, block: LevelBlock) -> Result<(), SubChunkError> { + let layer = self + .blocks + .get_mut(self.active_layer as usize) + .ok_or(SubChunkError::LayerError(self.active_layer))?; + layer[idx_3_to_1::(xyz, 16u8, 16u8)] = block; + self.is_empty = false; + Ok(()) + } + + pub fn get_active_layer(&self) -> u8 { + self.active_layer + } + + pub fn set_active_layer(&mut self, idx: u8) { + if idx as usize >= self.blocks.len() { + panic!( + "Selected sub chunk index outside of valid range!, Layer Count: {}", + self.blocks.len() + ) } + self.active_layer = idx; + } + + pub fn add_sub_layer(&mut self) { + self.blocks + .push(Box::new(std::array::from_fn(|_| LevelBlock::air()))); + } + + pub fn get_sub_layer_count(&self) -> usize { + self.blocks.len() } - impl, UserState> - SubChunk - { - fn encode_single_layer(&self, layer_override: usize) -> BlockLayer { - let mut indices = Box::new([0u16; 4096]); - let mut unique_block_array = Vec::new(); - let layer = &self.blocks[layer_override]; - for z in 0..16u8 { - for y in 0..16u8 { - for x in 0..16u8 { - let current_block = &layer[idx_3_to_1((x, y, z).into(), 16, 16)]; - if let Some(index) = unique_block_array - .iter() - .position(|ele| ele == current_block) - { - indices[idx_3_to_1((x, y, z).into(), 16, 16)] = index as u16; - } else { - unique_block_array.push(current_block.clone()); - indices[idx_3_to_1((x, y, z).into(), 16, 16)] = - (unique_block_array.len() - 1) as u16; - } + pub fn encode_single_layer(&self, layer_override: usize) -> BlockLayer { + let mut indices = Box::new([0u16; 4096]); + let mut unique_block_array = Vec::new(); + let layer = &self.blocks[layer_override]; + for z in 0..16u8 { + for y in 0..16u8 { + for x in 0..16u8 { + let current_block = &layer[idx_3_to_1((x, y, z).into(), 16, 16)]; + if let Some(index) = unique_block_array + .iter() + .position(|ele| ele == current_block) + { + indices[idx_3_to_1((x, y, z).into(), 16, 16)] = index as u16; + } else { + unique_block_array.push(current_block.clone()); + indices[idx_3_to_1((x, y, z).into(), 16, 16)] = + (unique_block_array.len() - 1) as u16; } } } - (indices, unique_block_array) } + (indices, unique_block_array) + } +} +impl SubChunkTraitExtended for SubChunk { + fn dimension(&self) -> Dimension { + self.dimension } - impl Clone for SubChunk { - fn clone(&self) -> Self { - Self { - active_layer: self.active_layer, - _state_tag: PhantomData, - blocks: self.blocks.clone(), - is_empty: self.is_empty, - y_index: self.y_index.clone(), + fn position(&self) -> Vec3 { + self.position + } +} + +impl SubChunkTrait for SubChunk { + type TransitionInformation = (); + type Err = (); + + fn decode_from_transition( + data: SubChunkTransition, + dimension: Dimension, + _: &mut Self::TransitionInformation, + ) -> Result { + let mut layers: Vec; 4096]>> = (0..data.layers.len()) + .map(|_| Box::new([const { MaybeUninit::uninit() }; 4096])) + .collect(); + for (layer_index, (indices, blocks)) in data.layers.into_iter().enumerate() { + let layer: &mut Box<[MaybeUninit; 4096]> = &mut layers[layer_index]; + for whole_index in 0..4096usize { + layer[whole_index].write(blocks[indices[whole_index] as usize].clone()); } } + + if layers.is_empty() { + Ok(Self::empty(data.position, dimension)) + } else { + let layers = unsafe { std::mem::transmute(layers) }; + + Ok(Self { + blocks: layers, + position: data.position, + dimension, + active_layer: 0, + is_empty: false, + }) + } + } + + fn to_transition(&self) -> Result { + let mut layers: Vec> = Vec::with_capacity(self.blocks.len()); + for layer in 0..self.blocks.len() { + layers.push(self.encode_single_layer(layer)); + } + Ok(SubChunkTransition { + layers, + data_version: 9, // TODO: Change this to be configurable + position: self.position, + }) + } + + fn is_empty(&self) -> bool { + self.is_empty } } fn bits_needed_to_store(val: u32) -> u8 { - if val == 0 { + if val <= 1 { 1 } else { - (32 - val.leading_zeros()) as u8 + if val.count_ones() == 1 { + // In binary, we might have something like + // 001 + // That is 4. We only need 2 bits + // to store it since 0,1,2,3 is 4 combinations. + // So to compute that we just count how may trailing zeros it has + + val.trailing_zeros() as u8 + } else { + 32 - val.leading_zeros() as u8 + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn verify_store_sanity() { + let test_data = [(2, 1), (4, 2), (3, 2), (5, 3), (8, 3)]; + + for (input, expected) in test_data { + let stored = bits_needed_to_store(input); + + assert_eq!(expected, stored); + } } } diff --git a/crates/level/src/level/world_block.rs b/crates/level/src/level/world_block.rs index bd14019a..fcd33c04 100644 --- a/crates/level/src/level/world_block.rs +++ b/crates/level/src/level/world_block.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use vek::{Vec2, Vec3}; #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(untagged)] @@ -10,95 +11,102 @@ pub enum BlockStateValue { } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -pub struct BlockTransitionalState { - name: String, - states: HashMap, +pub struct LevelBlock { + // If the name of anything in this struct changes, the world ends + pub name: String, + pub states: HashMap, + #[serde(default)] + // Honestly? I haven't the faintest idea what this does + pub version: i32, } -pub trait WorldBlockTrait: Clone + PartialEq { - type UserState; - fn from_transition(value: BlockTransitionalState, state: &mut Self::UserState) -> Self; - fn into_transition(self, state: &mut Self::UserState) -> BlockTransitionalState; - - fn get_id(&self) -> &str; - fn air(state: &mut Self::UserState) -> Self; - fn from_other(other: &Self) -> Self { - other.clone() - } -} - -#[cfg(feature = "default-impl")] -pub mod default_impl { - use super::*; - use std::fmt::Formatter; - use std::marker::PhantomData; - - pub struct WorldBlock { - id: String, - pub states: HashMap, - _state_marker: PhantomData, - } - - impl WorldBlock { - pub fn new(id: String) -> Self { - Self { - id, - states: HashMap::new(), - _state_marker: PhantomData, - } +impl LevelBlock { + pub fn air() -> Self { + Self { + name: String::from("minecraft:air"), + states: HashMap::new(), + version: 18163713, } } - impl<'a, UserState> PartialEq for WorldBlock { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.states == other.states + pub fn id(id: String) -> Self { + Self { + name: id, + states: HashMap::new(), + version: 18163713, } } - impl WorldBlockTrait for WorldBlock { - type UserState = UserState; + pub fn block_pos_to_sub_chunk(pos: Vec3) -> (Vec3, Vec3) { + let sub_chunk_y = pos.y >> 4; + let sub_chunk_y_inside = pos.y & 0xF; - fn from_transition(value: BlockTransitionalState, _: &mut Self::UserState) -> Self { - Self { - id: value.name.clone(), - states: value.states.clone(), - _state_marker: PhantomData, - } - } + let chunk_xz = Vec2::new( + pos.x >> 4, + pos.z >> 4, // Stolen from the game + ); - fn into_transition(self, _: &mut Self::UserState) -> BlockTransitionalState { - BlockTransitionalState { - name: self.id, - states: self.states, - } - } + let sub_chunk_xz = Vec2::new(pos.x & 0xF, pos.z & 0xF); - fn get_id(&self) -> &str { - &self.id - } + ( + (chunk_xz.x, sub_chunk_y, chunk_xz.y).into(), + ( + sub_chunk_xz.x as u8, + sub_chunk_y_inside as u8, + sub_chunk_xz.y as u8, + ) + .into(), + ) + } - fn air(_: &mut Self::UserState) -> Self { - Self { - id: String::from("minecraft:air"), - states: HashMap::new(), - _state_marker: PhantomData, - } + fn interior_position(pos: i32) -> u8 { + if pos >= 0 { + (pos % 16) as u8 + } else { + (pos % 16 + 16) as u8 } } +} - impl std::fmt::Debug for WorldBlock { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "WorldBlock(ID: {}, States: {:?})", self.id, self.states) +impl, StateType: Into>> + From<(IdType, StateType)> for LevelBlock +{ + fn from((id, states): (IdType, StateType)) -> Self { + Self { + name: id.into(), + states: states.into(), + version: 18163713, } } +} - impl std::clone::Clone for WorldBlock { - fn clone(&self) -> Self { - Self { - id: self.id.clone(), - states: self.states.clone(), - _state_marker: PhantomData, - } - } +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn position_translation_tests() { + // Test values stolen from minecraft java's debug menu + + assert_eq!( + LevelBlock::block_pos_to_sub_chunk((117, 73, -163).into()), + ((7, 4, -11).into(), (5, 9, 13).into()) + ); + assert_eq!( + LevelBlock::block_pos_to_sub_chunk((117, -53, -163).into()), + ((7, -4, -11).into(), (5, 11, 13).into()) + ); + assert_eq!( + LevelBlock::block_pos_to_sub_chunk((164, -39, -219).into()), + ((10, -3, -14).into(), (4, 9, 5).into()) + ); + assert_eq!( + LevelBlock::block_pos_to_sub_chunk((0, -3, 0).into()), + ((0, -1, 0).into(), (0, 13, 0).into()) + ); + assert_eq!( + LevelBlock::block_pos_to_sub_chunk((16, -16, 16).into()), + ((1, -1, 1).into(), (0, 0, 0).into()) + ); } } diff --git a/crates/level/src/lib.rs b/crates/level/src/lib.rs index 615351ff..f01b0f53 100644 --- a/crates/level/src/lib.rs +++ b/crates/level/src/lib.rs @@ -1,4 +1,4 @@ extern crate core; pub mod level; -mod types; +pub mod utility; diff --git a/crates/level/src/types/clear_cache.rs b/crates/level/src/types/clear_cache.rs deleted file mode 100644 index ef931789..00000000 --- a/crates/level/src/types/clear_cache.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::collections::HashMap; -use std::hash::Hash; - -#[derive(Debug, PartialEq)] -struct ClearCacheContainerValue { - internal: CachedValue, - sequence_id: usize, -} - -impl ClearCacheContainerValue { - pub fn new(internal: CachedValue, sequence_id: usize) -> Self { - Self { - internal, - sequence_id, - } - } -} - -pub struct ClearCacheContainer { - sequence_id: usize, - pub clear_threshold: usize, - cache_information: HashMap>, -} - -#[allow(dead_code)] -impl ClearCacheContainer { - pub fn new() -> Self { - Self { - sequence_id: 0, - clear_threshold: 2048, - cache_information: HashMap::new(), - } - } - - pub fn with_threshold(clear_threshold: usize) -> Self { - Self { - sequence_id: 0, - clear_threshold, - cache_information: HashMap::new(), - } - } - - pub fn get(&self, key: &KeyType) -> Option<&CachedValue> { - Some(&self.cache_information.get(key)?.internal) - } - - pub fn insert(&mut self, key: KeyType, val: CachedValue) { - let idx = self.next_seq(); - let _ = self - .cache_information - .insert(key, ClearCacheContainerValue::new(val, idx)); // Drops the old value - } - - pub fn is_cached(&self, key: &KeyType) -> bool { - self.get(key).map_or_else(|| false, |_| true) - } - - pub fn len(&self) -> usize { - self.cache_information.len() - } - - pub fn seq_id(&self) -> usize { - self.sequence_id - } - - fn next_seq(&mut self) -> usize { - self.sequence_id += 1; - self.sequence_id - 1 - } - - pub fn cull Result<(), E>>( - &mut self, - mut on_culled: F, - ) -> Result<(), E> { - if self.sequence_id != 0 && self.sequence_id % self.clear_threshold != 0 { - return Ok(()); - } - let max_distance = self.clear_threshold / 10; // This grabs the maximum distance the seq ID can be and still be kept in the cache - let dropped_keys = self - .cache_information - .iter() - .filter_map(|(key, val)| { - if self.sequence_id - val.sequence_id > max_distance { - Some(key.clone()) - } else { - None - } - }) - .collect::>(); - - { - for key in dropped_keys { - let information = self.cache_information.remove(&key).unwrap(); - on_culled(key, information.internal)?; - } - } - - Ok(()) - } - - pub fn clear Result<(), E>>( - &mut self, - mut on_culled: F, - ) -> Result<(), E> { - for (key, val) in self.cache_information.drain() { - on_culled(key, val.internal)?; - } - Ok(()) - } -} diff --git a/crates/level/src/types/miner.rs b/crates/level/src/types/miner.rs deleted file mode 100644 index d4cdc147..00000000 --- a/crates/level/src/types/miner.rs +++ /dev/null @@ -1,29 +0,0 @@ -use vek::Vec3; - -#[allow(dead_code)] -pub fn idx_3_to_1>(vec: Vec3, width: T, height: T) -> usize -where - usize: From, -{ - (usize::from(vec.x) - + usize::from(vec.y) * usize::from(width) - + usize::from(vec.z) * usize::from(width) * usize::from(height)) - .into() -} - -#[allow(dead_code)] -pub fn in_bounds(min: T, max: T, val: T) -> bool { - val >= min && val <= max -} - -#[macro_export] -macro_rules! level_try { - ($name:ident, $expr:expr $(,)?) => { - match $expr { - Ok(val) => val, - Err(err) => { - return Err($crate::level::level::LevelError::$name(err)); - } - } - }; -} diff --git a/crates/level/src/types/mod.rs b/crates/level/src/types/mod.rs deleted file mode 100644 index eb00f4af..00000000 --- a/crates/level/src/types/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod clear_cache; -pub mod miner; diff --git a/crates/level/src/utility/miner.rs b/crates/level/src/utility/miner.rs new file mode 100644 index 00000000..d5688bbf --- /dev/null +++ b/crates/level/src/utility/miner.rs @@ -0,0 +1,60 @@ +use std::cmp::{max, min}; +use std::io; +use std::io::Write; +use vek::Vec3; + +pub fn idx_3_to_1 + vek::num_traits::AsPrimitive>( + vec: Vec3, + height: T, + depth: T, +) -> usize +where + usize: From, +{ + let vec = vec.as_::(); + let x = vec.x; + let y = vec.y; + let z = vec.z; + let height = usize::from(height); + let depth = usize::from(depth); + + x * depth * height + z * height + y +} +/// Checks EXCLUSIVELY if something is in range +pub fn in_range(min: T, max: T, val: T) -> bool { + min < val && val < max +} + +pub fn bounds_left_optimized(lhs: Vec3, rhs: Vec3) -> (Vec3, Vec3) { + let smallest_new_x = min(lhs.x.clone(), rhs.x.clone()); + let smallest_new_y = min(lhs.y.clone(), rhs.y.clone()); + let smallest_new_z = min(lhs.z.clone(), rhs.z.clone()); + + let largest_new_x = max(lhs.x, rhs.x); + let largest_new_y = max(lhs.y, rhs.y); + let largest_new_z = max(lhs.z, rhs.z); + + ( + (smallest_new_x, smallest_new_y, smallest_new_z).into(), + (largest_new_x, largest_new_y, largest_new_z).into(), + ) +} + +#[cfg(test)] +mod test { + use crate::utility::miner::bounds_left_optimized; + use vek::Vec3; + + #[test] + fn bounds_left() { + assert_eq!( + bounds_left_optimized(Vec3::new(-3, -3, -3), Vec3::new(5, 5, 5)), + (Vec3::new(-3, -3, -3), Vec3::new(5, 5, 5)) + ); + + assert_eq!( + bounds_left_optimized(Vec3::new(10, -3, -3), Vec3::new(5, 5, 5)), + (Vec3::new(5, -3, -3), Vec3::new(10, 5, 5)) + ); + } +} diff --git a/crates/level/src/utility/mod.rs b/crates/level/src/utility/mod.rs new file mode 100644 index 00000000..25588127 --- /dev/null +++ b/crates/level/src/utility/mod.rs @@ -0,0 +1 @@ +pub mod miner; diff --git a/crates/level/test_level/db.7z b/crates/level/test_level/db.7z new file mode 100644 index 00000000..5771279f Binary files /dev/null and b/crates/level/test_level/db.7z differ diff --git a/crates/level/test_level/db/000010.log b/crates/level/test_level/db/000010.log new file mode 100644 index 00000000..cad5b045 Binary files /dev/null and b/crates/level/test_level/db/000010.log differ diff --git a/crates/level/test_level/db/000011.ldb b/crates/level/test_level/db/000011.ldb new file mode 100644 index 00000000..3e510925 Binary files /dev/null and b/crates/level/test_level/db/000011.ldb differ diff --git a/crates/level/test_level/db/000044.ldb b/crates/level/test_level/db/000044.ldb deleted file mode 100644 index 03a17383..00000000 Binary files a/crates/level/test_level/db/000044.ldb and /dev/null differ diff --git a/crates/level/test_level/db/CURRENT b/crates/level/test_level/db/CURRENT index 8ee0a8e4..875cf233 100644 --- a/crates/level/test_level/db/CURRENT +++ b/crates/level/test_level/db/CURRENT @@ -1 +1 @@ -MANIFEST-000041 +MANIFEST-000007 diff --git a/crates/level/test_level/db/LOG.old b/crates/level/test_level/db/LOG.old new file mode 100644 index 00000000..06ad2544 --- /dev/null +++ b/crates/level/test_level/db/LOG.old @@ -0,0 +1,4 @@ +Recovered manifest with next_file=5 manifest_num=4 log_num=3 prev_log_num=0 last_seq=0 +reusing manifest "C:\\Users\\Duckos\\AppData\\Roaming\\.minecraft_bedrock\\installations\\Duckos\\Latest Release\\packageData\\minecraftWorlds\\7qAFhawYI3c=\\db\\MANIFEST-000002" +Recovering log file "\\\\?\\C:\\Users\\Duckos\\AppData\\Roaming\\.minecraft_bedrock\\installations\\Duckos\\Latest Release\\packageData\\minecraftWorlds\\7qAFhawYI3c=\\db\\000003.log" +reusing log file "\\\\?\\C:\\Users\\Duckos\\AppData\\Roaming\\.minecraft_bedrock\\installations\\Duckos\\Latest Release\\packageData\\minecraftWorlds\\7qAFhawYI3c=\\db\\000003.log" diff --git a/crates/level/test_level/db/MANIFEST-000007 b/crates/level/test_level/db/MANIFEST-000007 new file mode 100644 index 00000000..2b690e66 Binary files /dev/null and b/crates/level/test_level/db/MANIFEST-000007 differ diff --git a/crates/level/test_level/db/MANIFEST-000041 b/crates/level/test_level/db/MANIFEST-000041 deleted file mode 100644 index 67575ee1..00000000 Binary files a/crates/level/test_level/db/MANIFEST-000041 and /dev/null differ diff --git a/crates/level/test_level/level.dat b/crates/level/test_level/level.dat.bak similarity index 100% rename from crates/level/test_level/level.dat rename to crates/level/test_level/level.dat.bak diff --git a/crates/level/test_level/level.dat_old b/crates/level/test_level/level.dat_old new file mode 100644 index 00000000..24723e84 Binary files /dev/null and b/crates/level/test_level/level.dat_old differ diff --git a/crates/level/test_level/levelname.txt b/crates/level/test_level/levelname.txt index b953ea65..bef4b774 100644 --- a/crates/level/test_level/levelname.txt +++ b/crates/level/test_level/levelname.txt @@ -1 +1 @@ -My World \ No newline at end of file +DDDDDDDD \ No newline at end of file diff --git a/crates/level/test_level/world_icon.jpeg b/crates/level/test_level/world_icon.jpeg index a1bfcdfe..f4658959 100644 Binary files a/crates/level/test_level/world_icon.jpeg and b/crates/level/test_level/world_icon.jpeg differ diff --git a/crates/level/tests/api_test.rs b/crates/level/tests/api_test.rs index bfc39c26..f83d42fe 100644 --- a/crates/level/tests/api_test.rs +++ b/crates/level/tests/api_test.rs @@ -1,92 +1,55 @@ -use bedrockrs_level::level::chunk::{FillFilter, LevelChunkTrait}; -use bedrockrs_level::level::level::default_impl::*; -use bedrockrs_level::level::level::{ChunkSelectionFilter, LevelConfiguration}; -use bedrockrs_level::level::sub_chunk::SubchunkFillFilter; +use bedrockrs_level::level::level::{ + BedrockLevel, ChunkSelectionFilter, FillConfig, GetBlockConfig, LevelConfiguration, + LevelDbError, SetBlockConfig, +}; +use bedrockrs_level::level::sub_chunk::{ + SerDeStore, SubChunk, SubChunkSerDe, SubChunkTraitExtended, +}; +use bedrockrs_level::level::world_block::LevelBlock; use bedrockrs_shared::world::dimension::Dimension; + +// Needed for the test only +use bedrockrs_level::level::file_interface::RawWorldTrait; +use bedrockrs_shared::world::dimension::Dimension::Overworld; use copy_dir::copy_dir; +use rusty_leveldb::LdbIterator; use std::path::Path; #[cfg(feature = "default-impl")] -fn get_level_with_copy( -) -> Result> -{ - let _ = std::fs::remove_dir_all("./test_level_temp"); // If this errors its fine +fn get_level_with_copy() -> Result { + let _ = std::fs::remove_dir_all("./test_level_temp"); // If this throws an error its fine copy_dir("./test_level", "./test_level_temp").unwrap(); BedrockLevel::open( Box::from(Path::new("./test_level_temp")), LevelConfiguration::default(), - BedrockState {}, + &mut (), ) } +fn dump_keys(level: &mut BedrockLevel) { + level.underlying_world_interface().db.flush().unwrap(); + let mut iter = level.underlying_world_interface().db.new_iter().unwrap(); + iter.seek_to_first(); + + println!("dump start"); + while let Some((k, v)) = iter.next() { + println!("{:?} {}", k, v.len()); + } + println!("dump end"); +} + #[cfg(feature = "default-impl")] #[test] -fn world_test( -) -> Result<(), BedrockLevelError> { - let wld_path = "./test_level/db"; - +fn world_test() -> Result<(), anyhow::Error> { println!("Loading World"); let mut level = get_level_with_copy()?; - println!("Collecting Chunks"); - let chunks = level.get_chunk_keys(ChunkSelectionFilter::Dimension(Dimension::Overworld)); - - println!("Collected {} Chunks!", chunks.len()); - - let blks = [ - BedrockWorldBlock::new("minecraft:iron_block".to_string()), - BedrockWorldBlock::new("minecraft:diamond_block".to_string()), - ]; - let len = chunks.len(); + let exists = level.chunk_exists((0, 0).into(), Overworld); - println!("Filling Chunks"); - for (idx, key) in chunks.into_iter().enumerate() { - let mut chunk = BedrockChunk::empty( - key, - (-4, 20).into(), - Dimension::Overworld, - &mut BedrockState {}, - ); - - for blk in &blks { - chunk - .fill_chunk( - blk.clone(), - FillFilter::Precedence(Box::new(|_, _, _, _| rand::random::() > 0.5)), - ) - .unwrap(); - } - - chunk.write_to_world(&mut level, None, None).unwrap(); - - if idx % (len / 10 + 1) == 0 { - println!("Wrote {idx} out of {len} chunks!"); - } + if !exists { + panic!("Expected chunk doesnt exist!") } - let mut chunk = level.get_chunk::((0, 0).into(), Dimension::Overworld)?; - - chunk - .fill_chunk( - BedrockWorldBlock::new("minecraft:diamond_block".into()), - FillFilter::Precedence(Box::new(|_, _, _, y| y == 0)), - ) - .unwrap(); - level.set_chunk(chunk)?; - - let mut chunk = level.get_chunk::((0, -1).into(), Dimension::Overworld)?; - - let subchunk = chunk.get_subchunk_mut(0).unwrap(); - subchunk - .fill( - &BedrockWorldBlock::new("minecraft:diamond_block".into()), - SubchunkFillFilter::Blanket, - ) - .unwrap(); - - level.set_chunk(chunk)?; - - level.close()?; Ok(()) }