1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

add basic support for getting biome ids in chunks

This commit is contained in:
mat 2025-06-02 07:45:26 +11:00
parent b103e6fdc0
commit d028d7c3e9
15 changed files with 689 additions and 505 deletions

View file

@ -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> {

View file

@ -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;\">",
)
);
}

View file

@ -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();

View file

@ -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;

View file

@ -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 {

View file

@ -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()
}
}

View file

@ -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,

View file

@ -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 = &sections[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_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 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_biome(&self, pos: ChunkSectionBiomePos) -> Biome {
self.biomes.get(pos)
}
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 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)
}
}

View file

@ -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 =

View file

@ -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)
);
}
}
}
}
}

View file

@ -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()
}
}

View file

@ -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,
}

View file

@ -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)
);
}
}
}
}

View file

@ -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)
}

View file

@ -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 = &sections[section_index];
let section = &sections[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 = &sections[section_index];
let section = &sections[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