Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix]: Fixed lots of bugs in the Level implementation #118

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9d633a3
Allow users to specify default implementation for level
akashic-records-of-the-abyss Jan 1, 2025
e8ee6c8
Merge branch 'bedrock-crustaceans:main' into proto_rework
akashic-records-of-the-abyss Jan 1, 2025
0749d80
Merge pull request #1 from akashic-records-of-the-abyss/proto_rework
akashic-records-of-the-abyss Jan 1, 2025
bca2217
Update Cargo.toml
akashic-records-of-the-abyss Jan 1, 2025
edf176c
changed syntax
akashic-records-of-the-abyss Jan 1, 2025
d180db8
Update Cargo.toml
akashic-records-of-the-abyss Jan 1, 2025
e8a629a
added propper close methods
akashic-records-of-the-abyss Jan 4, 2025
5328546
added filling function and fixed writer bugs
akashic-records-of-the-abyss Jan 4, 2025
7c647a5
Changed how the project is included
akashic-records-of-the-abyss Jan 4, 2025
dc314cd
Cleaned up fill function
akashic-records-of-the-abyss Jan 4, 2025
449cde3
Made the level APIs more user friendly
akashic-records-of-the-abyss Jan 4, 2025
ee2d460
Made clippy happy
akashic-records-of-the-abyss Jan 4, 2025
b52b662
Revert "Made clippy happy"
akashic-records-of-the-abyss Jan 4, 2025
4486a11
Reapply "Made clippy happy"
akashic-records-of-the-abyss Jan 4, 2025
c22917f
Merge branch 'dev' into proto_rework
akashic-records-of-the-abyss Jan 4, 2025
20c15ad
Merge branch 'main' into proto_rework
akashic-records-of-the-abyss Jan 4, 2025
b270416
Merge pull request #2 from akashic-records-of-the-abyss/proto_rework
akashic-records-of-the-abyss Jan 4, 2025
d613875
Began on examples
akashic-records-of-the-abyss Jan 4, 2025
8234eb5
Addressed issues with imports
akashic-records-of-the-abyss Jan 4, 2025
6f7750b
Continued working on examples
akashic-records-of-the-abyss Jan 4, 2025
c9110d3
Added deleting to the API
akashic-records-of-the-abyss Jan 4, 2025
6c8601c
Made config public
akashic-records-of-the-abyss Jan 4, 2025
a136f46
Merge branch 'world_examples' into proto_rework
akashic-records-of-the-abyss Jan 4, 2025
6c4c205
Added better getter for chunk
akashic-records-of-the-abyss Jan 4, 2025
cab5541
Merge branch 'proto_rework' of https://github.com/akashic-records-of-…
akashic-records-of-the-abyss Jan 4, 2025
ea312fe
Update Cargo.toml
theaddonn Jan 4, 2025
810f5be
Fixed tests
akashic-records-of-the-abyss Jan 4, 2025
434d5b6
Pushed level
akashic-records-of-the-abyss Jan 4, 2025
00ed1ce
Tried a hack
akashic-records-of-the-abyss Jan 4, 2025
d9d7026
Update Cargo.toml
theaddonn Jan 4, 2025
401be96
Removed example for now
akashic-records-of-the-abyss Jan 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand All @@ -37,8 +37,10 @@ chrono = "0.4"
[features]

addon = ["dep:bedrockrs_addon"]
proto = ["dep:bedrockrs_proto","dep:bedrockrs_proto_core","dep:bedrockrs_macros",]
level = ["dep:bedrockrs_level"]
proto = ["dep:bedrockrs_proto", "dep:bedrockrs_proto_core", "dep:bedrockrs_macros", ]
level = ["dep:bedrockrs_level", "level-default"]

level-default = ["bedrockrs_level/default-impl"]
full = ["addon", "level", "proto", "server"]
form = ["dep:bedrockrs_form"]
server = ["dep:bedrockrs_server", "proto", "level", "form"]
Expand Down
26 changes: 16 additions & 10 deletions crates/level/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ version = "0.1.0"
edition = "2021"
akashic-records-of-the-abyss marked this conversation as resolved.
Show resolved Hide resolved

[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"] }
bytemuck = { version = "1.19", features = ["must_cast"] }

thiserror = "1"
byteorder = "1"
uuid = { version = "1", features = ["v4"] }
bytemuck = { version = "1", features = ["must_cast"] }
len-trait = "0.6"
concat-idents = "1.x"
serde = "1.x.x"
rusty-leveldb = "3.x"
miniz_oxide = "0.8"
concat-idents = "1"
serde = "1"
rusty-leveldb = "3"
miniz_oxide = "0"

vek = "0.17"

[dev-dependencies]
Expand All @@ -28,4 +29,9 @@ default-impl = []

[[test]]
name = "api_test"
required-features = ["default-impl"]


[features]
default-impl = []
default = ["default-impl"]

23 changes: 13 additions & 10 deletions crates/level/src/level/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub enum FillFilter<UserBlockType: WorldBlockTrait> {
/// # Parameters
/// - A boxed function with the following parameters:
/// - `&UserBlockType`: The current block being evaluated.
/// - `Vec3<u8>`: The local coordinates of the block within the chunk.
/// - `Vec3<u8>`: The local coordinates of the block within the current subchunk.
/// - `Vec2<i32>`: The world-space XZ coordinates of the chunk.
/// - `i8`: The subchunk y.
///
Expand All @@ -40,6 +40,17 @@ pub enum FillFilter<UserBlockType: WorldBlockTrait> {
Precedence(Box<dyn Fn(&UserBlockType, Vec3<u8>, Vec2<i32>, i8) -> bool>),
}

impl<T: WorldBlockTrait> FillFilter<T> {
pub fn may_place(&self, target: &T, position: Vec3<u8>, xz: Vec2<i32>, 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")]
Expand Down Expand Up @@ -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()
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/level/src/level/db_interface/bedrock_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl LevelDBKey for ChunkKey {
size += std::mem::size_of::<i32>();
}
size += 1; // For the key_type
if let Some(_) = self.y_index {
if self.y_index.is_some() {
size += 1;
}
size
Expand Down
14 changes: 13 additions & 1 deletion crates/level/src/level/db_interface/rusty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ impl<UserState> RustyDBInterface<UserState> {
}
}

impl<T> Drop for RustyDBInterface<T> {
fn drop(&mut self) {
self.db.close().unwrap();
}
}

impl<UserState> RawWorldTrait for RustyDBInterface<UserState> {
type Err = DBError;
type UserState = UserState;
Expand Down Expand Up @@ -187,7 +193,8 @@ impl<UserState> RawWorldTrait for RustyDBInterface<UserState> {
chunk_info: ChunkKey,
state: &mut Self::UserState,
) -> Result<(), Self::Err> {
Ok(self.set_subchunk_raw(chunk_info, &[], state)?)
// This looks strange, but it's just a wrapper over writing to the DB
self.set_subchunk_raw(chunk_info, &[], state)
}

fn build_key(key: &ChunkKey) -> Vec<u8> {
Expand All @@ -211,6 +218,11 @@ impl<UserState> RawWorldTrait for RustyDBInterface<UserState> {
})
}

fn close(&mut self) -> Result<(), Self::Err> {
self.db.close()?;
Ok(())
}

fn generated_chunks(
&mut self,
_: &mut Self::UserState,
Expand Down
2 changes: 2 additions & 0 deletions crates/level/src/level/file_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ pub trait RawWorldTrait: Sized {
state: &mut Self::UserState,
) -> Result<Self, Self::Err>;

fn close(&mut self) -> Result<(), Self::Err>;

fn generated_chunks(
&mut self,
state: &mut Self::UserState,
Expand Down
80 changes: 63 additions & 17 deletions crates/level/src/level/level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,22 @@ pub enum LevelError<DataBaseError: Debug, SubChunkDecodeError: Debug, SubChunkEr
SubChunkError(SubChunkError),
}

pub struct ClosedLevel;
#[derive(Debug)]
pub struct LevelConfiguration {
sub_chunk_range: Vec2<i8>,
rw_cache: bool,
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<
Expand All @@ -66,7 +81,7 @@ pub struct Level<
> {
db: UserWorldInterface,
state: UserState,
rw_cache: bool,
config: LevelConfiguration,
cached_sub_chunks: ClearCacheContainer<SubchunkCacheKey, UserSubChunkType>,
chunk_existence: HashSet<(Dimension, Vec2<i32>)>,
_block_type_marker: PhantomData<UserBlockType>,
Expand All @@ -89,33 +104,33 @@ where
/// Simple function used to open the world
pub fn open(
path: Box<Path>,
create_db_if_missing: bool,
rw_cache: bool,
config: LevelConfiguration,
mut state: UserState,
) -> Result<
Self,
LevelError<UserWorldInterface::Err, UserSubChunkDecoder::Err, UserSubChunkType::Err>,
> {
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(
{
let mut buff = path.into_path_buf();
buff.push("db");
buff.into_boxed_path()
},
create_db_if_missing,
config.create_db_if_missing,
&mut state,
)
}
});
let mut this = Self {
db,
state,
rw_cache,
config,
cached_sub_chunks: ClearCacheContainer::with_threshold(1024),
chunk_existence: HashSet::new(),
_block_type_marker: PhantomData,
Expand Down Expand Up @@ -143,8 +158,18 @@ where
}

/// Must call before destruction
pub fn close(mut self) {
self.cull().unwrap();
pub fn close(
mut self,
) -> Result<
(),
LevelError<UserWorldInterface::Err, UserSubChunkDecoder::Err, UserSubChunkType::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(())
}

/// Returns all chunks (in the form of its key) that exist in the world
Expand Down Expand Up @@ -200,7 +225,7 @@ where
UserSubChunkType,
LevelError<UserWorldInterface::Err, UserSubChunkDecoder::Err, UserSubChunkType::Err>,
> {
if self.rw_cache {
if self.config.rw_cache {
if let Some(chunk) = self
.cached_sub_chunks
.get(&SubchunkCacheKey::new(xz, y, dim))
Expand Down Expand Up @@ -244,7 +269,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
Expand All @@ -269,7 +294,7 @@ where
(),
LevelError<UserWorldInterface::Err, UserSubChunkDecoder::Err, UserSubChunkType::Err>,
> {
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());
Expand All @@ -295,7 +320,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<UserChunkType: LevelChunkTrait<Self, UserLevel = Self>>(
pub fn set_chunk_ex<UserChunkType: LevelChunkTrait<Self, UserLevel = Self>>(
&mut self,
chnk: UserChunkType,
xz_override: Option<Vec2<i32>>,
Expand All @@ -304,6 +329,14 @@ 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<UserChunkType: LevelChunkTrait<Self, UserLevel = Self>>(
&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
Expand All @@ -319,11 +352,16 @@ where
>,
>(
&mut self,
min_max: Vec2<i8>,
xz: Vec2<i32>,
dim: Dimension,
min_max: Option<Vec2<i8>>,
) -> Result<UserChunkType, UserChunkType::Err> {
UserChunkType::load_from_world(min_max, xz, dim, self)
UserChunkType::load_from_world(
min_max.unwrap_or(self.config.sub_chunk_range.clone()),
xz,
dim,
self,
)
}

fn handle_exist(&mut self, xz: Vec2<i32>, dim: Dimension) {
Expand Down Expand Up @@ -385,6 +423,14 @@ where
}
Ok(())
}

fn flush_existence_buffer(&mut self) -> Result<(), UserWorldInterface::Err> {
for (dim, pos) in &self.chunk_existence {
self.db
.exist_chunk(ChunkKey::chunk_marker(*pos, *dim), &mut self.state)?
}
Ok(())
}
}

pub trait LevelModificationProvider {
Expand Down
Loading