diff --git a/Cargo.lock b/Cargo.lock index 1ef95311..f3b5df08 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,7 @@ dependencies = [ name = "azalea-world" version = "0.1.0" dependencies = [ + "azalea-block", "azalea-core", "azalea-nbt", "azalea-protocol", diff --git a/azalea-block/block-macros/src/lib.rs b/azalea-block/block-macros/src/lib.rs index d38062d4..6206bb65 100644 --- a/azalea-block/block-macros/src/lib.rs +++ b/azalea-block/block-macros/src/lib.rs @@ -461,9 +461,12 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { block_structs.extend(block_struct); } + let last_state_id = (state_id - 1) as u32; quote! { #property_enums + #[repr(u32)] + #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum BlockState { #block_state_enum_variants } @@ -479,6 +482,15 @@ pub fn make_block_states(input: TokenStream) -> TokenStream { } } } + + impl BlockState { + /// Returns the highest possible state + #[inline] + pub fn max_state() -> u32 { + #last_state_id + } + } + } .into() } diff --git a/azalea-block/src/lib.rs b/azalea-block/src/lib.rs index 01c86b11..95e8ce37 100644 --- a/azalea-block/src/lib.rs +++ b/azalea-block/src/lib.rs @@ -4,3 +4,44 @@ mod blocks; pub use behavior::BlockBehavior; pub use blocks::*; +use std::mem; + +impl BlockState { + /// Transmutes a u32 to a block state. UB if the value is not a valid block + /// state. + #[inline] + pub unsafe fn from_u32_unsafe(state_id: u32) -> Self { + mem::transmute::(state_id) + } + + #[inline] + pub fn is_valid_state(state_id: u32) -> bool { + state_id <= Self::max_state() + } +} + +impl TryFrom for BlockState { + type Error = (); + + /// Safely converts a state id to a block state. + fn try_from(state_id: u32) -> Result { + if Self::is_valid_state(state_id) { + Ok(unsafe { Self::from_u32_unsafe(state_id) }) + } else { + Err(()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_u32() { + assert_eq!(BlockState::try_from(0).unwrap(), BlockState::Air); + + assert!(BlockState::try_from(BlockState::max_state()).is_ok()); + assert!(BlockState::try_from(BlockState::max_state() + 1).is_err()); + } +} diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index e8d81ac3..79e6155d 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -9,3 +9,4 @@ version = "0.1.0" azalea-core = {path = "../azalea-core"} azalea-nbt = {path = "../azalea-nbt"} azalea-protocol = {path = "../azalea-protocol"} +azalea-block = {path = "../azalea-block"} diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs index aba52aef..c69cb216 100644 --- a/azalea-world/src/bit_storage.rs +++ b/azalea-world/src/bit_storage.rs @@ -188,6 +188,12 @@ impl BitStorage { let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits; *cell = *cell & !(self.mask << bit_index) | (value & self.mask) << bit_index; } + + /// The number of entries. + #[inline] + pub fn size(&self) -> usize { + self.size + } } #[cfg(test)] diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index 766c61f0..8777c0a3 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -8,6 +8,7 @@ use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; use azalea_protocol::mc_buf::{McBufReadable, McBufWritable}; pub use bit_storage::BitStorage; use palette::PalettedContainer; +use azalea_block::BlockState; use std::{ io::{Read, Write}, ops::{Index, IndexMut}, @@ -57,7 +58,7 @@ impl World { self.storage.view_center = *pos; } - pub fn get_block_state(&self, pos: &BlockPos) -> Option { + pub fn get_block_state(&self, pos: &BlockPos) -> Option { self.storage.get_block_state(pos, self.min_y) } } @@ -122,7 +123,7 @@ impl ChunkStorage { && (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius } - pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option { + pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option { let chunk_pos = ChunkPos::from(pos); println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos); let chunk = &self[&chunk_pos]; @@ -175,7 +176,7 @@ impl Chunk { (y.div_floor(16) - min_section_index) as u32 } - pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> u32 { + pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> BlockState { let section_index = self.section_index(pos.y, min_y); // TODO: make sure the section exists let section = &self.sections[section_index as usize]; @@ -210,6 +211,14 @@ impl McBufReadable for Section { // "A section has more blocks than what should be possible. This is a bug!" // ); let states = PalettedContainer::read_with_type(buf, &PalettedContainerType::BlockStates)?; + for i in 0..states.storage.size() { + if !BlockState::is_valid_state(states.storage.get(i) as u32) { + return Err(format!( + "Invalid block state {} (index {}) found in section.", + states.storage.get(i), i + )); + } + } let biomes = PalettedContainer::read_with_type(buf, &PalettedContainerType::Biomes)?; Ok(Section { block_count, @@ -229,9 +238,9 @@ impl McBufWritable for Section { } impl Section { - // TODO: return a BlockState instead of a u32 - fn get(&self, pos: ChunkSectionBlockPos) -> u32 { + fn get(&self, pos: ChunkSectionBlockPos) -> BlockState { + // TODO: use the unsafe method and do the check earlier self.states - .get(pos.x as usize, pos.y as usize, pos.z as usize) + .get(pos.x as usize, pos.y as usize, pos.z as usize).try_into().expect("Invalid block state.") } }