From 614b21129804930159f041ce3f51202bc3e1c0b6 Mon Sep 17 00:00:00 2001 From: mat Date: Fri, 17 Jun 2022 23:55:11 -0500 Subject: [PATCH] EntityStorage --- Cargo.lock | 8 ++ azalea-client/src/connect.rs | 4 +- azalea-entity/src/lib.rs | 13 ++- azalea-world/Cargo.toml | 7 +- azalea-world/src/chunk.rs | 182 +++++++++++++++++++++++++++++++++++ azalea-world/src/entity.rs | 44 +++++++++ azalea-world/src/lib.rs | 177 +--------------------------------- 7 files changed, 260 insertions(+), 175 deletions(-) create mode 100644 azalea-world/src/chunk.rs create mode 100644 azalea-world/src/entity.rs diff --git a/Cargo.lock b/Cargo.lock index 85dc7a98..c63ef7a8 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,8 +193,10 @@ version = "0.1.0" dependencies = [ "azalea-block", "azalea-core", + "azalea-entity", "azalea-nbt", "azalea-protocol", + "nohash-hasher", ] [[package]] @@ -771,6 +773,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "num" version = "0.4.0" diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs index 1551ca69..2deaab68 100755 --- a/azalea-client/src/connect.rs +++ b/azalea-client/src/connect.rs @@ -19,7 +19,7 @@ use azalea_protocol::{ }, resolver, ServerAddress, }; -use azalea_world::{ChunkStorage, World}; +use azalea_world::{ChunkStorage, EntityStorage, World}; use std::{fmt::Debug, sync::Arc}; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::sync::Mutex; @@ -266,6 +266,7 @@ impl Client { height, min_y, storage: ChunkStorage::new(16), + entities: EntityStorage::new(), }); conn.lock() @@ -356,6 +357,7 @@ impl Client { y: p.y, z: p.z, }; + p.id; } GamePacket::ClientboundSetEntityDataPacket(p) => { // println!("Got set entity data packet {:?}", p); diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index 506f5780..0e0178b5 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -4,7 +4,18 @@ use azalea_core::EntityPos; pub struct Entity { /// The incrementing numerical id of the entity. pub id: u32, - pub pos: EntityPos, + pos: EntityPos, +} + +impl Entity { + pub fn pos(&self) -> &EntityPos { + &self.pos + } + + pub fn set_pos(&mut self, pos: EntityPos) { + // TODO: check if it moved to another chunk + self.pos = pos; + } } // #[cfg(test)] diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index 79e6155d..e5e9da1d 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -6,7 +6,12 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +azalea-block = {path = "../azalea-block"} azalea-core = {path = "../azalea-core"} +azalea-entity = {path = "../azalea-entity"} azalea-nbt = {path = "../azalea-nbt"} azalea-protocol = {path = "../azalea-protocol"} -azalea-block = {path = "../azalea-block"} +nohash-hasher = "0.2.0" + +[profile.release] +lto = true diff --git a/azalea-world/src/chunk.rs b/azalea-world/src/chunk.rs new file mode 100644 index 00000000..96bbd922 --- /dev/null +++ b/azalea-world/src/chunk.rs @@ -0,0 +1,182 @@ +use crate::bit_storage::BitStorage; +use crate::palette::PalettedContainer; +use crate::palette::PalettedContainerType; +use crate::World; +use azalea_block::BlockState; +use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; +use azalea_protocol::mc_buf::{McBufReadable, McBufWritable}; +use std::{ + io::{Read, Write}, + ops::{Index, IndexMut}, + sync::{Arc, Mutex}, +}; + +const SECTION_HEIGHT: u32 = 16; + +pub struct ChunkStorage { + pub view_center: ChunkPos, + chunk_radius: u32, + view_range: u32, + // chunks is a list of size chunk_radius * chunk_radius + chunks: Vec>>>, +} + +// 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 + } +} + +impl ChunkStorage { + pub fn new(chunk_radius: u32) -> Self { + let view_range = chunk_radius * 2 + 1; + ChunkStorage { + view_center: ChunkPos::new(0, 0), + chunk_radius, + view_range, + chunks: vec![None; (view_range * view_range) as usize], + } + } + + fn get_index(&self, chunk_pos: &ChunkPos) -> usize { + (floor_mod(chunk_pos.x, self.view_range) * self.view_range + + floor_mod(chunk_pos.z, self.view_range)) as usize + } + + pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool { + (chunk_pos.x - self.view_center.x).unsigned_abs() <= self.chunk_radius + && (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius + } + + 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]; + match chunk { + Some(chunk) => Some(chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y)), + None => None, + } + } +} + +impl Index<&ChunkPos> for ChunkStorage { + type Output = Option>>; + + fn index(&self, pos: &ChunkPos) -> &Self::Output { + &self.chunks[self.get_index(pos)] + } +} +impl IndexMut<&ChunkPos> for ChunkStorage { + fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output { + let index = self.get_index(pos); + &mut self.chunks[index] + } +} + +#[derive(Debug)] +pub struct Chunk { + pub sections: Vec
, +} + +impl Chunk { + pub fn read_with_world(buf: &mut impl Read, data: &World) -> Result { + Self::read_with_world_height(buf, data.height) + } + + pub fn read_with_world_height(buf: &mut impl Read, world_height: u32) -> Result { + let section_count = world_height / SECTION_HEIGHT; + let mut sections = Vec::with_capacity(section_count as usize); + for _ in 0..section_count { + let section = Section::read_into(buf)?; + sections.push(section); + } + Ok(Chunk { sections }) + } + + 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); + let min_section_index = min_y.div_floor(16); + (y.div_floor(16) - min_section_index) as 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]; + let chunk_section_pos = ChunkSectionBlockPos::from(pos); + let block_state = section.get(chunk_section_pos); + block_state + } +} + +impl McBufWritable for Chunk { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + for section in &self.sections { + section.write_into(buf)?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct Section { + pub block_count: u16, + pub states: PalettedContainer, + pub biomes: PalettedContainer, +} + +impl McBufReadable for Section { + fn read_into(buf: &mut impl Read) -> Result { + let block_count = u16::read_into(buf)?; + + // this is commented out because the vanilla server is wrong + // assert!( + // block_count <= 16 * 16 * 16, + // "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, + states, + biomes, + }) + } +} + +impl McBufWritable for Section { + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + self.block_count.write_into(buf)?; + self.states.write_into(buf)?; + self.biomes.write_into(buf)?; + Ok(()) + } +} + +impl Section { + 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) + .try_into() + .expect("Invalid block state.") + } +} diff --git a/azalea-world/src/entity.rs b/azalea-world/src/entity.rs new file mode 100644 index 00000000..a25b1f40 --- /dev/null +++ b/azalea-world/src/entity.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; + +use azalea_core::ChunkPos; +use azalea_entity::Entity; +use nohash_hasher::IntMap; + +pub struct EntityStorage { + by_id: IntMap, + // TODO: this doesn't work yet (should be updated in the set_pos method in azalea-entity) + by_chunk: HashMap, +} + +impl EntityStorage { + pub fn new() -> Self { + Self { + by_id: IntMap::default(), + by_chunk: HashMap::default(), + } + } + + /// Add an entity to the storage. + #[inline] + pub fn insert(&mut self, entity: Entity) { + self.by_id.insert(entity.id, entity); + } + + /// Remove an entity from the storage by its id. + #[inline] + pub fn remove_by_id(&mut self, id: u32) { + self.by_id.remove(&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) + } + + /// 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) + } +} diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index e651e455..b47126d4 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -1,14 +1,16 @@ #![feature(int_roundings)] mod bit_storage; +mod chunk; +mod entity; mod palette; -use crate::palette::PalettedContainerType; use azalea_block::BlockState; use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; use azalea_protocol::mc_buf::{McBufReadable, McBufWritable}; pub use bit_storage::BitStorage; -use palette::PalettedContainer; +pub use chunk::{Chunk, ChunkStorage}; +pub use entity::EntityStorage; use std::{ io::{Read, Write}, ops::{Index, IndexMut}, @@ -24,10 +26,9 @@ mod tests { } } -const SECTION_HEIGHT: u32 = 16; - pub struct World { pub storage: ChunkStorage, + pub entities: EntityStorage, pub height: u32, pub min_y: i32, } @@ -83,171 +84,3 @@ impl IndexMut<&ChunkPos> for World { // } // } - -pub struct ChunkStorage { - view_center: ChunkPos, - chunk_radius: u32, - view_range: u32, - // chunks is a list of size chunk_radius * chunk_radius - chunks: Vec>>>, -} - -// 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 - } -} - -impl ChunkStorage { - pub fn new(chunk_radius: u32) -> Self { - let view_range = chunk_radius * 2 + 1; - ChunkStorage { - view_center: ChunkPos::new(0, 0), - chunk_radius, - view_range, - chunks: vec![None; (view_range * view_range) as usize], - } - } - - fn get_index(&self, chunk_pos: &ChunkPos) -> usize { - (floor_mod(chunk_pos.x, self.view_range) * self.view_range - + floor_mod(chunk_pos.z, self.view_range)) as usize - } - - pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool { - (chunk_pos.x - self.view_center.x).unsigned_abs() <= self.chunk_radius - && (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius - } - - 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]; - match chunk { - Some(chunk) => Some(chunk.lock().unwrap().get(&ChunkBlockPos::from(pos), min_y)), - None => None, - } - } -} - -impl Index<&ChunkPos> for ChunkStorage { - type Output = Option>>; - - fn index(&self, pos: &ChunkPos) -> &Self::Output { - &self.chunks[self.get_index(pos)] - } -} -impl IndexMut<&ChunkPos> for ChunkStorage { - fn index_mut<'a>(&'a mut self, pos: &ChunkPos) -> &'a mut Self::Output { - let index = self.get_index(pos); - &mut self.chunks[index] - } -} - -#[derive(Debug)] -pub struct Chunk { - pub sections: Vec
, -} - -impl Chunk { - pub fn read_with_world(buf: &mut impl Read, data: &World) -> Result { - Self::read_with_world_height(buf, data.height) - } - - pub fn read_with_world_height(buf: &mut impl Read, world_height: u32) -> Result { - let section_count = world_height / SECTION_HEIGHT; - let mut sections = Vec::with_capacity(section_count as usize); - for _ in 0..section_count { - let section = Section::read_into(buf)?; - sections.push(section); - } - Ok(Chunk { sections }) - } - - 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); - let min_section_index = min_y.div_floor(16); - (y.div_floor(16) - min_section_index) as 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]; - let chunk_section_pos = ChunkSectionBlockPos::from(pos); - let block_state = section.get(chunk_section_pos); - block_state - } -} - -impl McBufWritable for Chunk { - fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - for section in &self.sections { - section.write_into(buf)?; - } - Ok(()) - } -} - -#[derive(Clone, Debug)] -pub struct Section { - pub block_count: u16, - pub states: PalettedContainer, - pub biomes: PalettedContainer, -} - -impl McBufReadable for Section { - fn read_into(buf: &mut impl Read) -> Result { - let block_count = u16::read_into(buf)?; - - // this is commented out because the vanilla server is wrong - // assert!( - // block_count <= 16 * 16 * 16, - // "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, - states, - biomes, - }) - } -} - -impl McBufWritable for Section { - fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { - self.block_count.write_into(buf)?; - self.states.write_into(buf)?; - self.biomes.write_into(buf)?; - Ok(()) - } -} - -impl Section { - 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) - .try_into() - .expect("Invalid block state.") - } -}