diff options
| -rw-r--r-- | azalea-block/src/block_state.rs | 6 | ||||
| -rw-r--r-- | azalea-chat/src/text_component.rs | 2 | ||||
| -rw-r--r-- | azalea-client/src/test_simulation.rs | 12 | ||||
| -rw-r--r-- | azalea-core/src/position.rs | 48 | ||||
| -rw-r--r-- | azalea-physics/src/collision/world_collisions.rs | 6 | ||||
| -rw-r--r-- | azalea-registry/src/data.rs | 18 | ||||
| -rw-r--r-- | azalea-world/benches/chunks.rs | 2 | ||||
| -rw-r--r-- | azalea-world/src/chunk_storage.rs | 115 | ||||
| -rw-r--r-- | azalea-world/src/find_blocks.rs | 20 | ||||
| -rw-r--r-- | azalea-world/src/palette.rs | 432 | ||||
| -rw-r--r-- | azalea-world/src/palette/container.rs | 314 | ||||
| -rw-r--r-- | azalea-world/src/palette/mod.rs | 115 | ||||
| -rw-r--r-- | azalea-world/src/palette/tests.rs | 80 | ||||
| -rw-r--r-- | azalea-world/src/world.rs | 5 | ||||
| -rw-r--r-- | azalea/src/pathfinder/world.rs | 19 |
15 files changed, 689 insertions, 505 deletions
diff --git a/azalea-block/src/block_state.rs b/azalea-block/src/block_state.rs index ab0ca076..dfa2a9b2 100644 --- a/azalea-block/src/block_state.rs +++ b/azalea-block/src/block_state.rs @@ -94,6 +94,12 @@ impl TryFrom<u16> for BlockState { } } } +impl From<BlockState> for u32 { + /// See [`BlockState::id`]. + fn from(value: BlockState) -> Self { + value.id as u32 + } +} impl AzaleaRead for BlockState { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs index 228dad3b..bd598e16 100644 --- a/azalea-chat/src/text_component.rs +++ b/azalea-chat/src/text_component.rs @@ -193,7 +193,7 @@ mod tests { END_SPAN = "</span>", GREEN = "<span style=\"color:#55FF55;\">", RED = "<span style=\"color:#FF5555;\">", - BOLD_AQUA = "<span style=\"color:#55FFFF;font-weight: bold;\">", + BOLD_AQUA = "<span style=\"color:#55FFFF;font-weight:bold;\">", ) ); } diff --git a/azalea-client/src/test_simulation.rs b/azalea-client/src/test_simulation.rs index c9f28e01..3f7cd8b2 100644 --- a/azalea-client/src/test_simulation.rs +++ b/azalea-client/src/test_simulation.rs @@ -1,6 +1,7 @@ use std::{fmt::Debug, sync::Arc}; use azalea_auth::game_profile::GameProfile; +use azalea_block::BlockState; use azalea_buf::AzaleaWrite; use azalea_core::{ delta::PositionDelta8, @@ -20,11 +21,8 @@ use azalea_protocol::packets::{ c_light_update::ClientboundLightUpdatePacketData, }, }; -use azalea_registry::{DimensionType, EntityKind}; -use azalea_world::{ - Chunk, Instance, MinecraftEntityId, Section, - palette::{PalettedContainer, PalettedContainerKind}, -}; +use azalea_registry::{Biome, DimensionType, EntityKind}; +use azalea_world::{Chunk, Instance, MinecraftEntityId, Section, palette::PalettedContainer}; use bevy_app::App; use bevy_ecs::{component::Mutable, prelude::*, schedule::ExecutorKind}; use parking_lot::RwLock; @@ -261,8 +259,8 @@ pub fn make_basic_empty_chunk( for _ in 0..section_count { sections.push(Section { block_count: 0, - states: PalettedContainer::new(PalettedContainerKind::BlockStates), - biomes: PalettedContainer::new(PalettedContainerKind::Biomes), + states: PalettedContainer::<BlockState>::new(), + biomes: PalettedContainer::<Biome>::new(), }); } sections.azalea_write(&mut chunk_bytes).unwrap(); diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index 04cce036..5932cb5b 100644 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -539,8 +539,8 @@ impl From<ChunkBlockPos> for u64 { } impl nohash_hasher::IsEnabled for ChunkBlockPos {} -/// The coordinates of a block inside a chunk section. Each coordinate must be -/// in the range [0, 15]. +/// The coordinates of a block inside a chunk section. Each coordinate should be +/// in the range 0..=15. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct ChunkSectionBlockPos { pub x: u8, @@ -549,6 +549,50 @@ pub struct ChunkSectionBlockPos { } vec3_impl!(ChunkSectionBlockPos, u8); +/// The coordinates of a chunk inside a chunk section. Each coordinate should be +/// in the range 0..=3. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ChunkSectionBiomePos { + pub x: u8, + pub y: u8, + pub z: u8, +} +impl From<&ChunkBiomePos> for ChunkSectionBiomePos { + #[inline] + fn from(pos: &ChunkBiomePos) -> Self { + ChunkSectionBiomePos { + x: pos.x, + y: (pos.y & 0b11) as u8, + z: pos.z, + } + } +} +vec3_impl!(ChunkSectionBiomePos, u8); + +/// The coordinates of a biome inside a chunk. Biomes are 4x4 blocks. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ChunkBiomePos { + pub x: u8, + pub y: i32, + pub z: u8, +} +impl From<&BlockPos> for ChunkBiomePos { + #[inline] + fn from(pos: &BlockPos) -> Self { + ChunkBiomePos::from(&ChunkBlockPos::from(pos)) + } +} +impl From<&ChunkBlockPos> for ChunkBiomePos { + #[inline] + fn from(pos: &ChunkBlockPos) -> Self { + ChunkBiomePos { + x: pos.x >> 2, + y: pos.y >> 2, + z: pos.z >> 2, + } + } +} + impl Add<ChunkSectionBlockPos> for ChunkSectionPos { type Output = BlockPos; diff --git a/azalea-physics/src/collision/world_collisions.rs b/azalea-physics/src/collision/world_collisions.rs index ded31275..11e8946f 100644 --- a/azalea-physics/src/collision/world_collisions.rs +++ b/azalea-physics/src/collision/world_collisions.rs @@ -86,7 +86,7 @@ impl<'a> BlockCollisionsState<'a> { let block_state: BlockState = if item_chunk_pos == initial_chunk_pos { match &initial_chunk { Some(initial_chunk) => initial_chunk - .get(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y) + .get_block_state(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y) .unwrap_or(BlockState::AIR), _ => BlockState::AIR, } @@ -186,7 +186,7 @@ impl<'a> BlockCollisionsState<'a> { for (cached_section_pos, cached_section) in &self.cached_sections { if section_pos == *cached_section_pos { - return cached_section.get(section_block_pos); + return cached_section.get_block_state(section_block_pos); } } @@ -211,7 +211,7 @@ impl<'a> BlockCollisionsState<'a> { // println!("chunk section data: {:?}", section.states.storage.data); // println!("biome length: {}", section.biomes.storage.data.len()); - section.get(section_block_pos) + section.get_block_state(section_block_pos) } fn get_block_shape(&mut self, block_state: BlockState) -> &'static VoxelShape { diff --git a/azalea-registry/src/data.rs b/azalea-registry/src/data.rs index 7ae55119..91e7106f 100644 --- a/azalea-registry/src/data.rs +++ b/azalea-registry/src/data.rs @@ -53,3 +53,21 @@ data_registry! {CatVariant, "cat_variant"} data_registry! {PigVariant, "pig_variant"} data_registry! {PaintingVariant, "painting_variant"} data_registry! {WolfVariant, "wolf_variant"} + +data_registry! {Biome, "biome"} +// these extra traits are required for Biome to be allowed to be palletable +impl Default for Biome { + fn default() -> Self { + Self::new_raw(0) + } +} +impl From<u32> for Biome { + fn from(id: u32) -> Self { + Self::new_raw(id) + } +} +impl From<Biome> for u32 { + fn from(biome: Biome) -> Self { + biome.protocol_id() + } +} diff --git a/azalea-world/benches/chunks.rs b/azalea-world/benches/chunks.rs index 0e7f4554..f84fdc56 100644 --- a/azalea-world/benches/chunks.rs +++ b/azalea-world/benches/chunks.rs @@ -11,7 +11,7 @@ fn bench_chunks(c: &mut Criterion) { for x in 0..16 { for z in 0..16 { - chunk.set( + chunk.set_block_state( &ChunkBlockPos::new(x, 1, z), azalea_registry::Block::Bedrock.into(), 0, diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs index b65c86b1..23d1bb89 100644 --- a/azalea-world/src/chunk_storage.rs +++ b/azalea-world/src/chunk_storage.rs @@ -12,14 +12,17 @@ use azalea_block::{ fluid_state::FluidState, }; use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; -use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; +use azalea_core::position::{ + BlockPos, ChunkBiomePos, ChunkBlockPos, ChunkPos, ChunkSectionBiomePos, ChunkSectionBlockPos, +}; +use azalea_registry::Biome; use nohash_hasher::IntMap; use parking_lot::RwLock; use tracing::{debug, trace, warn}; use crate::{ heightmap::{Heightmap, HeightmapKind}, - palette::{PalettedContainer, PalettedContainerKind}, + palette::PalettedContainer, }; const SECTION_HEIGHT: u32 = 16; @@ -59,11 +62,11 @@ pub struct Chunk { } /// A section of a chunk, i.e. a 16*16*16 block area. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Section { pub block_count: u16, - pub states: PalettedContainer, - pub biomes: PalettedContainer, + pub states: PalettedContainer<BlockState>, + pub biomes: PalettedContainer<Biome>, } /// Get the actual stored view distance for the selected view distance. For some @@ -72,16 +75,6 @@ pub fn calculate_chunk_storage_range(view_distance: u32) -> u32 { u32::max(view_distance, 2) + 3 } -impl Default for Section { - fn default() -> Self { - Section { - block_count: 0, - states: PalettedContainer::new(PalettedContainerKind::BlockStates), - biomes: PalettedContainer::new(PalettedContainerKind::Biomes), - } - } -} - impl Default for Chunk { fn default() -> Self { Chunk { @@ -171,7 +164,7 @@ impl PartialChunkStorage { let chunk_pos = ChunkPos::from(pos); let chunk_lock = chunk_storage.get(&chunk_pos)?; let mut chunk = chunk_lock.write(); - Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, chunk_storage.min_y)) + Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, chunk_storage.min_y)) } pub fn replace_with_packet_data( @@ -301,7 +294,7 @@ impl ChunkStorage { let chunk_pos = ChunkPos::from(pos); let chunk = self.get(&chunk_pos)?; let chunk = chunk.read(); - chunk.get(&ChunkBlockPos::from(pos), self.min_y) + chunk.get_block_state(&ChunkBlockPos::from(pos), self.min_y) } pub fn get_fluid_state(&self, pos: &BlockPos) -> Option<FluidState> { @@ -309,6 +302,13 @@ impl ChunkStorage { Some(FluidState::from(block_state)) } + pub fn get_biome(&self, pos: &BlockPos) -> Option<Biome> { + let chunk_pos = ChunkPos::from(pos); + let chunk = self.get(&chunk_pos)?; + let chunk = chunk.read(); + chunk.get_biome(&ChunkBiomePos::from(pos), self.min_y) + } + pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> { if pos.y < self.min_y || pos.y >= (self.min_y + self.height as i32) { return None; @@ -316,7 +316,7 @@ impl ChunkStorage { let chunk_pos = ChunkPos::from(pos); let chunk = self.get(&chunk_pos)?; let mut chunk = chunk.write(); - Some(chunk.get_and_set(&ChunkBlockPos::from(pos), state, self.min_y)) + Some(chunk.get_and_set_block_state(&ChunkBlockPos::from(pos), state, self.min_y)) } } @@ -346,7 +346,7 @@ impl Chunk { let mut heightmaps = HashMap::new(); for (kind, data) in heightmaps_data { - let data: Box<[u64]> = data.iter().copied().collect(); + let data: Box<[u64]> = data.clone(); let heightmap = Heightmap::new(*kind, dimension_height, min_y, data); heightmaps.insert(*kind, heightmap); } @@ -357,22 +357,26 @@ impl Chunk { }) } - pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> { + pub fn get_block_state(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> { get_block_state_from_sections(&self.sections, pos, min_y) } - #[must_use = "Use Chunk::set instead if you don't need the previous state"] - pub fn get_and_set( + #[must_use = "Use Chunk::set_block_state instead if you don't need the previous state"] + pub fn get_and_set_block_state( &mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32, ) -> BlockState { let section_index = section_index(pos.y, min_y); - // TODO: make sure the section exists - let section = &mut self.sections[section_index as usize]; + let Some(section) = self.sections.get_mut(section_index as usize) else { + warn!( + "Tried to get and set block state {state:?} at out-of-bounds relative chunk position {pos:?}", + ); + return BlockState::AIR; + }; let chunk_section_pos = ChunkSectionBlockPos::from(pos); - let previous_state = section.get_and_set(chunk_section_pos, state); + let previous_state = section.get_and_set_block_state(chunk_section_pos, state); for heightmap in self.heightmaps.values_mut() { heightmap.update(pos, state, &self.sections); @@ -381,17 +385,35 @@ impl Chunk { previous_state } - pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) { + pub fn set_block_state(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) { let section_index = section_index(pos.y, min_y); - // TODO: make sure the section exists - let section = &mut self.sections[section_index as usize]; + let Some(section) = self.sections.get_mut(section_index as usize) else { + warn!( + "Tried to set block state {state:?} at out-of-bounds relative chunk position {pos:?}", + ); + return; + }; let chunk_section_pos = ChunkSectionBlockPos::from(pos); - section.set(chunk_section_pos, state); + section.set_block_state(chunk_section_pos, state); for heightmap in self.heightmaps.values_mut() { heightmap.update(pos, state, &self.sections); } } + + pub fn get_biome(&self, pos: &ChunkBiomePos, min_y: i32) -> Option<Biome> { + if pos.y < min_y { + // y position is out of bounds + return None; + } + let section_index = section_index(pos.y, min_y); + let Some(section) = self.sections.get(section_index as usize) else { + warn!("Tried to get biome at out-of-bounds relative chunk position {pos:?}",); + return None; + }; + let chunk_section_pos = ChunkSectionBiomePos::from(pos); + Some(section.get_biome(chunk_section_pos)) + } } /// Get the block state at the given position from a list of sections. Returns @@ -413,7 +435,7 @@ pub fn get_block_state_from_sections( }; let section = §ions[section_index]; let chunk_section_pos = ChunkSectionBlockPos::from(pos); - Some(section.get(chunk_section_pos)) + Some(section.get_block_state(chunk_section_pos)) } impl AzaleaWrite for Chunk { @@ -448,7 +470,7 @@ impl AzaleaRead for Section { // "A section has more blocks than what should be possible. This is a bug!" // ); - let states = PalettedContainer::read_with_type(buf, &PalettedContainerKind::BlockStates)?; + let states = PalettedContainer::<BlockState>::read(buf)?; for i in 0..states.storage.size() { if !BlockState::is_valid_state(states.storage.get(i) as BlockStateIntegerRepr) { @@ -459,7 +481,7 @@ impl AzaleaRead for Section { } } - let biomes = PalettedContainer::read_with_type(buf, &PalettedContainerKind::Biomes)?; + let biomes = PalettedContainer::<Biome>::read(buf)?; Ok(Section { block_count, states, @@ -478,19 +500,28 @@ impl AzaleaWrite for Section { } impl Section { - pub fn get(&self, pos: ChunkSectionBlockPos) -> BlockState { - self.states - .get(pos.x as usize, pos.y as usize, pos.z as usize) + pub fn get_block_state(&self, pos: ChunkSectionBlockPos) -> BlockState { + self.states.get(pos) } - - pub fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState { - self.states - .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state) + pub fn get_and_set_block_state( + &mut self, + pos: ChunkSectionBlockPos, + state: BlockState, + ) -> BlockState { + self.states.get_and_set(pos, state) + } + pub fn set_block_state(&mut self, pos: ChunkSectionBlockPos, state: BlockState) { + self.states.set(pos, state); } - pub fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) { - self.states - .set(pos.x as usize, pos.y as usize, pos.z as usize, state); + pub fn get_biome(&self, pos: ChunkSectionBiomePos) -> Biome { + self.biomes.get(pos) + } + pub fn set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) { + self.biomes.set(pos, biome); + } + pub fn get_and_set_biome(&mut self, pos: ChunkSectionBiomePos, biome: Biome) -> Biome { + self.biomes.get_and_set(pos, biome) } } diff --git a/azalea-world/src/find_blocks.rs b/azalea-world/src/find_blocks.rs index 1215549b..b266d799 100644 --- a/azalea-world/src/find_blocks.rs +++ b/azalea-world/src/find_blocks.rs @@ -1,9 +1,9 @@ -use azalea_block::BlockStates; +use azalea_block::{BlockState, BlockStates}; use azalea_core::position::{BlockPos, ChunkPos}; use crate::{ChunkStorage, Instance, iterators::ChunkIterator, palette::Palette}; -fn palette_maybe_has_block(palette: &Palette, block_states: &BlockStates) -> bool { +fn palette_maybe_has_block(palette: &Palette<BlockState>, block_states: &BlockStates) -> bool { match &palette { Palette::SingleValue(id) => block_states.contains(id), Palette::Linear(ids) => ids.iter().any(|id| block_states.contains(id)), @@ -63,11 +63,11 @@ impl Instance { let block_state = section.states.get_at_index(i); if block_states.contains(&block_state) { - let (section_x, section_y, section_z) = section.states.coords_from_index(i); + let section_pos = section.states.coords_from_index(i); let (x, y, z) = ( - chunk_pos.x * 16 + (section_x as i32), - self.chunks.min_y + (section_index * 16) as i32 + section_y as i32, - chunk_pos.z * 16 + (section_z as i32), + chunk_pos.x * 16 + (section_pos.x as i32), + self.chunks.min_y + (section_index * 16) as i32 + section_pos.y as i32, + chunk_pos.z * 16 + (section_pos.z as i32), ); let this_block_pos = BlockPos { x, y, z }; let this_block_distance = (nearest_to - this_block_pos).length_manhattan(); @@ -190,11 +190,11 @@ impl Iterator for FindBlocks<'_> { let block_state = section.states.get_at_index(i); if self.block_states.contains(&block_state) { - let (section_x, section_y, section_z) = section.states.coords_from_index(i); + let section_pos = section.states.coords_from_index(i); let (x, y, z) = ( - chunk_pos.x * 16 + (section_x as i32), - self.chunks.min_y + (section_index * 16) as i32 + section_y as i32, - chunk_pos.z * 16 + (section_z as i32), + chunk_pos.x * 16 + (section_pos.x as i32), + self.chunks.min_y + (section_index * 16) as i32 + section_pos.y as i32, + chunk_pos.z * 16 + (section_pos.z as i32), ); let this_block_pos = BlockPos { x, y, z }; let this_block_distance = diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs deleted file mode 100644 index cef9bdee..00000000 --- a/azalea-world/src/palette.rs +++ /dev/null @@ -1,432 +0,0 @@ -use std::io::{self, Cursor, Write}; - -use azalea_block::BlockState; -use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; -use tracing::warn; - -use crate::BitStorage; - -#[derive(Clone, Debug, Copy)] -pub enum PalettedContainerKind { - Biomes, - BlockStates, -} - -#[derive(Clone, Debug)] -pub struct PalettedContainer { - pub bits_per_entry: u8, - /// This is usually a list of unique values that appear in the container so - /// they can be indexed by the bit storage. - /// - /// Sometimes it doesn't contain anything if there's too many unique items - /// in the bit storage, though. - pub palette: Palette, - /// Compacted list of indices pointing to entry IDs in the Palette. - pub storage: BitStorage, - pub container_type: PalettedContainerKind, -} - -impl PalettedContainer { - pub fn new(container_type: PalettedContainerKind) -> Self { - let palette = Palette::SingleValue(BlockState::AIR); - let size = container_type.size(); - let storage = BitStorage::new(0, size, Some(Box::new([]))).unwrap(); - - PalettedContainer { - bits_per_entry: 0, - palette, - storage, - container_type, - } - } - - pub fn read_with_type( - buf: &mut Cursor<&[u8]>, - container_type: &'static PalettedContainerKind, - ) -> Result<Self, BufReadError> { - let bits_per_entry = u8::azalea_read(buf)?; - let palette_type = PaletteKind::from_bits_and_type(bits_per_entry, container_type); - let palette = palette_type.read(buf)?; - let size = container_type.size(); - - let mut storage = match BitStorage::new( - bits_per_entry as usize, - size, - if bits_per_entry == 0 { - Some(Box::new([])) - } else { - // we're going to update the data after creating the bitstorage - None - }, - ) { - Ok(storage) => storage, - Err(e) => { - warn!("Failed to create bit storage: {:?}", e); - return Err(BufReadError::Custom( - "Failed to create bit storage".to_string(), - )); - } - }; - - // now read the data - for i in 0..storage.data.len() { - storage.data[i] = u64::azalea_read(buf)?; - } - - Ok(PalettedContainer { - bits_per_entry, - palette, - storage, - container_type: *container_type, - }) - } - - /// Calculates the index of the given coordinates. - pub fn index_from_coords(&self, x: usize, y: usize, z: usize) -> usize { - let size_bits = self.container_type.size_bits(); - - (((y << size_bits) | z) << size_bits) | x - } - - pub fn coords_from_index(&self, index: usize) -> (usize, usize, usize) { - let size_bits = self.container_type.size_bits(); - let mask = (1 << size_bits) - 1; - ( - index & mask, - (index >> size_bits >> size_bits) & mask, - (index >> size_bits) & mask, - ) - } - - /// Returns the value at the given index. - /// - /// # Panics - /// - /// This function panics if the index is greater than or equal to the number - /// of things in the storage. (So for block states, it must be less than - /// 4096). - pub fn get_at_index(&self, index: usize) -> BlockState { - // first get the palette id - let paletted_value = self.storage.get(index); - // and then get the value from that id - self.palette.value_for(paletted_value as usize) - } - - /// Returns the value at the given coordinates. - pub fn get(&self, x: usize, y: usize, z: usize) -> BlockState { - // let paletted_value = self.storage.get(self.get_index(x, y, z)); - // self.palette.value_for(paletted_value as usize) - self.get_at_index(self.index_from_coords(x, y, z)) - } - - /// Sets the id at the given coordinates and return the previous id - pub fn get_and_set(&mut self, x: usize, y: usize, z: usize, value: BlockState) -> BlockState { - let paletted_value = self.id_for(value); - let block_state_id = self - .storage - .get_and_set(self.index_from_coords(x, y, z), paletted_value as u64); - // error in debug mode - #[cfg(debug_assertions)] - if block_state_id > BlockState::MAX_STATE.into() { - warn!( - "Old block state from get_and_set {block_state_id} was greater than max state {}", - BlockState::MAX_STATE - ); - } - - BlockState::try_from(block_state_id as u32).unwrap_or_default() - } - - /// Sets the id at the given index and return the previous id. You probably - /// want `.set` instead. - pub fn set_at_index(&mut self, index: usize, value: BlockState) { - let paletted_value = self.id_for(value); - self.storage.set(index, paletted_value as u64); - } - - /// Sets the id at the given coordinates and return the previous id - pub fn set(&mut self, x: usize, y: usize, z: usize, value: BlockState) { - self.set_at_index(self.index_from_coords(x, y, z), value); - } - - fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer { - let new_palette_type = - PaletteKind::from_bits_and_type(bits_per_entry, &self.container_type); - - let old_palette_type = (&self.palette).into(); - if bits_per_entry == self.bits_per_entry && new_palette_type == old_palette_type { - return self.clone(); - } - let storage = - BitStorage::new(bits_per_entry as usize, self.container_type.size(), None).unwrap(); - - // sanity check - debug_assert_eq!(storage.size(), self.container_type.size()); - - // let palette = new_palette_type.as_empty_palette(1usize << (bits_per_entry as - // usize)); - let palette = new_palette_type.as_empty_palette(); - PalettedContainer { - bits_per_entry, - palette, - storage, - container_type: self.container_type, - } - } - - fn on_resize(&mut self, bits_per_entry: u8, value: BlockState) -> usize { - // in vanilla this is always true, but it's sometimes false in purpur servers - // assert!(bits_per_entry <= 5, "bits_per_entry must be <= 5"); - let mut new_data = self.create_or_reuse_data(bits_per_entry); - new_data.copy_from(&self.palette, &self.storage); - *self = new_data; - self.id_for(value) - } - - fn copy_from(&mut self, palette: &Palette, storage: &BitStorage) { - for i in 0..storage.size() { - let value = palette.value_for(storage.get(i) as usize); - let id = self.id_for(value) as u64; - self.storage.set(i, id); - } - } - - pub fn id_for(&mut self, value: BlockState) -> usize { - match &mut self.palette { - Palette::SingleValue(v) => { - if *v != value { - self.on_resize(1, value) - } else { - 0 - } - } - Palette::Linear(palette) => { - if let Some(index) = palette.iter().position(|&v| v == value) { - return index; - } - let capacity = 2usize.pow(self.bits_per_entry.into()); - if capacity > palette.len() { - palette.push(value); - palette.len() - 1 - } else { - self.on_resize(self.bits_per_entry + 1, value) - } - } - Palette::Hashmap(palette) => { - // TODO? vanilla keeps this in memory as a hashmap, but it should be benchmarked - // before changing it - if let Some(index) = palette.iter().position(|v| *v == value) { - return index; - } - let capacity = 2usize.pow(self.bits_per_entry.into()); - if capacity > palette.len() { - palette.push(value); - palette.len() - 1 - } else { - self.on_resize(self.bits_per_entry + 1, value) - } - } - Palette::Global => value.id() as usize, - } - } -} - -impl AzaleaWrite for PalettedContainer { - fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { - self.bits_per_entry.azalea_write(buf)?; - self.palette.azalea_write(buf)?; - self.storage.data.azalea_write(buf)?; - Ok(()) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum PaletteKind { - SingleValue, - Linear, - Hashmap, - Global, -} - -/// A representation of the different types of chunk palettes Minecraft uses. -#[derive(Clone, Debug)] -pub enum Palette { - /// ID of the corresponding entry in its global palette - SingleValue(BlockState), - // in vanilla this keeps a `size` field that might be less than the length, but i'm not sure - // it's actually needed? - Linear(Vec<BlockState>), - Hashmap(Vec<BlockState>), - Global, -} - -impl Palette { - pub fn value_for(&self, id: usize) -> BlockState { - match self { - Palette::SingleValue(v) => *v, - Palette::Linear(v) => v.get(id).copied().unwrap_or_default(), - Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(), - Palette::Global => BlockState::try_from(id as u32).unwrap_or_default(), - } - } -} - -impl AzaleaWrite for Palette { - fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { - match self { - Palette::SingleValue(value) => { - value.azalea_write(buf)?; - } - Palette::Linear(values) => { - values.azalea_write(buf)?; - } - Palette::Hashmap(values) => { - values.azalea_write(buf)?; - } - Palette::Global => {} - } - Ok(()) - } -} - -impl PaletteKind { - pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerKind) -> Self { - match container_type { - PalettedContainerKind::BlockStates => match bits_per_entry { - 0 => PaletteKind::SingleValue, - 1..=4 => PaletteKind::Linear, - 5..=8 => PaletteKind::Hashmap, - _ => PaletteKind::Global, - }, - PalettedContainerKind::Biomes => match bits_per_entry { - 0 => PaletteKind::SingleValue, - 1..=3 => PaletteKind::Linear, - _ => PaletteKind::Global, - }, - } - } - - pub fn read(&self, buf: &mut Cursor<&[u8]>) -> Result<Palette, BufReadError> { - Ok(match self { - // since they're read as varints it's actually fine to just use BlockStateIntegerRepr - // instead of the correct type (u32) - PaletteKind::SingleValue => Palette::SingleValue(BlockState::azalea_read(buf)?), - PaletteKind::Linear => Palette::Linear(Vec::<BlockState>::azalea_read(buf)?), - PaletteKind::Hashmap => Palette::Hashmap(Vec::<BlockState>::azalea_read(buf)?), - PaletteKind::Global => Palette::Global, - }) - } - - pub fn as_empty_palette(&self) -> Palette { - match self { - PaletteKind::SingleValue => Palette::SingleValue(BlockState::AIR), - PaletteKind::Linear => Palette::Linear(Vec::new()), - PaletteKind::Hashmap => Palette::Hashmap(Vec::new()), - PaletteKind::Global => Palette::Global, - } - } -} - -impl From<&Palette> for PaletteKind { - fn from(palette: &Palette) -> Self { - match palette { - Palette::SingleValue(_) => PaletteKind::SingleValue, - Palette::Linear(_) => PaletteKind::Linear, - Palette::Hashmap(_) => PaletteKind::Hashmap, - Palette::Global => PaletteKind::Global, - } - } -} - -impl PalettedContainerKind { - fn size_bits(&self) -> usize { - match self { - PalettedContainerKind::BlockStates => 4, - PalettedContainerKind::Biomes => 2, - } - } - - fn size(&self) -> usize { - 1 << (self.size_bits() * 3) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_resize_0_bits_to_1() { - let mut palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates); - - assert_eq!(palette_container.bits_per_entry, 0); - assert_eq!(palette_container.get_at_index(0), BlockState::AIR); - assert_eq!( - PaletteKind::from(&palette_container.palette), - PaletteKind::SingleValue - ); - let block_state_1 = BlockState::try_from(1_u32).unwrap(); - palette_container.set_at_index(0, block_state_1); - assert_eq!(palette_container.get_at_index(0), block_state_1); - assert_eq!( - PaletteKind::from(&palette_container.palette), - PaletteKind::Linear - ); - } - - #[test] - fn test_resize_0_bits_to_5() { - let mut palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates); - - let set = |pc: &mut PalettedContainer, i, v: u32| { - pc.set_at_index(i, BlockState::try_from(v).unwrap()); - }; - - set(&mut palette_container, 0, 0); // 0 bits - assert_eq!(palette_container.bits_per_entry, 0); - - set(&mut palette_container, 1, 1); // 1 bit - assert_eq!(palette_container.bits_per_entry, 1); - - set(&mut palette_container, 2, 2); // 2 bits - assert_eq!(palette_container.bits_per_entry, 2); - set(&mut palette_container, 3, 3); - - set(&mut palette_container, 4, 4); // 3 bits - assert_eq!(palette_container.bits_per_entry, 3); - set(&mut palette_container, 5, 5); - set(&mut palette_container, 6, 6); - set(&mut palette_container, 7, 7); - - set(&mut palette_container, 8, 8); // 4 bits - assert_eq!(palette_container.bits_per_entry, 4); - set(&mut palette_container, 9, 9); - set(&mut palette_container, 10, 10); - set(&mut palette_container, 11, 11); - set(&mut palette_container, 12, 12); - set(&mut palette_container, 13, 13); - set(&mut palette_container, 14, 14); - set(&mut palette_container, 15, 15); - assert_eq!(palette_container.bits_per_entry, 4); - - set(&mut palette_container, 16, 16); // 5 bits - assert_eq!(palette_container.bits_per_entry, 5); - } - - #[test] - fn test_coords_from_index() { - let palette_container = PalettedContainer::new(PalettedContainerKind::BlockStates); - - for x in 0..15 { - for y in 0..15 { - for z in 0..15 { - assert_eq!( - palette_container - .coords_from_index(palette_container.index_from_coords(x, y, z)), - (x, y, z) - ); - } - } - } - } -} diff --git a/azalea-world/src/palette/container.rs b/azalea-world/src/palette/container.rs new file mode 100644 index 00000000..2f3d9238 --- /dev/null +++ b/azalea-world/src/palette/container.rs @@ -0,0 +1,314 @@ +use std::{ + fmt::Debug, + io::{self, Cursor, Write}, +}; + +use azalea_block::BlockState; +use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; +use azalea_core::position::{ChunkSectionBiomePos, ChunkSectionBlockPos}; +use azalea_registry::Biome; +use tracing::warn; + +use super::{Palette, PaletteKind}; +use crate::BitStorage; + +#[derive(Clone, Debug)] +pub struct PalettedContainer<S: PalletedContainerKind> { + pub bits_per_entry: u8, + /// This is usually a list of unique values that appear in the container so + /// they can be indexed by the bit storage. + /// + /// Sometimes it doesn't contain anything if there's too many unique items + /// in the bit storage, though. + pub palette: Palette<S>, + /// Compacted list of indices pointing to entry IDs in the Palette. + pub storage: BitStorage, +} + +pub trait PalletedContainerKind: Copy + Clone + Debug + Default + TryFrom<u32> + Into<u32> { + type SectionPos: SectionPos; + + fn size_bits() -> usize; + + fn size() -> usize { + 1 << (Self::size_bits() * 3) + } + + fn bits_per_entry_to_palette_kind(bits_per_entry: u8) -> PaletteKind; +} +impl PalletedContainerKind for BlockState { + type SectionPos = ChunkSectionBlockPos; + + fn size_bits() -> usize { + 4 + } + + fn bits_per_entry_to_palette_kind(bits_per_entry: u8) -> PaletteKind { + match bits_per_entry { + 0 => PaletteKind::SingleValue, + 1..=4 => PaletteKind::Linear, + 5..=8 => PaletteKind::Hashmap, + _ => PaletteKind::Global, + } + } +} +impl PalletedContainerKind for Biome { + type SectionPos = ChunkSectionBiomePos; + + fn size_bits() -> usize { + 2 + } + + fn bits_per_entry_to_palette_kind(bits_per_entry: u8) -> PaletteKind { + match bits_per_entry { + 0 => PaletteKind::SingleValue, + 1..=3 => PaletteKind::Linear, + _ => PaletteKind::Global, + } + } +} + +/// A trait for position types that are sometimes valid ways to index into a +/// chunk section. +pub trait SectionPos { + fn coords(&self) -> (usize, usize, usize); + fn new(x: usize, y: usize, z: usize) -> Self; +} +impl SectionPos for ChunkSectionBlockPos { + fn coords(&self) -> (usize, usize, usize) { + (self.x as usize, self.y as usize, self.z as usize) + } + + fn new(x: usize, y: usize, z: usize) -> Self { + ChunkSectionBlockPos { + x: x as u8, + y: y as u8, + z: z as u8, + } + } +} +impl SectionPos for ChunkSectionBiomePos { + fn coords(&self) -> (usize, usize, usize) { + (self.x as usize, self.y as usize, self.z as usize) + } + + fn new(x: usize, y: usize, z: usize) -> Self { + ChunkSectionBiomePos { + x: x as u8, + y: y as u8, + z: z as u8, + } + } +} + +impl<S: PalletedContainerKind> PalettedContainer<S> { + pub fn new() -> Self { + let palette = Palette::SingleValue(S::default()); + let size = S::size(); + let storage = BitStorage::new(0, size, Some(Box::new([]))).unwrap(); + + PalettedContainer { + bits_per_entry: 0, + palette, + storage, + } + } + + pub fn read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { + let bits_per_entry = u8::azalea_read(buf)?; + let palette_type = S::bits_per_entry_to_palette_kind(bits_per_entry); + let palette = palette_type.read(buf)?; + let size = S::size(); + + let mut storage = match BitStorage::new( + bits_per_entry as usize, + size, + if bits_per_entry == 0 { + Some(Box::new([])) + } else { + // we're going to update the data after creating the bitstorage + None + }, + ) { + Ok(storage) => storage, + Err(e) => { + warn!("Failed to create bit storage: {:?}", e); + return Err(BufReadError::Custom( + "Failed to create bit storage".to_string(), + )); + } + }; + + // now read the data + for i in 0..storage.data.len() { + storage.data[i] = u64::azalea_read(buf)?; + } + + Ok(PalettedContainer { + bits_per_entry, + palette, + storage, + }) + } + + /// Calculates the index of the given coordinates. + pub fn index_from_coords(&self, pos: S::SectionPos) -> usize { + let size_bits = S::size_bits(); + let (x, y, z) = pos.coords(); + (((y << size_bits) | z) << size_bits) | x + } + + pub fn coords_from_index(&self, index: usize) -> S::SectionPos { + let size_bits = S::size_bits(); + let mask = (1 << size_bits) - 1; + S::SectionPos::new( + index & mask, + (index >> size_bits >> size_bits) & mask, + (index >> size_bits) & mask, + ) + } + + /// Returns the value at the given index. + /// + /// # Panics + /// + /// This function panics if the index is greater than or equal to the number + /// of things in the storage. (So for block states, it must be less than + /// 4096). + pub fn get_at_index(&self, index: usize) -> S { + // first get the palette id + let paletted_value = self.storage.get(index); + // and then get the value from that id + self.palette.value_for(paletted_value as usize) + } + + /// Returns the value at the given coordinates. + pub fn get(&self, pos: S::SectionPos) -> S { + // let paletted_value = self.storage.get(self.get_index(x, y, z)); + // self.palette.value_for(paletted_value as usize) + self.get_at_index(self.index_from_coords(pos)) + } + + /// Sets the id at the given coordinates and return the previous id + pub fn get_and_set(&mut self, pos: S::SectionPos, value: S) -> S { + let paletted_value = self.id_for(value); + let block_state_id = self + .storage + .get_and_set(self.index_from_coords(pos), paletted_value as u64); + // error in debug mode + #[cfg(debug_assertions)] + if block_state_id > BlockState::MAX_STATE.into() { + warn!( + "Old block state from get_and_set {block_state_id} was greater than max state {}", + BlockState::MAX_STATE + ); + } + + S::try_from(block_state_id as u32).unwrap_or_default() + } + + /// Sets the id at the given index and return the previous id. You probably + /// want `.set` instead. + pub fn set_at_index(&mut self, index: usize, value: S) { + let paletted_value = self.id_for(value); + self.storage.set(index, paletted_value as u64); + } + + /// Sets the id at the given coordinates and return the previous id + pub fn set(&mut self, pos: S::SectionPos, value: S) { + self.set_at_index(self.index_from_coords(pos), value); + } + + fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer<S> { + let new_palette_type = S::bits_per_entry_to_palette_kind(bits_per_entry); + + let old_palette_type = (&self.palette).into(); + if bits_per_entry == self.bits_per_entry && new_palette_type == old_palette_type { + return self.clone(); + } + let storage = BitStorage::new(bits_per_entry as usize, S::size(), None).unwrap(); + + // sanity check + debug_assert_eq!(storage.size(), S::size()); + + // let palette = new_palette_type.as_empty_palette(1usize << (bits_per_entry as + // usize)); + let palette = new_palette_type.as_empty_palette(); + PalettedContainer { + bits_per_entry, + palette, + storage, + } + } + + fn on_resize(&mut self, bits_per_entry: u8, value: S) -> usize { + // in vanilla this is always true, but it's sometimes false in purpur servers + // assert!(bits_per_entry <= 5, "bits_per_entry must be <= 5"); + let mut new_data = self.create_or_reuse_data(bits_per_entry); + new_data.copy_from(&self.palette, &self.storage); + *self = new_data; + self.id_for(value) + } + + fn copy_from(&mut self, palette: &Palette<S>, storage: &BitStorage) { + for i in 0..storage.size() { + let value = palette.value_for(storage.get(i) as usize); + let id = self.id_for(value) as u64; + self.storage.set(i, id); + } + } + + pub fn id_for(&mut self, value: S) -> usize { + match &mut self.palette { + Palette::SingleValue(v) => { + if (*v).into() != value.into() { + self.on_resize(1, value) + } else { + 0 + } + } + Palette::Linear(palette) => { + if let Some(index) = palette.iter().position(|&v| v.into() == value.into()) { + return index; + } + let capacity = 2usize.pow(self.bits_per_entry.into()); + if capacity > palette.len() { + palette.push(value); + palette.len() - 1 + } else { + self.on_resize(self.bits_per_entry + 1, value) + } + } + Palette::Hashmap(palette) => { + // TODO? vanilla keeps this in memory as a hashmap, but it should be benchmarked + // before changing it + if let Some(index) = palette.iter().position(|v| (*v).into() == value.into()) { + return index; + } + let capacity = 2usize.pow(self.bits_per_entry.into()); + if capacity > palette.len() { + palette.push(value); + palette.len() - 1 + } else { + self.on_resize(self.bits_per_entry + 1, value) + } + } + Palette::Global => value.into() as usize, + } + } +} + +impl<S: PalletedContainerKind> AzaleaWrite for PalettedContainer<S> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + self.bits_per_entry.azalea_write(buf)?; + self.palette.azalea_write(buf)?; + self.storage.data.azalea_write(buf)?; + Ok(()) + } +} + +impl<S: PalletedContainerKind> Default for PalettedContainer<S> { + fn default() -> Self { + Self::new() + } +} diff --git a/azalea-world/src/palette/mod.rs b/azalea-world/src/palette/mod.rs new file mode 100644 index 00000000..65a04f6a --- /dev/null +++ b/azalea-world/src/palette/mod.rs @@ -0,0 +1,115 @@ +mod container; + +#[cfg(test)] +mod tests; + +use std::{ + fmt::Debug, + io::{self, Cursor, Write}, +}; + +use azalea_buf::{AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; +pub use container::*; + +/// A representation of the different types of chunk palettes Minecraft uses. +#[derive(Clone, Debug)] +pub enum Palette<S: PalletedContainerKind> { + /// ID of the corresponding entry in its global palette + SingleValue(S), + // in vanilla this keeps a `size` field that might be less than the length, but i'm not sure + // it's actually needed? + Linear(Vec<S>), + Hashmap(Vec<S>), + Global, +} + +impl<S: PalletedContainerKind> Palette<S> { + pub fn value_for(&self, id: usize) -> S { + match self { + Palette::SingleValue(v) => *v, + Palette::Linear(v) => v.get(id).copied().unwrap_or_default(), + Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(), + Palette::Global => S::try_from(id as u32).unwrap_or_default(), + } + } +} + +impl<S: PalletedContainerKind> AzaleaWrite for Palette<S> { + fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { + match self { + Palette::SingleValue(value) => { + (*value).into().azalea_write_var(buf)?; + } + Palette::Linear(values) => { + (values.len() as u32).azalea_write_var(buf)?; + for value in values { + (*value).into().azalea_write_var(buf)?; + } + } + Palette::Hashmap(values) => { + (values.len() as u32).azalea_write_var(buf)?; + for value in values { + (*value).into().azalea_write_var(buf)?; + } + } + Palette::Global => {} + } + Ok(()) + } +} + +impl PaletteKind { + pub fn read<S: PalletedContainerKind>( + &self, + buf: &mut Cursor<&[u8]>, + ) -> Result<Palette<S>, BufReadError> { + Ok(match self { + // since they're read as varints it's actually fine to just use BlockStateIntegerRepr + // instead of the correct type (u32) + PaletteKind::SingleValue => { + Palette::SingleValue(S::try_from(u32::azalea_read_var(buf)?).unwrap_or_default()) + } + PaletteKind::Linear => Palette::Linear( + Vec::<u32>::azalea_read_var(buf)? + .into_iter() + .map(|v| S::try_from(v).unwrap_or_default()) + .collect(), + ), + PaletteKind::Hashmap => Palette::Hashmap( + Vec::<u32>::azalea_read_var(buf)? + .into_iter() + .map(|v| S::try_from(v).unwrap_or_default()) + .collect(), + ), + PaletteKind::Global => Palette::Global, + }) + } + + pub fn as_empty_palette<S: PalletedContainerKind>(&self) -> Palette<S> { + match self { + PaletteKind::SingleValue => Palette::SingleValue(S::default()), + PaletteKind::Linear => Palette::Linear(Vec::new()), + PaletteKind::Hashmap => Palette::Hashmap(Vec::new()), + PaletteKind::Global => Palette::Global, + } + } +} + +impl<S: PalletedContainerKind> From<&Palette<S>> for PaletteKind { + fn from(palette: &Palette<S>) -> Self { + match palette { + Palette::SingleValue(_) => PaletteKind::SingleValue, + Palette::Linear(_) => PaletteKind::Linear, + Palette::Hashmap(_) => PaletteKind::Hashmap, + Palette::Global => PaletteKind::Global, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PaletteKind { + SingleValue, + Linear, + Hashmap, + Global, +} diff --git a/azalea-world/src/palette/tests.rs b/azalea-world/src/palette/tests.rs new file mode 100644 index 00000000..d1423306 --- /dev/null +++ b/azalea-world/src/palette/tests.rs @@ -0,0 +1,80 @@ +use azalea_block::BlockState; +use azalea_core::position::ChunkSectionBlockPos; + +use super::*; + +#[test] +fn test_resize_0_bits_to_1() { + let mut palette_container = PalettedContainer::<BlockState>::new(); + + assert_eq!(palette_container.bits_per_entry, 0); + assert_eq!(palette_container.get_at_index(0), BlockState::AIR); + assert_eq!( + PaletteKind::from(&palette_container.palette), + PaletteKind::SingleValue + ); + let block_state_1 = BlockState::try_from(1_u32).unwrap(); + palette_container.set_at_index(0, block_state_1); + assert_eq!(palette_container.get_at_index(0), block_state_1); + assert_eq!( + PaletteKind::from(&palette_container.palette), + PaletteKind::Linear + ); +} + +#[test] +fn test_resize_0_bits_to_5() { + let mut palette_container = PalettedContainer::<BlockState>::new(); + + let set = |pc: &mut PalettedContainer<BlockState>, i, v: u32| { + pc.set_at_index(i, BlockState::try_from(v).unwrap()); + }; + + set(&mut palette_container, 0, 0); // 0 bits + assert_eq!(palette_container.bits_per_entry, 0); + + set(&mut palette_container, 1, 1); // 1 bit + assert_eq!(palette_container.bits_per_entry, 1); + + set(&mut palette_container, 2, 2); // 2 bits + assert_eq!(palette_container.bits_per_entry, 2); + set(&mut palette_container, 3, 3); + + set(&mut palette_container, 4, 4); // 3 bits + assert_eq!(palette_container.bits_per_entry, 3); + set(&mut palette_container, 5, 5); + set(&mut palette_container, 6, 6); + set(&mut palette_container, 7, 7); + + set(&mut palette_container, 8, 8); // 4 bits + assert_eq!(palette_container.bits_per_entry, 4); + set(&mut palette_container, 9, 9); + set(&mut palette_container, 10, 10); + set(&mut palette_container, 11, 11); + set(&mut palette_container, 12, 12); + set(&mut palette_container, 13, 13); + set(&mut palette_container, 14, 14); + set(&mut palette_container, 15, 15); + assert_eq!(palette_container.bits_per_entry, 4); + + set(&mut palette_container, 16, 16); // 5 bits + assert_eq!(palette_container.bits_per_entry, 5); +} + +#[test] +fn test_coords_from_index() { + let palette_container = PalettedContainer::<BlockState>::new(); + + for x in 0..15 { + for y in 0..15 { + for z in 0..15 { + assert_eq!( + palette_container.coords_from_index( + palette_container.index_from_coords(ChunkSectionBlockPos::new(x, y, z)) + ), + ChunkSectionBlockPos::new(x, y, z) + ); + } + } + } +} diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index 183f59b5..00bee13a 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -11,6 +11,7 @@ use azalea_core::{ position::{BlockPos, ChunkPos}, registry_holder::RegistryHolder, }; +use azalea_registry::Biome; use bevy_ecs::{component::Component, entity::Entity}; use derive_more::{Deref, DerefMut}; use nohash_hasher::IntMap; @@ -170,6 +171,10 @@ impl Instance { self.chunks.get_block_state(pos).map(FluidState::from) } + pub fn get_biome(&self, pos: &BlockPos) -> Option<Biome> { + self.chunks.get_biome(pos) + } + pub fn set_block_state(&self, pos: &BlockPos, state: BlockState) -> Option<BlockState> { self.chunks.set_block_state(pos, state) } diff --git a/azalea/src/pathfinder/world.rs b/azalea/src/pathfinder/world.rs index 013f7c2d..b77183e8 100644 --- a/azalea/src/pathfinder/world.rs +++ b/azalea/src/pathfinder/world.rs @@ -9,7 +9,7 @@ use azalea_core::{ position::{BlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos}, }; use azalea_physics::collision::BlockWithShape; -use azalea_world::Instance; +use azalea_world::{Instance, palette::PalettedContainer}; use parking_lot::RwLock; use super::{mining::MiningCache, rel_block_pos::RelBlockPos}; @@ -26,7 +26,12 @@ pub struct CachedWorld { // we store `PalettedContainer`s instead of `Chunk`s or `Section`s because it doesn't contain // any unnecessary data like heightmaps or biomes. - cached_chunks: RefCell<Vec<(ChunkPos, Vec<azalea_world::palette::PalettedContainer>)>>, + cached_chunks: RefCell< + Vec<( + ChunkPos, + Vec<azalea_world::palette::PalettedContainer<BlockState>>, + )>, + >, last_chunk_cache_index: RefCell<Option<usize>>, cached_blocks: UnsafeCell<CachedSections>, @@ -119,7 +124,7 @@ impl CachedWorld { fn with_section<T>( &self, section_pos: ChunkSectionPos, - f: impl FnOnce(&azalea_world::palette::PalettedContainer) -> T, + f: impl FnOnce(&azalea_world::palette::PalettedContainer<BlockState>) -> T, ) -> Option<T> { if section_pos.y * 16 < self.min_y { // y position is out of bounds @@ -143,7 +148,7 @@ impl CachedWorld { // y position is out of bounds return None; }; - let section: &azalea_world::palette::PalettedContainer = §ions[section_index]; + let section = §ions[section_index]; return Some(f(section)); } @@ -165,7 +170,7 @@ impl CachedWorld { return None; }; *self.last_chunk_cache_index.borrow_mut() = Some(chunk_index); - let section: &azalea_world::palette::PalettedContainer = §ions[section_index]; + let section = §ions[section_index]; return Some(f(section)); } @@ -173,11 +178,11 @@ impl CachedWorld { let chunk = world.chunks.get(&chunk_pos)?; let chunk = chunk.read(); - let sections: Vec<azalea_world::palette::PalettedContainer> = chunk + let sections = chunk .sections .iter() .map(|section| section.states.clone()) - .collect(); + .collect::<Vec<PalettedContainer<BlockState>>>(); if section_index >= sections.len() { // y position is out of bounds |
