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:
parent
b103e6fdc0
commit
d028d7c3e9
15 changed files with 689 additions and 505 deletions
|
@ -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> {
|
||||
|
|
|
@ -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;\">",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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_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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
314
azalea-world/src/palette/container.rs
Normal file
314
azalea-world/src/palette/container.rs
Normal 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()
|
||||
}
|
||||
}
|
115
azalea-world/src/palette/mod.rs
Normal file
115
azalea-world/src/palette/mod.rs
Normal 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,
|
||||
}
|
80
azalea-world/src/palette/tests.rs
Normal file
80
azalea-world/src/palette/tests.rs
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue