diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2022-08-29 20:41:01 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-08-29 20:41:01 -0500 |
| commit | f42d630544165d11a544224ac273d6aaf89d8095 (patch) | |
| tree | 94bd73771ecb582d89a87cdca8e21b2d6573ef12 /azalea-world/src | |
| parent | 2ea804401f54a45765860201d10d0569d07862ec (diff) | |
| download | azalea-drasl-f42d630544165d11a544224ac273d6aaf89d8095.tar.xz | |
Physics (#11)
* Put physics module in azalea-entity
* port aabb
* add more stuff to PositionXYZ
* azalea-physics
* important collision things
* more physics stuff
* backup because i'm about to delete shapes
* more shape stuff
* CubeVoxelShape
* no compile errors???
insane
* impl VoxelShape for ArrayVoxelShape
* Shapes stuff
* collide_x but it doesn't work yet
* binary_search
* it compiles
* Entity has bounding box
* Update discrete_voxel_shape.rs
* Entity::make_bounding_box
* ok i'm about to merge az-entity and az-world
might be a terrible idea which is why i'm committing first
* ok so i moved entity to world
* on_pos and move_entity compiles
* add send_position
* move collision stuff to collision module in az-physics
* dimension is no longer an Option
* start trying to do collision for the client
* collision works :tada:
* start adding palette resizing
* get_and_set (pain)
* it compiles but probably won't work
* add a test
* remove printlns
* add more tests for palette stuff
* ClientboundMoveVec3Packet -> ClientboundMoveEntityPosPacket
i think i changed this on accident once
* palette resizing works
todo: remove the printlns
* Remove printlns in palette.rs
* fix issues from merge
* fixes + work a bit more on physics
* Better entities (#19)
* well it compiles
* add tests to entity storage
* add suggestions in azalea-brigadier
* this probably causes ub
* fix brigadiersuggestions
* get rid of entityid
* test From<EntityMut> for EntityRef
* don't mention other libraries since there's too many
* fix warnings
* do todos in brigadier suggestions
* work on physics
* more physics stuff
* remove trait feature on az-block
i think rust gets confused and compiles the macro without the feature
* bump ahash
* aes tests in az-crypto
* optimize aes's deps
* fix crashes
* fix section_index for negative numbers and test
* fix BlockPos protocol implementation
* remove some debug prints
* prepare to add ai_step
* make ai step work
* clippy
Diffstat (limited to 'azalea-world/src')
| -rw-r--r-- | azalea-world/src/bit_storage.rs | 51 | ||||
| -rw-r--r-- | azalea-world/src/chunk_storage.rs (renamed from azalea-world/src/chunk.rs) | 129 | ||||
| -rw-r--r-- | azalea-world/src/entity/data.rs | 148 | ||||
| -rw-r--r-- | azalea-world/src/entity/dimensions.rs | 23 | ||||
| -rw-r--r-- | azalea-world/src/entity/mod.rs | 316 | ||||
| -rw-r--r-- | azalea-world/src/entity_storage.rs (renamed from azalea-world/src/entity.rs) | 101 | ||||
| -rw-r--r-- | azalea-world/src/lib.rs | 86 | ||||
| -rw-r--r-- | azalea-world/src/palette.rs | 318 |
8 files changed, 1012 insertions, 160 deletions
diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs index fcb3f8f9..2626c312 100644 --- a/azalea-world/src/bit_storage.rs +++ b/azalea-world/src/bit_storage.rs @@ -103,16 +103,21 @@ impl BitStorage { /// Create a new BitStorage with the given number of bits per entry. /// `size` is the number of entries in the BitStorage. pub fn new(bits: usize, size: usize, data: Option<Vec<u64>>) -> Result<Self, BitStorageError> { - // vanilla has this assert but it's not always true for some reason?? - // assert!(bits >= 1 && bits <= 32); - if let Some(data) = &data { + // 0 bit storage if data.is_empty() { - // TODO: make 0 bit storage actually work - return Ok(BitStorage::default()); + return Ok(BitStorage { + data: Vec::with_capacity(0), + bits, + size, + ..Default::default() + }); } } + // vanilla has this assert but it's not always true for some reason?? + // assert!(bits >= 1 && bits <= 32); + let values_per_long = 64 / bits; let magic_index = values_per_long - 1; let (divide_mul, divide_add, divide_shift) = MAGIC[magic_index as usize]; @@ -163,23 +168,43 @@ impl BitStorage { assert!( index < self.size, - "Index {} out of bounds (max is {})", + "Index {} out of bounds (must be less than {})", index, - self.size - 1 + self.size ); + + // 0 bit storage + if self.data.is_empty() { + return 0; + } + let cell_index = self.cell_index(index as u64); let cell = &self.data[cell_index as usize]; let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits; cell >> bit_index & self.mask } + pub fn get_and_set(&mut self, index: usize, value: u64) -> u64 { + // 0 bit storage + if self.data.is_empty() { + return 0; + } + + assert!(index < self.size); + assert!(value <= self.mask); + let cell_index = self.cell_index(index as u64); + let cell = &mut self.data[cell_index as usize]; + let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits; + let old_value = *cell >> (bit_index as u64) & self.mask; + *cell = *cell & !(self.mask << bit_index) | (value & self.mask) << bit_index; + old_value + } + pub fn set(&mut self, index: usize, value: u64) { - // Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)var1); - // Validate.inclusiveBetween(0L, this.mask, (long)var2); - // int var3 = this.cellIndex(var1); - // long var4 = this.data[var3]; - // int var6 = (var1 - var3 * this.valuesPerLong) * this.bits; - // this.data[var3] = var4 & ~(this.mask << var6) | ((long)var2 & this.mask) << var6; + // 0 bit storage + if self.data.is_empty() { + return; + } assert!(index < self.size); assert!(value <= self.mask); diff --git a/azalea-world/src/chunk.rs b/azalea-world/src/chunk_storage.rs index 9d393c2d..4ceea347 100644 --- a/azalea-world/src/chunk.rs +++ b/azalea-world/src/chunk_storage.rs @@ -4,6 +4,7 @@ use crate::Dimension; use azalea_block::BlockState; use azalea_buf::BufReadError; use azalea_buf::{McBufReadable, McBufWritable}; +use azalea_core::floor_mod; use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; use std::fmt::Debug; use std::{ @@ -24,13 +25,33 @@ pub struct ChunkStorage { chunks: Vec<Option<Arc<Mutex<Chunk>>>>, } -// java moment -// it might be possible to replace this with just a modulo, but i copied java's floorMod just in case -fn floor_mod(x: i32, y: u32) -> u32 { - if x < 0 { - y - ((-x) as u32 % y) - } else { - x as u32 % y +#[derive(Debug)] +pub struct Chunk { + pub sections: Vec<Section>, +} + +#[derive(Clone, Debug)] +pub struct Section { + pub block_count: u16, + pub states: PalettedContainer, + pub biomes: PalettedContainer, +} + +impl Default for Section { + fn default() -> Self { + Section { + block_count: 0, + states: PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap(), + biomes: PalettedContainer::new(&PalettedContainerType::Biomes).unwrap(), + } + } +} + +impl Default for Chunk { + fn default() -> Self { + Chunk { + sections: vec![Section::default(); (384 / 16) as usize], + } } } @@ -59,13 +80,24 @@ impl ChunkStorage { pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option<BlockState> { let chunk_pos = ChunkPos::from(pos); - println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos); let chunk = &self[&chunk_pos]; chunk .as_ref() .map(|chunk| chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y)) } + pub fn set_block_state(&self, pos: &BlockPos, state: BlockState, min_y: i32) -> BlockState { + let chunk_pos = ChunkPos::from(pos); + let chunk = &self[&chunk_pos]; + if let Some(chunk) = chunk.as_ref() { + let mut chunk = chunk.lock().unwrap(); + chunk.get_and_set(&ChunkBlockPos::from(pos), state, min_y) + } else { + // nothing is in this chunk, just return air + BlockState::Air + } + } + pub fn replace_with_packet_data( &mut self, pos: &ChunkPos, @@ -104,11 +136,6 @@ impl IndexMut<&ChunkPos> for ChunkStorage { } } -#[derive(Debug)] -pub struct Chunk { - pub sections: Vec<Section>, -} - impl Chunk { pub fn read_with_dimension( buf: &mut impl Read, @@ -131,9 +158,7 @@ impl Chunk { } pub fn section_index(&self, y: i32, min_y: i32) -> u32 { - // TODO: check the build height and stuff, this code will be broken if the min build height is 0 - // (LevelHeightAccessor.getMinSection in vanilla code) - assert!(y >= 0); + assert!(y >= min_y, "y ({}) must be at least {}", y, min_y); let min_section_index = min_y.div_floor(16); (y.div_floor(16) - min_section_index) as u32 } @@ -145,6 +170,27 @@ impl Chunk { let chunk_section_pos = ChunkSectionBlockPos::from(pos); section.get(chunk_section_pos) } + + pub fn get_and_set( + &mut self, + pos: &ChunkBlockPos, + state: BlockState, + min_y: i32, + ) -> BlockState { + let section_index = self.section_index(pos.y, min_y); + // TODO: make sure the section exists + let section = &mut self.sections[section_index as usize]; + let chunk_section_pos = ChunkSectionBlockPos::from(pos); + section.get_and_set(chunk_section_pos, state) + } + + pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) { + let section_index = self.section_index(pos.y, min_y); + // TODO: make sure the section exists + let section = &mut self.sections[section_index as usize]; + let chunk_section_pos = ChunkSectionBlockPos::from(pos); + section.set(chunk_section_pos, state) + } } impl McBufWritable for Chunk { @@ -170,13 +216,6 @@ impl Debug for ChunkStorage { } } -#[derive(Clone, Debug)] -pub struct Section { - pub block_count: u16, - pub states: PalettedContainer, - pub biomes: PalettedContainer, -} - impl McBufReadable for Section { fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> { let block_count = u16::read_from(buf)?; @@ -220,9 +259,47 @@ impl McBufWritable for Section { impl Section { fn get(&self, pos: ChunkSectionBlockPos) -> BlockState { // TODO: use the unsafe method and do the check earlier + let state = self + .states + .get(pos.x as usize, pos.y as usize, pos.z as usize); + // if there's an unknown block assume it's air + BlockState::try_from(state).unwrap_or(BlockState::Air) + } + + fn get_and_set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) -> BlockState { + let previous_state = + self.states + .get_and_set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32); + // if there's an unknown block assume it's air + BlockState::try_from(previous_state).unwrap_or(BlockState::Air) + } + + fn set(&mut self, pos: ChunkSectionBlockPos, state: BlockState) { self.states - .get(pos.x as usize, pos.y as usize, pos.z as usize) - .try_into() - .expect("Invalid block state.") + .set(pos.x as usize, pos.y as usize, pos.z as usize, state as u32); + } +} + +impl Default for ChunkStorage { + fn default() -> Self { + Self::new(8, 384, -64) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_section_index() { + let chunk = Chunk::default(); + assert_eq!(chunk.section_index(0, 0), 0); + assert_eq!(chunk.section_index(128, 0), 8); + assert_eq!(chunk.section_index(127, 0), 7); + assert_eq!(chunk.section_index(0, -64), 4); + assert_eq!(chunk.section_index(-64, -64), 0); + assert_eq!(chunk.section_index(-49, -64), 0); + assert_eq!(chunk.section_index(-48, -64), 1); + assert_eq!(chunk.section_index(128, -64), 12); } } diff --git a/azalea-world/src/entity/data.rs b/azalea-world/src/entity/data.rs new file mode 100644 index 00000000..ff708653 --- /dev/null +++ b/azalea-world/src/entity/data.rs @@ -0,0 +1,148 @@ +use azalea_buf::{BufReadError, McBufVarReadable}; +use azalea_buf::{McBuf, McBufReadable, McBufWritable}; +use azalea_chat::component::Component; +use azalea_core::{BlockPos, Direction, Particle, Slot}; +use std::io::{Read, Write}; +use uuid::Uuid; + +#[derive(Clone, Debug)] +pub struct EntityMetadata(Vec<EntityDataItem>); + +#[derive(Clone, Debug)] +pub struct EntityDataItem { + // we can't identify what the index is for here because we don't know the + // entity type + pub index: u8, + pub value: EntityDataValue, +} + +impl McBufReadable for EntityMetadata { + fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> { + let mut metadata = Vec::new(); + loop { + let index = u8::read_from(buf)?; + if index == 0xff { + break; + } + let value = EntityDataValue::read_from(buf)?; + metadata.push(EntityDataItem { index, value }); + } + Ok(EntityMetadata(metadata)) + } +} + +impl McBufWritable for EntityMetadata { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + for item in &self.0 { + item.index.write_into(buf)?; + item.value.write_into(buf)?; + } + 0xffu8.write_into(buf)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub enum EntityDataValue { + Byte(u8), + // varint + Int(i32), + Float(f32), + String(String), + Component(Component), + OptionalComponent(Option<Component>), + ItemStack(Slot), + Boolean(bool), + Rotations { x: f32, y: f32, z: f32 }, + BlockPos(BlockPos), + OptionalBlockPos(Option<BlockPos>), + Direction(Direction), + OptionalUuid(Option<Uuid>), + // 0 for absent (implies air); otherwise, a block state ID as per the global palette + // this is a varint + OptionalBlockState(Option<i32>), + CompoundTag(azalea_nbt::Tag), + Particle(Particle), + VillagerData(VillagerData), + // 0 for absent; 1 + actual value otherwise. Used for entity IDs. + OptionalUnsignedInt(Option<u32>), + Pose(Pose), +} + +impl McBufReadable for EntityDataValue { + fn read_from(buf: &mut impl Read) -> Result<Self, BufReadError> { + let data_type = u32::var_read_from(buf)?; + Ok(match data_type { + 0 => EntityDataValue::Byte(u8::read_from(buf)?), + 1 => EntityDataValue::Int(i32::var_read_from(buf)?), + 2 => EntityDataValue::Float(f32::read_from(buf)?), + 3 => EntityDataValue::String(String::read_from(buf)?), + 4 => EntityDataValue::Component(Component::read_from(buf)?), + 5 => EntityDataValue::OptionalComponent(Option::<Component>::read_from(buf)?), + 6 => EntityDataValue::ItemStack(Slot::read_from(buf)?), + 7 => EntityDataValue::Boolean(bool::read_from(buf)?), + 8 => EntityDataValue::Rotations { + x: f32::read_from(buf)?, + y: f32::read_from(buf)?, + z: f32::read_from(buf)?, + }, + 9 => EntityDataValue::BlockPos(BlockPos::read_from(buf)?), + 10 => EntityDataValue::OptionalBlockPos(Option::<BlockPos>::read_from(buf)?), + 11 => EntityDataValue::Direction(Direction::read_from(buf)?), + 12 => EntityDataValue::OptionalUuid(Option::<Uuid>::read_from(buf)?), + 13 => EntityDataValue::OptionalBlockState({ + let val = i32::read_from(buf)?; + if val == 0 { + None + } else { + Some(val) + } + }), + 14 => EntityDataValue::CompoundTag(azalea_nbt::Tag::read_from(buf)?), + 15 => EntityDataValue::Particle(Particle::read_from(buf)?), + 16 => EntityDataValue::VillagerData(VillagerData::read_from(buf)?), + 17 => EntityDataValue::OptionalUnsignedInt({ + let val = u32::var_read_from(buf)?; + if val == 0 { + None + } else { + Some((val - 1) as u32) + } + }), + 18 => EntityDataValue::Pose(Pose::read_from(buf)?), + _ => { + return Err(BufReadError::UnexpectedEnumVariant { + id: data_type as i32, + }) + } + }) + } +} + +impl McBufWritable for EntityDataValue { + fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> { + todo!(); + } +} + +#[derive(Clone, Debug, Copy, McBuf)] +pub enum Pose { + Standing = 0, + FallFlying = 1, + Sleeping = 2, + Swimming = 3, + SpinAttack = 4, + Sneaking = 5, + LongJumping = 6, + Dying = 7, +} + +#[derive(Debug, Clone, McBuf)] +pub struct VillagerData { + #[var] + type_: u32, + #[var] + profession: u32, + #[var] + level: u32, +} diff --git a/azalea-world/src/entity/dimensions.rs b/azalea-world/src/entity/dimensions.rs new file mode 100644 index 00000000..1d013d10 --- /dev/null +++ b/azalea-world/src/entity/dimensions.rs @@ -0,0 +1,23 @@ +use azalea_core::{Vec3, AABB}; + +#[derive(Debug, Default)] +pub struct EntityDimensions { + pub width: f32, + pub height: f32, +} + +impl EntityDimensions { + pub fn make_bounding_box(&self, pos: &Vec3) -> AABB { + let radius = (self.width / 2.0) as f64; + let height = self.height as f64; + AABB { + min_x: pos.x - radius, + min_y: pos.y, + min_z: pos.z - radius, + + max_x: pos.x + radius, + max_y: pos.y + height, + max_z: pos.z + radius, + } + } +} diff --git a/azalea-world/src/entity/mod.rs b/azalea-world/src/entity/mod.rs new file mode 100644 index 00000000..37321e0a --- /dev/null +++ b/azalea-world/src/entity/mod.rs @@ -0,0 +1,316 @@ +mod data; +mod dimensions; + +use crate::Dimension; +use azalea_core::{BlockPos, Vec3, AABB}; +pub use data::*; +pub use dimensions::*; +use std::ops::{Deref, DerefMut}; +use std::ptr::NonNull; +use uuid::Uuid; + +#[derive(Debug)] +pub struct EntityRef<'d> { + /// The dimension this entity is in. + pub dimension: &'d Dimension, + /// The incrementing numerical id of the entity. + pub id: u32, + pub data: &'d EntityData, +} + +impl<'d> EntityRef<'d> { + pub fn new(dimension: &'d Dimension, id: u32, data: &'d EntityData) -> Self { + // TODO: have this be based on the entity type + Self { + dimension, + id, + data, + } + } +} + +impl<'d> EntityRef<'d> { + #[inline] + pub fn pos(&self) -> &Vec3 { + &self.pos + } + + pub fn make_bounding_box(&self) -> AABB { + self.dimensions.make_bounding_box(&self.pos()) + } + + /// Get the position of the block below the entity, but a little lower. + pub fn on_pos_legacy(&self) -> BlockPos { + self.on_pos(0.2) + } + + // int x = Mth.floor(this.position.x); + // int y = Mth.floor(this.position.y - (double)var1); + // int z = Mth.floor(this.position.z); + // BlockPos var5 = new BlockPos(x, y, z); + // if (this.level.getBlockState(var5).isAir()) { + // BlockPos var6 = var5.below(); + // BlockState var7 = this.level.getBlockState(var6); + // if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || var7.getBlock() instanceof FenceGateBlock) { + // return var6; + // } + // } + // return var5; + pub fn on_pos(&self, offset: f32) -> BlockPos { + let x = self.pos().x.floor() as i32; + let y = (self.pos().y - offset as f64).floor() as i32; + let z = self.pos().z.floor() as i32; + let pos = BlockPos { x, y, z }; + + // TODO: check if block below is a fence, wall, or fence gate + // let block_pos = pos.below(); + // let block_state = dimension.get_block_state(&block_pos); + // if block_state == Some(BlockState::Air) { + // let block_pos_below = block_pos.below(); + // let block_state_below = dimension.get_block_state(&block_pos_below); + // if let Some(block_state_below) = block_state_below { + // if block_state_below.is_fence() + // || block_state_below.is_wall() + // || block_state_below.is_fence_gate() + // { + // return block_pos_below; + // } + // } + // } + + pos + } +} + +#[derive(Debug)] +pub struct EntityMut<'d> { + /// The dimension this entity is in. + pub dimension: &'d mut Dimension, + /// The incrementing numerical id of the entity. + pub id: u32, + pub data: NonNull<EntityData>, +} + +impl<'d> EntityMut<'d> { + pub fn new(dimension: &'d mut Dimension, id: u32, data: NonNull<EntityData>) -> Self { + Self { + dimension, + id, + data, + } + } + + /// Sets the position of the entity. This doesn't update the cache in + /// azalea-world, and should only be used within azalea-world! + pub unsafe fn move_unchecked(&mut self, new_pos: Vec3) { + self.pos = new_pos; + let bounding_box = self.make_bounding_box(); + self.bounding_box = bounding_box; + } + + pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) { + self.y_rot = y_rot.clamp(-90.0, 90.0) % 360.0; + self.x_rot = x_rot % 360.0; + // TODO: minecraft also sets yRotO and xRotO to xRot and yRot ... but idk what they're used for so + } + + pub fn move_relative(&mut self, speed: f32, acceleration: &Vec3) { + let input_vector = self.input_vector(speed, acceleration); + self.delta += input_vector; + } + + pub fn input_vector(&self, speed: f32, acceleration: &Vec3) -> Vec3 { + let distance = acceleration.length_squared(); + if distance < 1.0E-7 { + return Vec3::default(); + } + let acceleration = if distance > 1.0 { + acceleration.normalize() + } else { + *acceleration + } + .scale(speed as f64); + let y_rot = f32::sin(self.y_rot * 0.017453292f32); + let x_rot = f32::cos(self.y_rot * 0.017453292f32); + Vec3 { + x: acceleration.x * (x_rot as f64) - acceleration.z * (y_rot as f64), + y: acceleration.y, + z: acceleration.z * (x_rot as f64) + acceleration.x * (y_rot as f64), + } + } +} + +impl<'d> EntityMut<'d> { + #[inline] + pub fn pos(&self) -> &Vec3 { + &self.pos + } + + pub fn make_bounding_box(&self) -> AABB { + self.dimensions.make_bounding_box(&self.pos()) + } + + /// Get the position of the block below the entity, but a little lower. + pub fn on_pos_legacy(&self) -> BlockPos { + self.on_pos(0.2) + } + + // int x = Mth.floor(this.position.x); + // int y = Mth.floor(this.position.y - (double)var1); + // int z = Mth.floor(this.position.z); + // BlockPos var5 = new BlockPos(x, y, z); + // if (this.level.getBlockState(var5).isAir()) { + // BlockPos var6 = var5.below(); + // BlockState var7 = this.level.getBlockState(var6); + // if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) || var7.getBlock() instanceof FenceGateBlock) { + // return var6; + // } + // } + // return var5; + pub fn on_pos(&self, offset: f32) -> BlockPos { + let x = self.pos().x.floor() as i32; + let y = (self.pos().y - offset as f64).floor() as i32; + let z = self.pos().z.floor() as i32; + let pos = BlockPos { x, y, z }; + + // TODO: check if block below is a fence, wall, or fence gate + // let block_pos = pos.below(); + // let block_state = dimension.get_block_state(&block_pos); + // if block_state == Some(BlockState::Air) { + // let block_pos_below = block_pos.below(); + // let block_state_below = dimension.get_block_state(&block_pos_below); + // if let Some(block_state_below) = block_state_below { + // if block_state_below.is_fence() + // || block_state_below.is_wall() + // || block_state_below.is_fence_gate() + // { + // return block_pos_below; + // } + // } + // } + + pos + } +} + +impl<'d> From<EntityMut<'d>> for EntityRef<'d> { + fn from(entity: EntityMut<'d>) -> EntityRef<'d> { + let data = unsafe { entity.data.as_ref() }; + EntityRef { + dimension: entity.dimension, + id: entity.id, + data, + } + } +} + +impl Deref for EntityMut<'_> { + type Target = EntityData; + + fn deref(&self) -> &Self::Target { + unsafe { self.data.as_ref() } + } +} + +impl DerefMut for EntityMut<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.data.as_mut() } + } +} + +impl Deref for EntityRef<'_> { + type Target = EntityData; + + fn deref(&self) -> &Self::Target { + self.data + } +} + +#[derive(Debug)] +pub struct EntityData { + pub uuid: Uuid, + /// The position of the entity right now. + /// This can be changde with unsafe_move, but the correct way is with dimension.move_entity + pos: Vec3, + /// The position of the entity last tick. + pub last_pos: Vec3, + pub delta: Vec3, + + /// X acceleration. + pub xxa: f32, + /// Y acceleration. + pub yya: f32, + /// Z acceleration. + pub zza: f32, + + pub x_rot: f32, + pub y_rot: f32, + + pub x_rot_last: f32, + pub y_rot_last: f32, + + pub on_ground: bool, + pub last_on_ground: bool, + + /// The width and height of the entity. + pub dimensions: EntityDimensions, + /// The bounding box of the entity. This is more than just width and height, unlike dimensions. + pub bounding_box: AABB, +} + +impl EntityData { + pub fn new(uuid: Uuid, pos: Vec3) -> Self { + let dimensions = EntityDimensions { + width: 0.8, + height: 1.8, + }; + + Self { + uuid, + pos, + last_pos: pos, + delta: Vec3::default(), + + xxa: 0., + yya: 0., + zza: 0., + + x_rot: 0., + y_rot: 0., + + y_rot_last: 0., + x_rot_last: 0., + + on_ground: false, + last_on_ground: false, + + // TODO: have this be based on the entity type + bounding_box: dimensions.make_bounding_box(&pos), + dimensions, + } + } + + #[inline] + pub fn pos(&self) -> &Vec3 { + &self.pos + } + + pub(crate) unsafe fn as_ptr(&mut self) -> NonNull<EntityData> { + NonNull::new_unchecked(self as *mut EntityData) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_mut_entity_to_ref_entity() { + let mut dim = Dimension::default(); + let uuid = Uuid::from_u128(100); + dim.add_entity(0, EntityData::new(uuid, Vec3::default())); + let entity: EntityMut = dim.entity_mut(0).unwrap(); + let entity_ref: EntityRef = entity.into(); + assert_eq!(entity_ref.uuid, uuid); + } +} diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity_storage.rs index 69529294..c7fd9c0b 100644 --- a/azalea-world/src/entity.rs +++ b/azalea-world/src/entity_storage.rs @@ -1,5 +1,5 @@ +use crate::entity::EntityData; use azalea_core::ChunkPos; -use azalea_entity::Entity; use log::warn; use nohash_hasher::{IntMap, IntSet}; use std::collections::HashMap; @@ -7,41 +7,41 @@ use uuid::Uuid; #[derive(Debug)] pub struct EntityStorage { - by_id: IntMap<u32, Entity>, - by_chunk: HashMap<ChunkPos, IntSet<u32>>, - by_uuid: HashMap<Uuid, u32>, + data_by_id: IntMap<u32, EntityData>, + id_by_chunk: HashMap<ChunkPos, IntSet<u32>>, + id_by_uuid: HashMap<Uuid, u32>, } impl EntityStorage { pub fn new() -> Self { Self { - by_id: IntMap::default(), - by_chunk: HashMap::default(), - by_uuid: HashMap::default(), + data_by_id: IntMap::default(), + id_by_chunk: HashMap::default(), + id_by_uuid: HashMap::default(), } } /// Add an entity to the storage. #[inline] - pub fn insert(&mut self, entity: Entity) { - self.by_chunk + pub fn insert(&mut self, id: u32, entity: EntityData) { + self.id_by_chunk .entry(ChunkPos::from(entity.pos())) .or_default() - .insert(entity.id); - self.by_uuid.insert(entity.uuid, entity.id); - self.by_id.insert(entity.id, entity); + .insert(id); + self.id_by_uuid.insert(entity.uuid, id); + self.data_by_id.insert(id, entity); } /// Remove an entity from the storage by its id. #[inline] pub fn remove_by_id(&mut self, id: u32) { - if let Some(entity) = self.by_id.remove(&id) { + if let Some(entity) = self.data_by_id.remove(&id) { let entity_chunk = ChunkPos::from(entity.pos()); let entity_uuid = entity.uuid; - if self.by_chunk.remove(&entity_chunk).is_none() { + if self.id_by_chunk.remove(&entity_chunk).is_none() { warn!("Tried to remove entity with id {id} from chunk {entity_chunk:?} but it was not found."); } - if self.by_uuid.remove(&entity_uuid).is_none() { + if self.id_by_uuid.remove(&entity_uuid).is_none() { warn!("Tried to remove entity with id {id} from uuid {entity_uuid:?} but it was not found."); } } else { @@ -49,36 +49,46 @@ impl EntityStorage { } } + /// Check if there is an entity that exists with the given id. + #[inline] + pub fn contains_id(&self, id: &u32) -> bool { + self.data_by_id.contains_key(id) + } + /// Get a reference to an entity by its id. #[inline] - pub fn get_by_id(&self, id: u32) -> Option<&Entity> { - self.by_id.get(&id) + pub fn get_by_id(&self, id: u32) -> Option<&EntityData> { + self.data_by_id.get(&id) } /// Get a mutable reference to an entity by its id. #[inline] - pub fn get_mut_by_id(&mut self, id: u32) -> Option<&mut Entity> { - self.by_id.get_mut(&id) + pub fn get_mut_by_id<'d>(&'d mut self, id: u32) -> Option<&'d mut EntityData> { + self.data_by_id.get_mut(&id) } /// Get a reference to an entity by its uuid. #[inline] - pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> { - self.by_uuid.get(uuid).and_then(|id| self.by_id.get(id)) + pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> { + self.id_by_uuid + .get(uuid) + .and_then(|id| self.data_by_id.get(id)) } /// Get a mutable reference to an entity by its uuid. #[inline] - pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut Entity> { - self.by_uuid.get(uuid).and_then(|id| self.by_id.get_mut(id)) + pub fn get_mut_by_uuid(&mut self, uuid: &Uuid) -> Option<&mut EntityData> { + self.id_by_uuid + .get(uuid) + .and_then(|id| self.data_by_id.get_mut(id)) } /// Clear all entities in a chunk. pub fn clear_chunk(&mut self, chunk: &ChunkPos) { - if let Some(entities) = self.by_chunk.remove(chunk) { + if let Some(entities) = self.id_by_chunk.remove(chunk) { for entity_id in entities { - if let Some(entity) = self.by_id.remove(&entity_id) { - self.by_uuid.remove(&entity.uuid); + if let Some(entity) = self.data_by_id.remove(&entity_id) { + self.id_by_uuid.remove(&entity.uuid); } else { warn!("While clearing chunk {chunk:?}, found an entity that isn't in by_id {entity_id}."); } @@ -94,10 +104,10 @@ impl EntityStorage { old_chunk: &ChunkPos, new_chunk: &ChunkPos, ) { - if let Some(entities) = self.by_chunk.get_mut(old_chunk) { + if let Some(entities) = self.id_by_chunk.get_mut(old_chunk) { entities.remove(&entity_id); } - self.by_chunk + self.id_by_chunk .entry(*new_chunk) .or_default() .insert(entity_id); @@ -105,24 +115,24 @@ impl EntityStorage { /// Get an iterator over all entities. #[inline] - pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> { - self.by_id.values() + pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> { + self.data_by_id.values() } - pub fn find_one_entity<F>(&self, mut f: F) -> Option<&Entity> + pub fn find_one_entity<F>(&self, mut f: F) -> Option<&EntityData> where - F: FnMut(&Entity) -> bool, + F: FnMut(&EntityData) -> bool, { self.entities().find(|&entity| f(entity)) } - pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<&Entity> + pub fn find_one_entity_in_chunk<F>(&self, chunk: &ChunkPos, mut f: F) -> Option<&EntityData> where - F: FnMut(&Entity) -> bool, + F: FnMut(&EntityData) -> bool, { - if let Some(entities) = self.by_chunk.get(chunk) { + if let Some(entities) = self.id_by_chunk.get(chunk) { for entity_id in entities { - if let Some(entity) = self.by_id.get(entity_id) { + if let Some(entity) = self.data_by_id.get(entity_id) { if f(entity) { return Some(entity); } @@ -138,3 +148,22 @@ impl Default for EntityStorage { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + use azalea_core::Vec3; + + #[test] + fn test_store_entity() { + let mut storage = EntityStorage::new(); + assert!(storage.get_by_id(0).is_none()); + + let uuid = Uuid::from_u128(100); + storage.insert(0, EntityData::new(uuid, Vec3::default())); + assert_eq!(storage.get_by_id(0).unwrap().uuid, uuid); + + storage.remove_by_id(0); + assert!(storage.get_by_id(0).is_none()); + } +} diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index 646ba1d9..77bb0f0f 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -1,17 +1,18 @@ #![feature(int_roundings)] mod bit_storage; -mod chunk; -mod entity; +mod chunk_storage; +pub mod entity; +mod entity_storage; mod palette; use azalea_block::BlockState; use azalea_buf::BufReadError; -use azalea_core::{BlockPos, ChunkPos, EntityPos, PositionDelta8}; -use azalea_entity::Entity; +use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3}; pub use bit_storage::BitStorage; -pub use chunk::{Chunk, ChunkStorage}; -pub use entity::EntityStorage; +pub use chunk_storage::{Chunk, ChunkStorage}; +use entity::{EntityData, EntityMut, EntityRef}; +pub use entity_storage::EntityStorage; use std::{ io::Read, ops::{Index, IndexMut}, @@ -20,17 +21,10 @@ use std::{ use thiserror::Error; use uuid::Uuid; -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} - /// A dimension is a collection of chunks and entities. -#[derive(Debug)] +/// Minecraft calls these "Levels", Fabric calls them "Worlds", Minestom calls them "Instances". +/// Yeah. +#[derive(Debug, Default)] pub struct Dimension { chunk_storage: ChunkStorage, entity_storage: EntityStorage, @@ -66,20 +60,20 @@ impl Dimension { self.chunk_storage.get_block_state(pos, self.min_y()) } - pub fn move_entity( - &mut self, - entity_id: u32, - new_pos: EntityPos, - ) -> Result<(), MoveEntityError> { - let entity = self - .entity_storage - .get_mut_by_id(entity_id) + pub fn set_block_state(&mut self, pos: &BlockPos, state: BlockState) -> BlockState { + self.chunk_storage.set_block_state(pos, state, self.min_y()) + } + + pub fn set_entity_pos(&mut self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> { + println!("set_entity_pos({}, {:?})", entity_id, new_pos); + let mut entity = self + .entity_mut(entity_id) .ok_or(MoveEntityError::EntityDoesNotExist)?; let old_chunk = ChunkPos::from(entity.pos()); let new_chunk = ChunkPos::from(&new_pos); // this is fine because we update the chunk below - entity.unsafe_move(new_pos); + unsafe { entity.move_unchecked(new_pos) }; if old_chunk != new_chunk { self.entity_storage .update_entity_chunk(entity_id, &old_chunk, &new_chunk); @@ -92,9 +86,8 @@ impl Dimension { entity_id: u32, delta: &PositionDelta8, ) -> Result<(), MoveEntityError> { - let entity = self - .entity_storage - .get_mut_by_id(entity_id) + let mut entity = self + .entity_mut(entity_id) .ok_or(MoveEntityError::EntityDoesNotExist)?; let new_pos = entity.pos().with_delta(delta); @@ -102,7 +95,7 @@ impl Dimension { let new_chunk = ChunkPos::from(&new_pos); // this is fine because we update the chunk below - entity.unsafe_move(new_pos); + unsafe { entity.move_unchecked(new_pos) }; if old_chunk != new_chunk { self.entity_storage .update_entity_chunk(entity_id, &old_chunk, &new_chunk); @@ -110,8 +103,8 @@ impl Dimension { Ok(()) } - pub fn add_entity(&mut self, entity: Entity) { - self.entity_storage.insert(entity); + pub fn add_entity(&mut self, id: u32, entity: EntityData) { + self.entity_storage.insert(id, entity); } pub fn height(&self) -> u32 { @@ -122,27 +115,46 @@ impl Dimension { self.chunk_storage.min_y } - pub fn entity_by_id(&self, id: u32) -> Option<&Entity> { + pub fn entity_data_by_id(&self, id: u32) -> Option<&EntityData> { self.entity_storage.get_by_id(id) } - pub fn mut_entity_by_id(&mut self, id: u32) -> Option<&mut Entity> { + pub fn entity_data_mut_by_id(&mut self, id: u32) -> Option<&mut EntityData> { self.entity_storage.get_mut_by_id(id) } - pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> { + pub fn entity<'d>(&'d self, id: u32) -> Option<EntityRef<'d>> { + let entity_data = self.entity_storage.get_by_id(id); + if let Some(entity_data) = entity_data { + Some(EntityRef::new(self, id, entity_data)) + } else { + None + } + } + + pub fn entity_mut<'d>(&'d mut self, id: u32) -> Option<EntityMut<'d>> { + let entity_data = self.entity_storage.get_mut_by_id(id); + if let Some(entity_data) = entity_data { + let entity_ptr = unsafe { entity_data.as_ptr() }; + Some(EntityMut::new(self, id, entity_ptr)) + } else { + None + } + } + + pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&EntityData> { self.entity_storage.get_by_uuid(uuid) } /// Get an iterator over all entities. #[inline] - pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, Entity> { + pub fn entities(&self) -> std::collections::hash_map::Values<'_, u32, EntityData> { self.entity_storage.entities() } - pub fn find_one_entity<F>(&self, mut f: F) -> Option<&Entity> + pub fn find_one_entity<F>(&self, mut f: F) -> Option<&EntityData> where - F: FnMut(&Entity) -> bool, + F: FnMut(&EntityData) -> bool, { self.entity_storage.find_one_entity(|entity| f(entity)) } diff --git a/azalea-world/src/palette.rs b/azalea-world/src/palette.rs index 2473a7e5..4e0f9a96 100644 --- a/azalea-world/src/palette.rs +++ b/azalea-world/src/palette.rs @@ -21,23 +21,27 @@ pub struct PalettedContainer { } impl PalettedContainer { + pub fn new(container_type: &'static PalettedContainerType) -> Result<Self, String> { + let palette = Palette::SingleValue(0); + let size = container_type.size(); + let storage = BitStorage::new(0, size, Some(vec![])).unwrap(); + + Ok(PalettedContainer { + bits_per_entry: 0, + palette, + storage, + container_type: *container_type, + }) + } + pub fn read_with_type( buf: &mut impl Read, - type_: &'static PalettedContainerType, + container_type: &'static PalettedContainerType, ) -> Result<Self, BufReadError> { let bits_per_entry = buf.read_byte()?; - let palette = match type_ { - PalettedContainerType::BlockStates => { - Palette::block_states_read_with_bits_per_entry(buf, bits_per_entry)? - } - PalettedContainerType::Biomes => { - Palette::biomes_read_with_bits_per_entry(buf, bits_per_entry)? - } - }; - let size = match type_ { - PalettedContainerType::BlockStates => 4096, - PalettedContainerType::Biomes => 64, - }; + let palette_type = PaletteType::from_bits_and_type(bits_per_entry, container_type); + let palette = palette_type.read(buf)?; + let size = container_type.size(); let data = Vec::<u64>::read_from(buf)?; debug_assert!( @@ -50,24 +54,130 @@ impl PalettedContainer { bits_per_entry, palette, storage, - container_type: *type_, + container_type: *container_type, }) } + /// Calculates the index of the given coordinates. pub fn get_index(&self, x: usize, y: usize, z: usize) -> usize { - let size_bits = match self.container_type { - PalettedContainerType::BlockStates => 4, - PalettedContainerType::Biomes => 2, - }; + let size_bits = self.container_type.size_bits(); (((y << size_bits) | z) << size_bits) | x } - pub fn get(&self, x: usize, y: usize, z: usize) -> u32 { - let paletted_value = self.storage.get(self.get_index(x, y, z)); - println!("palette: {:?}", self.palette); + /// Returns the value at the given index. + pub fn get_at_index(&self, index: usize) -> u32 { + let paletted_value = self.storage.get(index); 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) -> u32 { + // 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.get_index(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: u32) -> u32 { + let paletted_value = self.id_for(value); + self.storage + .get_and_set(self.get_index(x, y, z), paletted_value as u64) as u32 + } + + /// 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: u32) { + 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: u32) { + self.set_at_index(self.get_index(x, y, z), value); + } + + fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer { + let new_palette_type = + PaletteType::from_bits_and_type(bits_per_entry, &self.container_type); + // note for whoever is trying to optimize this: vanilla has this + // but it causes a stack overflow since it's not changing the bits per entry + // i don't know how to fix this properly so glhf + // let old_palette_type: PaletteType = (&self.palette).into(); + // if 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.into_empty_palette(1usize << (bits_per_entry as usize)); + let palette = new_palette_type.into_empty_palette(); + PalettedContainer { + bits_per_entry, + palette, + storage, + container_type: self.container_type, + } + } + + fn on_resize(&mut self, bits_per_entry: u8, value: u32) -> usize { + if bits_per_entry > 5 { + panic!("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; + let id = self.id_for(value); + id + } + + 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: u32) -> 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 as usize; + } + 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 also i don't care + if let Some(index) = palette.iter().position(|v| *v == value) { + return index as usize; + } + 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 as usize, + } + } } impl McBufWritable for PalettedContainer { @@ -79,45 +189,37 @@ impl McBufWritable for PalettedContainer { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PaletteType { + SingleValue, + Linear, + Hashmap, + Global, +} + #[derive(Clone, Debug)] pub enum Palette { /// ID of the corresponding entry in its global palette SingleValue(u32), + // 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<u32>), Hashmap(Vec<u32>), Global, } impl Palette { - pub fn block_states_read_with_bits_per_entry( - buf: &mut impl Read, - bits_per_entry: u8, - ) -> Result<Palette, BufReadError> { - Ok(match bits_per_entry { - 0 => Palette::SingleValue(u32::var_read_from(buf)?), - 1..=4 => Palette::Linear(Vec::<u32>::var_read_from(buf)?), - 5..=8 => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?), - _ => Palette::Global, - }) - } - - pub fn biomes_read_with_bits_per_entry( - buf: &mut impl Read, - bits_per_entry: u8, - ) -> Result<Palette, BufReadError> { - Ok(match bits_per_entry { - 0 => Palette::SingleValue(u32::var_read_from(buf)?), - 1..=3 => Palette::Linear(Vec::<u32>::var_read_from(buf)?), - _ => Palette::Global, - }) - } - - pub fn value_for(&self, value: usize) -> u32 { + pub fn value_for(&self, id: usize) -> u32 { match self { Palette::SingleValue(v) => *v, - Palette::Linear(v) => v[value], - Palette::Hashmap(v) => v[value], - Palette::Global => value as u32, + Palette::Linear(v) => v[id], + Palette::Hashmap(v) => { + if id >= v.len() { + 0 + } else { + v[id] + } + } + Palette::Global => id as u32, } } } @@ -139,3 +241,123 @@ impl McBufWritable for Palette { Ok(()) } } + +impl PaletteType { + pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerType) -> Self { + match container_type { + PalettedContainerType::BlockStates => match bits_per_entry { + 0 => PaletteType::SingleValue, + 1..=4 => PaletteType::Linear, + 5..=8 => PaletteType::Hashmap, + _ => PaletteType::Global, + }, + PalettedContainerType::Biomes => match bits_per_entry { + 0 => PaletteType::SingleValue, + 1..=3 => PaletteType::Linear, + _ => PaletteType::Global, + }, + } + } + + pub fn read(&self, buf: &mut impl Read) -> Result<Palette, BufReadError> { + Ok(match self { + PaletteType::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?), + PaletteType::Linear => Palette::Linear(Vec::<u32>::var_read_from(buf)?), + PaletteType::Hashmap => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?), + PaletteType::Global => Palette::Global, + }) + } + + pub fn into_empty_palette(&self) -> Palette { + match self { + PaletteType::SingleValue => Palette::SingleValue(0), + PaletteType::Linear => Palette::Linear(Vec::new()), + PaletteType::Hashmap => Palette::Hashmap(Vec::new()), + PaletteType::Global => Palette::Global, + } + } +} + +impl From<&Palette> for PaletteType { + fn from(palette: &Palette) -> Self { + match palette { + Palette::SingleValue(_) => PaletteType::SingleValue, + Palette::Linear(_) => PaletteType::Linear, + Palette::Hashmap(_) => PaletteType::Hashmap, + Palette::Global => PaletteType::Global, + } + } +} + +impl PalettedContainerType { + fn size_bits(&self) -> usize { + match self { + PalettedContainerType::BlockStates => 4, + PalettedContainerType::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(&PalettedContainerType::BlockStates).unwrap(); + + assert_eq!(palette_container.bits_per_entry, 0); + assert_eq!(palette_container.get_at_index(0), 0); + assert_eq!( + PaletteType::from(&palette_container.palette), + PaletteType::SingleValue + ); + palette_container.set_at_index(0, 1); + assert_eq!(palette_container.get_at_index(0), 1); + assert_eq!( + PaletteType::from(&palette_container.palette), + PaletteType::Linear + ); + } + + #[test] + fn test_resize_0_bits_to_5() { + let mut palette_container = + PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap(); + + palette_container.set_at_index(0, 0); // 0 bits + assert_eq!(palette_container.bits_per_entry, 0); + + palette_container.set_at_index(1, 1); // 1 bit + assert_eq!(palette_container.bits_per_entry, 1); + + palette_container.set_at_index(2, 2); // 2 bits + assert_eq!(palette_container.bits_per_entry, 2); + palette_container.set_at_index(3, 3); + + palette_container.set_at_index(4, 4); // 3 bits + assert_eq!(palette_container.bits_per_entry, 3); + palette_container.set_at_index(5, 5); + palette_container.set_at_index(6, 6); + palette_container.set_at_index(7, 7); + + palette_container.set_at_index(8, 8); // 4 bits + assert_eq!(palette_container.bits_per_entry, 4); + palette_container.set_at_index(9, 9); + palette_container.set_at_index(10, 10); + palette_container.set_at_index(11, 11); + palette_container.set_at_index(12, 12); + palette_container.set_at_index(13, 13); + palette_container.set_at_index(14, 14); + palette_container.set_at_index(15, 15); + assert_eq!(palette_container.bits_per_entry, 4); + + palette_container.set_at_index(16, 16); // 5 bits + assert_eq!(palette_container.bits_per_entry, 5); + } +} |
