diff --git a/Cargo.toml b/Cargo.toml index 0b628b2b..80341233 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ bedrockrs_proto_core = { path = "crates/proto_core", optional = true } bedrockrs_server = { path = "crates/server", optional = true } -bedrockrs_level = {path = "crates/level", optional = true} +bedrockrs_level = { path = "crates/level", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } @@ -35,14 +35,18 @@ log = "0.4" chrono = "0.4" [features] +default = ["full"] addon = ["dep:bedrockrs_addon"] -proto = ["dep:bedrockrs_proto","dep:bedrockrs_proto_core","dep:bedrockrs_macros",] -level = ["dep:bedrockrs_level"] -full = ["addon", "level", "proto", "server"] form = ["dep:bedrockrs_form"] +proto = ["dep:bedrockrs_proto", "dep:bedrockrs_proto_core", "dep:bedrockrs_macros", ] +level = ["dep:bedrockrs_level", "level-default"] server = ["dep:bedrockrs_server", "proto", "level", "form"] +level-default = ["bedrockrs_level/default-impl"] + +full = ["addon", "form", "proto", "level", "server", "level-default"] + [[example]] name = "proto_server" path = "examples/proto/server.rs" @@ -52,3 +56,4 @@ required-features = ["proto"] name = "proto_parsing" path = "examples/proto_parsing.rs" required-features = ["proto"] + diff --git a/crates/level/Cargo.toml b/crates/level/Cargo.toml index ae5aafe1..fa4efd62 100644 --- a/crates/level/Cargo.toml +++ b/crates/level/Cargo.toml @@ -4,28 +4,29 @@ version = "0.1.0" edition = "2021" [dependencies] -bedrockrs_core = { path = "../core" } bedrockrs_shared = { path = "../shared" } nbtx = { git = "https://github.com/bedrock-crustaceans/nbtx" } + thiserror = "2.0" -byteorder = "1.x" -uuid = { version = "1.x", features = ["v4"] } +byteorder = "1.5" +uuid = { version = "1.11", features = ["v4"] } bytemuck = { version = "1.19", features = ["must_cast"] } -len-trait = "0.6" -concat-idents = "1.x" -serde = "1.x.x" -rusty-leveldb = "3.x" +serde = "1.0" +rusty-leveldb = "3.0" miniz_oxide = "0.8" vek = "0.17" [dev-dependencies] rand = "0.8" -[features] -default-impl = [] [[test]] name = "api_test" -required-features = ["default-impl"] + + +[features] +default-impl = [] +default = ["default-impl"] + diff --git a/crates/level/src/level/chunk.rs b/crates/level/src/level/chunk.rs index 110cb9e4..6a110c74 100644 --- a/crates/level/src/level/chunk.rs +++ b/crates/level/src/level/chunk.rs @@ -31,7 +31,7 @@ pub enum FillFilter { /// # Parameters /// - A boxed function with the following parameters: /// - `&UserBlockType`: The current block being evaluated. - /// - `Vec3`: The local coordinates of the block within the chunk. + /// - `Vec3`: The local coordinates of the block within the current subchunk. /// - `Vec2`: The world-space XZ coordinates of the chunk. /// - `i8`: The subchunk y. /// @@ -40,6 +40,17 @@ pub enum FillFilter { 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")] @@ -382,15 +393,7 @@ pub mod default_impl { let blk = subchunk .get_block((x, y, z).into()) .ok_or(FillError::BlockIndexDidntReturn(x, y, z))?; - if match &filter { - FillFilter::Blanket => true, - FillFilter::Replace(mask) => - mask == blk, - FillFilter::Avoid(mask) => mask != blk, - FillFilter::Precedence(func) => { - func(blk, (x, y, z).into(), pos, subchunk.get_y()) - } - } { + if filter.may_place(blk, (x, y, z).into(), pos, y_level) { subchunk.set_block((x, y, z).into(), block.clone()).unwrap() } } diff --git a/crates/level/src/level/db_interface/bedrock_key.rs b/crates/level/src/level/db_interface/bedrock_key.rs index c7afcea3..8f157e7a 100644 --- a/crates/level/src/level/db_interface/bedrock_key.rs +++ b/crates/level/src/level/db_interface/bedrock_key.rs @@ -49,7 +49,7 @@ impl LevelDBKey for ChunkKey { size += std::mem::size_of::(); } size += 1; // For the key_type - if let Some(_) = self.y_index { + if self.y_index.is_some() { size += 1; } size diff --git a/crates/level/src/level/db_interface/rusty.rs b/crates/level/src/level/db_interface/rusty.rs index 4ee00137..defe29b6 100644 --- a/crates/level/src/level/db_interface/rusty.rs +++ b/crates/level/src/level/db_interface/rusty.rs @@ -113,11 +113,17 @@ impl RustyDBInterface { } } +impl Drop for RustyDBInterface { + fn drop(&mut self) { + self.db.close().unwrap(); + } +} + impl RawWorldTrait for RustyDBInterface { type Err = DBError; type UserState = UserState; - fn set_subchunk_raw( + fn write_bytes_to_key( &mut self, chunk_info: ChunkKey, chunk_bytes: &[u8], @@ -128,7 +134,7 @@ impl RawWorldTrait for RustyDBInterface { Ok(self.db.write(batch, false)?) } - fn get_subchunk_raw( + fn get_bytes_from_key( &mut self, chunk_info: ChunkKey, _: &mut Self::UserState, @@ -136,12 +142,12 @@ impl RawWorldTrait for RustyDBInterface { Ok(self.db.get(&Self::build_key(&chunk_info))) } - fn chunk_exists( + fn delete_bytes_at_key( &mut self, chunk_info: ChunkKey, _: &mut Self::UserState, - ) -> Result { - Ok(self.db.get(&Self::build_key(&chunk_info)).is_some()) + ) -> Result<(), Self::Err> { + Ok(self.db.delete(&Self::build_key(&chunk_info))?) } fn write_subchunk_batch( @@ -182,14 +188,6 @@ impl RawWorldTrait for RustyDBInterface { Ok(self.db.write(batch, false)?) } - fn exist_chunk( - &mut self, - chunk_info: ChunkKey, - state: &mut Self::UserState, - ) -> Result<(), Self::Err> { - Ok(self.set_subchunk_raw(chunk_info, &[], state)?) - } - fn build_key(key: &ChunkKey) -> Vec { let mut key_bytes: Vec = vec![0; key.estimate_size()]; let mut buff: Cursor<&mut [u8]> = Cursor::new(&mut key_bytes); @@ -211,6 +209,11 @@ impl RawWorldTrait for RustyDBInterface { }) } + fn close(&mut self) -> Result<(), Self::Err> { + self.db.close()?; + Ok(()) + } + fn generated_chunks( &mut self, _: &mut Self::UserState, diff --git a/crates/level/src/level/file_interface.rs b/crates/level/src/level/file_interface.rs index 376e919c..15330a30 100644 --- a/crates/level/src/level/file_interface.rs +++ b/crates/level/src/level/file_interface.rs @@ -34,24 +34,49 @@ pub trait RawWorldTrait: Sized { type UserState; - fn set_subchunk_raw( + fn write_bytes_to_key( &mut self, chunk_info: ChunkKey, chunk_bytes: &[u8], state: &mut Self::UserState, ) -> Result<(), Self::Err>; - fn get_subchunk_raw( + fn get_bytes_from_key( &mut self, chunk_info: ChunkKey, state: &mut Self::UserState, ) -> Result>, Self::Err>; + fn delete_bytes_at_key( + &mut self, + chunk_info: ChunkKey, + state: &mut Self::UserState, + ) -> Result<(), Self::Err>; + + fn set_subchunk_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) + } + + 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 chunk_exists( &mut self, chunk_info: ChunkKey, state: &mut Self::UserState, - ) -> Result; + ) -> Result { + Ok(self.get_bytes_from_key(chunk_info, state)?.is_some()) + } fn write_subchunk_batch( &mut self, @@ -65,11 +90,13 @@ pub trait RawWorldTrait: Sized { state: &mut Self::UserState, ) -> Result<(), Self::Err>; - fn exist_chunk( + fn mark_exist_chunk( &mut self, chunk_info: ChunkKey, state: &mut Self::UserState, - ) -> Result<(), Self::Err>; + ) -> Result<(), Self::Err> { + self.write_bytes_to_key(chunk_info, &[], state) + } fn build_key(key: &ChunkKey) -> Vec; @@ -79,8 +106,36 @@ pub trait RawWorldTrait: Sized { 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 delete_chunk( + &mut self, + xz: Vec2, + dimension: Dimension, + subchunk_range: Vec2, + state: &mut Self::UserState, + ) -> Result<(), Self::Err> { + for y in subchunk_range.x..=subchunk_range.y { + self.delete_subchunk(xz, dimension, y, state)? + } + + self.delete_bytes_at_key(ChunkKey::chunk_marker(xz, dimension), state)?; + + 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) + } } diff --git a/crates/level/src/level/level.rs b/crates/level/src/level/level.rs index dfab8791..042be833 100644 --- a/crates/level/src/level/level.rs +++ b/crates/level/src/level/level.rs @@ -54,7 +54,22 @@ pub enum LevelError, + pub rw_cache: bool, + pub create_db_if_missing: bool, +} + +impl Default for LevelConfiguration { + fn default() -> Self { + Self { + sub_chunk_range: (-4, 20).into(), + rw_cache: false, + create_db_if_missing: false, + } + } +} #[allow(dead_code)] pub struct Level< @@ -66,7 +81,7 @@ pub struct Level< > { db: UserWorldInterface, state: UserState, - rw_cache: bool, + config: LevelConfiguration, cached_sub_chunks: ClearCacheContainer, chunk_existence: HashSet<(Dimension, Vec2)>, _block_type_marker: PhantomData, @@ -89,17 +104,17 @@ where /// Simple function used to open the world pub fn open( path: Box, - create_db_if_missing: bool, - rw_cache: bool, + config: LevelConfiguration, mut state: UserState, ) -> Result< Self, LevelError, > { let db = level_try!(DatabaseError, { - let val = UserWorldInterface::new(path.clone(), create_db_if_missing, &mut state); - if val.is_ok() { - Ok(val.unwrap()) + let val = + UserWorldInterface::new(path.clone(), config.create_db_if_missing, &mut state); + if let Ok(v) = val { + Ok(v) } else { UserWorldInterface::new( { @@ -107,7 +122,7 @@ where buff.push("db"); buff.into_boxed_path() }, - create_db_if_missing, + config.create_db_if_missing, &mut state, ) } @@ -115,7 +130,7 @@ where let mut this = Self { db, state, - rw_cache, + config, cached_sub_chunks: ClearCacheContainer::with_threshold(1024), chunk_existence: HashSet::new(), _block_type_marker: PhantomData, @@ -137,14 +152,51 @@ where &mut self.db } + 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()) + } + + pub fn remove_chunk_ex( + &mut self, + xz: Vec2, + dimension: Dimension, + subchunk_range: Vec2, + ) -> Result<(), UserWorldInterface::Err> { + self.db + .delete_chunk(xz, dimension, subchunk_range, &mut self.state) + } + + 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) + } + /// Checks if a given chunk exists pub fn chunk_exists(&mut self, xz: Vec2, dimension: Dimension) -> bool { self.chunk_existence.contains(&(dimension, xz)) } /// Must call before destruction - pub fn close(mut self) { - self.cull().unwrap(); + pub fn close( + mut self, + ) -> Result< + (), + LevelError, + > { + 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(()) } /// Returns all chunks (in the form of its key) that exist in the world @@ -200,7 +252,7 @@ where UserSubChunkType, LevelError, > { - if self.rw_cache { + if self.config.rw_cache { if let Some(chunk) = self .cached_sub_chunks .get(&SubchunkCacheKey::new(xz, y, dim)) @@ -244,7 +296,7 @@ where } } }?; - if self.rw_cache { + if self.config.rw_cache { if let Some(data) = &out.1 { let new = data.state_clone(&mut self.state); self.cached_sub_chunks @@ -269,7 +321,7 @@ where (), LevelError, > { - if self.rw_cache { + if self.config.rw_cache { self.cached_sub_chunks .insert(SubchunkCacheKey::new(xz, y, dim), data); level_try!(SubChunkDecodeError, self.perform_flush()); @@ -295,7 +347,7 @@ where /// 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>( + pub fn set_chunk_ex>( &mut self, chnk: UserChunkType, xz_override: Option>, @@ -304,10 +356,18 @@ where chnk.write_to_world(self, xz_override, dim_override) } + /// Sets a whole chunk in the saved position of the chunk and the saved dimension. + pub fn set_chunk>( + &mut self, + chnk: UserChunkType, + ) -> Result<(), UserChunkType::Err> { + self.set_chunk_ex(chnk, None, None) + } + /// 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< + pub fn get_chunk_ex< UserChunkType: LevelChunkTrait< Self, UserLevel = Self, @@ -319,13 +379,34 @@ where >, >( &mut self, - min_max: Vec2, xz: Vec2, dim: Dimension, + min_max: Vec2, ) -> Result { UserChunkType::load_from_world(min_max, xz, dim, self) } + /// 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, + >, + >, + >( + &mut self, + xz: Vec2, + dim: Dimension, + ) -> Result { + self.get_chunk_ex(xz, dim, self.config.sub_chunk_range.clone()) + } + fn handle_exist(&mut self, xz: Vec2, dim: Dimension) { self.chunk_existence.insert((dim, xz)); } @@ -385,6 +466,14 @@ where } Ok(()) } + + 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(()) + } } pub trait LevelModificationProvider { diff --git a/crates/level/src/level/sub_chunk.rs b/crates/level/src/level/sub_chunk.rs index 65d4d8c8..74ede1c2 100644 --- a/crates/level/src/level/sub_chunk.rs +++ b/crates/level/src/level/sub_chunk.rs @@ -1,7 +1,64 @@ +use crate::level::world_block::WorldBlockTrait; +use std::fmt::Debug; use std::io::Cursor; +use thiserror::Error; use vek::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>), +} + +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), + } + } +} + +#[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, @@ -103,7 +160,7 @@ pub mod default_impl { use crate::types::miner::idx_3_to_1; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use nbtx::NbtError; - use std::io::{Cursor, Seek, SeekFrom, Write}; + use std::io::{Cursor, Write}; use std::marker::PhantomData; use std::mem::MaybeUninit; use thiserror::Error; @@ -185,24 +242,19 @@ pub mod default_impl { let palette_count = bytes.read_u32::()?; let mut blocks = Vec::with_capacity(palette_count as usize); for _ in 0_usize..palette_count as usize { - let position = bytes.position(); - let cursor = &mut Cursor::new(&bytes.get_mut()[position as usize..]); - if network { blocks.push(Self::BlockType::from_transition( nbtx::from_bytes::( - cursor, + bytes, )?, state, )); } else { blocks.push(Self::BlockType::from_transition( - nbtx::from_bytes::(cursor)?, + nbtx::from_bytes::(bytes)?, state, )); } - let pos = cursor.position(); - bytes.seek(SeekFrom::Start(pos))?; } transitiondata.new_layer((block_indices, blocks)); } @@ -269,7 +321,7 @@ pub mod default_impl { } pub fn force_empty(&mut self) { - self.is_empty = false; + self.is_empty = true; } pub fn replace(&mut self, other: Self) { @@ -278,6 +330,42 @@ pub mod default_impl { self.active_layer = other.active_layer; self.is_empty = other.is_empty; } + + 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 + } + + 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))?; + } + } + } + } + + Ok(()) + } } #[derive(Debug, Error)] pub enum SubChunkError { diff --git a/crates/level/test_level/LOG.old b/crates/level/test_level/LOG.old new file mode 100644 index 00000000..e69de29b diff --git a/crates/level/test_level/db/000010.ldb b/crates/level/test_level/db/000010.ldb deleted file mode 100644 index 43519829..00000000 Binary files a/crates/level/test_level/db/000010.ldb and /dev/null differ diff --git a/crates/level/test_level/db/000012.log b/crates/level/test_level/db/000012.log deleted file mode 100644 index cd74e4a1..00000000 Binary files a/crates/level/test_level/db/000012.log and /dev/null differ diff --git a/crates/level/test_level/db/000013.ldb b/crates/level/test_level/db/000013.ldb deleted file mode 100644 index 35c27dd4..00000000 Binary files a/crates/level/test_level/db/000013.ldb and /dev/null differ diff --git a/crates/level/test_level/db/000043.log b/crates/level/test_level/db/000043.log new file mode 100644 index 00000000..2e685785 Binary files /dev/null and b/crates/level/test_level/db/000043.log differ diff --git a/crates/level/test_level/db/000044.ldb b/crates/level/test_level/db/000044.ldb new file mode 100644 index 00000000..03a17383 Binary files /dev/null and b/crates/level/test_level/db/000044.ldb differ diff --git a/crates/level/test_level/db/CURRENT b/crates/level/test_level/db/CURRENT index 875cf233..8ee0a8e4 100644 --- a/crates/level/test_level/db/CURRENT +++ b/crates/level/test_level/db/CURRENT @@ -1 +1 @@ -MANIFEST-000007 +MANIFEST-000041 diff --git a/crates/level/test_level/db/LOG b/crates/level/test_level/db/LOG index a69bbc2d..e385d684 100644 --- a/crates/level/test_level/db/LOG +++ b/crates/level/test_level/db/LOG @@ -1,7 +1,4 @@ -Recovered manifest with next_file=12 manifest_num=11 log_num=9 prev_log_num=0 last_seq=662 -reusing manifest "./test_level\\db\\MANIFEST-000007" -Recovering log file "\\\\?\\G:\\Projects\\rust\\bedrockrs\\crates\\level\\test_level\\db\\000009.log" -reusing log file "\\\\?\\G:\\Projects\\rust\\bedrockrs\\crates\\level\\test_level\\db\\000009.log" -Start write of L0 table 000013 -L0 table 000013 has 1748199 bytes -Deleting file type=Log num=9 +Recovered manifest with next_file=46 manifest_num=45 log_num=43 prev_log_num=0 last_seq=4837 +reusing manifest "./test_level/db\\MANIFEST-000041" +Recovering log file "\\\\?\\G:\\Projects\\rust\\bedrockrs\\crates\\level\\test_level\\db\\000043.log" +reusing log file "\\\\?\\G:\\Projects\\rust\\bedrockrs\\crates\\level\\test_level\\db\\000043.log" diff --git a/crates/level/test_level/db/LOG.old b/crates/level/test_level/db/LOG.old index 00980fef..e385d684 100644 --- a/crates/level/test_level/db/LOG.old +++ b/crates/level/test_level/db/LOG.old @@ -1,4 +1,4 @@ -Recovered manifest with next_file=12 manifest_num=11 log_num=9 prev_log_num=0 last_seq=662 -reusing manifest "C:\\Users\\Duckos\\AppData\\Roaming\\.minecraft_bedrock\\installations\\Duckos\\Latest Release\\packageData\\minecraftWorlds\\0Vhz00QYds0=\\db\\MANIFEST-000007" -Recovering log file "\\\\?\\C:\\Users\\Duckos\\AppData\\Roaming\\.minecraft_bedrock\\installations\\Duckos\\Latest Release\\packageData\\minecraftWorlds\\0Vhz00QYds0=\\db\\000009.log" -reusing log file "\\\\?\\C:\\Users\\Duckos\\AppData\\Roaming\\.minecraft_bedrock\\installations\\Duckos\\Latest Release\\packageData\\minecraftWorlds\\0Vhz00QYds0=\\db\\000009.log" +Recovered manifest with next_file=46 manifest_num=45 log_num=43 prev_log_num=0 last_seq=4837 +reusing manifest "./test_level/db\\MANIFEST-000041" +Recovering log file "\\\\?\\G:\\Projects\\rust\\bedrockrs\\crates\\level\\test_level\\db\\000043.log" +reusing log file "\\\\?\\G:\\Projects\\rust\\bedrockrs\\crates\\level\\test_level\\db\\000043.log" diff --git a/crates/level/test_level/db/MANIFEST-000007 b/crates/level/test_level/db/MANIFEST-000007 deleted file mode 100644 index 238dd33d..00000000 Binary files a/crates/level/test_level/db/MANIFEST-000007 and /dev/null differ diff --git a/crates/level/test_level/db/MANIFEST-000041 b/crates/level/test_level/db/MANIFEST-000041 new file mode 100644 index 00000000..67575ee1 Binary files /dev/null and b/crates/level/test_level/db/MANIFEST-000041 differ diff --git a/crates/level/tests/api_test.rs b/crates/level/tests/api_test.rs index fe7d9658..87d3cbb6 100644 --- a/crates/level/tests/api_test.rs +++ b/crates/level/tests/api_test.rs @@ -1,6 +1,7 @@ use bedrockrs_level::level::chunk::{FillFilter, LevelChunkTrait}; use bedrockrs_level::level::level::default_impl::*; -use bedrockrs_level::level::level::ChunkSelectionFilter; +use bedrockrs_level::level::level::{ChunkSelectionFilter, LevelConfiguration}; +use bedrockrs_level::level::sub_chunk::SubchunkFillFilter; use bedrockrs_shared::world::dimension::Dimension; use std::path::Path; @@ -8,14 +9,13 @@ use std::path::Path; #[test] fn world_test( ) -> Result<(), BedrockLevelError> { - let wld_path = "./test_level"; + let wld_path = "./test_level/db"; println!("Loading World"); let mut level = BedrockLevel::open( Box::from(Path::new(wld_path)), - false, - false, + LevelConfiguration::default(), BedrockState {}, )?; @@ -45,17 +45,38 @@ fn world_test( blk.clone(), FillFilter::Precedence(Box::new(|_, _, _, _| rand::random::() > 0.5)), ) - .expect("Fill failed"); + .unwrap(); } - chunk.write_to_world(&mut level, None, None)?; + chunk.write_to_world(&mut level, None, None).unwrap(); if idx % (len / 10 + 1) == 0 { println!("Wrote {idx} out of {len} chunks!"); } } + let mut chunk = level.get_chunk::((0, 0).into(), Dimension::Overworld)?; - level.close(); + 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(()) }