diff --git a/Cargo.lock b/Cargo.lock index 6f61f0c1c..168ac60bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -457,6 +457,7 @@ dependencies = [ "bevy_app", "bevy_ecs", "bevy_time", + "nohash-hasher", "once_cell", "parking_lot", "tracing", @@ -543,10 +544,10 @@ dependencies = [ "bevy_ecs", "criterion", "derive_more", - "enum-as-inner", "nohash-hasher", "once_cell", "parking_lot", + "rustc-hash", "serde", "serde_json", "simdnbt", diff --git a/Cargo.toml b/Cargo.toml old mode 100755 new mode 100644 diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index 3855a715c..d301aba15 100755 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -397,9 +397,7 @@ impl simdnbt::FromNbtTag for FormattedText { trace!("keybind text components aren't yet supported"); return None; } else { - let Some(_nbt) = compound.get("nbt") else { - return None; - }; + let _nbt = compound.get("nbt")?; let _separator = FormattedText::parse_separator_nbt(compound)?; let _interpret = match compound.get("interpret") { diff --git a/azalea-physics/Cargo.toml b/azalea-physics/Cargo.toml index 340dc096e..043c0548c 100644 --- a/azalea-physics/Cargo.toml +++ b/azalea-physics/Cargo.toml @@ -20,6 +20,7 @@ bevy_ecs = "0.13.0" tracing = "0.1.40" once_cell = "1.19.0" parking_lot = "^0.12.1" +nohash-hasher = "0.2.0" [dev-dependencies] bevy_time = "0.13.0" diff --git a/azalea-physics/src/collision/world_collisions.rs b/azalea-physics/src/collision/world_collisions.rs index c4c6a846e..cb721f004 100644 --- a/azalea-physics/src/collision/world_collisions.rs +++ b/azalea-physics/src/collision/world_collisions.rs @@ -4,11 +4,11 @@ use azalea_block::BlockState; use azalea_core::{ cursor3d::{Cursor3d, CursorIterationType}, math::EPSILON, - position::{ChunkPos, ChunkSectionPos}, + position::{BlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos}, }; use azalea_world::{Chunk, Instance}; use parking_lot::RwLock; -use std::{ops::Deref, sync::Arc}; +use std::sync::Arc; pub fn get_block_collisions(world: &Instance, aabb: AABB) -> BlockCollisions<'_> { BlockCollisions::new(world, aabb) @@ -20,6 +20,8 @@ pub struct BlockCollisions<'a> { pub entity_shape: VoxelShape, pub cursor: Cursor3d, pub only_suffocating_blocks: bool, + + cached_sections: Vec<(ChunkSectionPos, azalea_world::Section)>, } impl<'a> BlockCollisions<'a> { @@ -40,6 +42,8 @@ impl<'a> BlockCollisions<'a> { entity_shape: VoxelShape::from(aabb), cursor, only_suffocating_blocks: false, + + cached_sections: Vec::new(), } } @@ -63,6 +67,36 @@ impl<'a> BlockCollisions<'a> { self.world.chunks.get(&chunk_pos) } + + fn get_block_state(&mut self, block_pos: BlockPos) -> BlockState { + let section_pos = ChunkSectionPos::from(block_pos); + let section_block_pos = ChunkSectionBlockPos::from(block_pos); + + for (cached_section_pos, cached_section) in &self.cached_sections { + if section_pos == *cached_section_pos { + return cached_section.get(section_block_pos); + } + } + + let chunk = self.get_chunk(block_pos.x, block_pos.z); + let Some(chunk) = chunk else { + return BlockState::AIR; + }; + let chunk = chunk.read(); + + let sections = &chunk.sections; + let section_index = + azalea_world::chunk_storage::section_index(block_pos.y, self.world.chunks.min_y) + as usize; + + let Some(section) = sections.get(section_index) else { + return BlockState::AIR; + }; + + self.cached_sections.push((section_pos, section.clone())); + + section.get(section_block_pos) + } } impl<'a> Iterator for BlockCollisions<'a> { @@ -74,24 +108,18 @@ impl<'a> Iterator for BlockCollisions<'a> { continue; } - let chunk = self.get_chunk(item.pos.x, item.pos.z); - let Some(chunk) = chunk else { - continue; - }; + let block_state = self.get_block_state(item.pos); - let pos = item.pos; - let block_state: BlockState = chunk - .read() - .get(&(&pos).into(), self.world.chunks.min_y) - .unwrap_or(BlockState::AIR); + if block_state.is_air() { + // fast path since we can't collide with air + continue; + } // TODO: continue if self.only_suffocating_blocks and the block is not // suffocating - let block_shape = block_state.shape(); - // if it's a full block do a faster collision check - if block_shape == BLOCK_SHAPE.deref() { + if block_state.is_shape_full() { if !self.aabb.intersects_aabb(&AABB { min_x: item.pos.x as f64, min_y: item.pos.y as f64, @@ -103,13 +131,15 @@ impl<'a> Iterator for BlockCollisions<'a> { continue; } - return Some(block_shape.move_relative( + return Some(BLOCK_SHAPE.move_relative( item.pos.x as f64, item.pos.y as f64, item.pos.z as f64, )); } + let block_shape = block_state.shape(); + let block_shape = block_shape.move_relative(item.pos.x as f64, item.pos.y as f64, item.pos.z as f64); // if the entity shape and block shape don't collide, continue diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index b2d9b9df8..20e3a261c 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -19,7 +19,6 @@ azalea-inventory = { version = "0.9.0", path = "../azalea-inventory" } azalea-registry = { path = "../azalea-registry", version = "0.9.0" } bevy_ecs = "0.13.0" derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } -enum-as-inner = "0.6.0" tracing = "0.1.40" nohash-hasher = "0.2.0" once_cell = "1.19.0" @@ -28,6 +27,7 @@ thiserror = "1.0.57" uuid = "1.7.0" serde_json = "1.0.113" serde = "1.0.196" +rustc-hash = "1.1.0" [dev-dependencies] azalea-client = { path = "../azalea-client" } diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs index 0b68ead68..69b8f9084 100644 --- a/azalea-world/src/container.rs +++ b/azalea-world/src/container.rs @@ -3,6 +3,7 @@ use bevy_ecs::{component::Component, system::Resource}; use derive_more::{Deref, DerefMut}; use nohash_hasher::IntMap; use parking_lot::RwLock; +use rustc_hash::FxHashMap; use std::{ collections::HashMap, sync::{Arc, Weak}, @@ -27,7 +28,7 @@ pub struct InstanceContainer { // telling them apart. We hope most servers are nice and don't do that though. It's only an // issue when there's multiple clients with the same WorldContainer in different worlds // anyways. - pub instances: HashMap>>, + pub instances: FxHashMap>>, } impl InstanceContainer { diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index 8a11fc463..d1d9a4f87 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -57,3 +57,7 @@ log = ["azalea-client/log"] [[bench]] name = "pathfinder" harness = false + +[[bench]] +name = "physics" +harness = false diff --git a/azalea/benches/physics.rs b/azalea/benches/physics.rs new file mode 100644 index 000000000..6f8dc7bc2 --- /dev/null +++ b/azalea/benches/physics.rs @@ -0,0 +1,144 @@ +use std::{hint::black_box, sync::Arc, time::Duration}; + +use azalea::{ + pathfinder::{ + astar::{self, a_star}, + goals::{BlockPosGoal, Goal}, + mining::MiningCache, + simulation::{SimulatedPlayerBundle, Simulation, SimulationSet}, + world::CachedWorld, + }, + BlockPos, Vec3, +}; +use azalea_core::position::{ChunkBlockPos, ChunkPos}; +use azalea_inventory::Menu; +use azalea_world::{Chunk, ChunkStorage, PartialChunkStorage}; +use criterion::{criterion_group, criterion_main, Bencher, Criterion}; +use parking_lot::RwLock; + +#[allow(dead_code)] +fn generate_world(partial_chunks: &mut PartialChunkStorage, size: u32) -> ChunkStorage { + let size = size as i32; + + let mut chunks = ChunkStorage::default(); + for chunk_x in -size..size { + for chunk_z in -size..size { + let chunk_pos = ChunkPos::new(chunk_x, chunk_z); + partial_chunks.set(&chunk_pos, Some(Chunk::default()), &mut chunks); + } + } + + // for chunk_x in -size..size { + // for chunk_z in -size..size { + // let chunk_pos = ChunkPos::new(chunk_x, chunk_z); + // let chunk = chunks.get(&chunk_pos).unwrap(); + // let mut chunk = chunk.write(); + // for x in 0..16_u8 { + // for z in 0..16_u8 { + // chunk.set( + // &ChunkBlockPos::new(x, 1, z), + // azalea_registry::Block::Bedrock.into(), + // chunks.min_y, + // ); + // if rng.gen_bool(0.5) { + // chunk.set( + // &ChunkBlockPos::new(x, 2, z), + // azalea_registry::Block::Bedrock.into(), + // chunks.min_y, + // ); + // } + // } + // } + // } + // } + + // let mut start = BlockPos::new(-64, 4, -64); + // // move start down until it's on a solid block + // while chunks.get_block_state(&start).unwrap().is_air() { + // start = start.down(1); + // } + // start = start.up(1); + + // let mut end = BlockPos::new(63, 4, 63); + // // move end down until it's on a solid block + // while chunks.get_block_state(&end).unwrap().is_air() { + // end = end.down(1); + // } + // end = end.up(1); + + chunks +} + +fn generate_mining_world( + partial_chunks: &mut PartialChunkStorage, + size: u32, +) -> (ChunkStorage, BlockPos, BlockPos) { + let size = size as i32; + + let mut chunks = ChunkStorage::default(); + for chunk_x in -size..size { + for chunk_z in -size..size { + let chunk_pos = ChunkPos::new(chunk_x, chunk_z); + partial_chunks.set(&chunk_pos, Some(Chunk::default()), &mut chunks); + } + } + + // let mut rng = StdRng::seed_from_u64(0); + + for chunk_x in -size..size { + for chunk_z in -size..size { + let chunk_pos = ChunkPos::new(chunk_x, chunk_z); + let chunk = chunks.get(&chunk_pos).unwrap(); + let mut chunk = chunk.write(); + for y in chunks.min_y..(chunks.min_y + chunks.height as i32) { + for x in 0..16_u8 { + for z in 0..16_u8 { + chunk.set( + &ChunkBlockPos::new(x, y, z), + azalea_registry::Block::Stone.into(), + chunks.min_y, + ); + } + } + } + } + } + + let start = BlockPos::new(-64, 4, -64); + let end = BlockPos::new(0, 4, 0); + + (chunks, start, end) +} + +fn run_physics_benchmark(b: &mut Bencher<'_>) { + let mut partial_chunks = PartialChunkStorage::new(32); + + let world = generate_world(&mut partial_chunks, 4); + + let mut simulation_set = SimulationSet::new(world); + + // let entity = simulation_set.spawn(SimulatedPlayerBundle::new(Vec3::new(0.0, + // 4.0, 0.0))); for _ in 0..20 { + // simulation_set.tick(); + // println!("tick over"); + // } + // simulation_set.despawn(entity); + // std::process::exit(0); + + b.iter(|| { + let entity = simulation_set.spawn(SimulatedPlayerBundle::new(Vec3::new(0.0, 4.0, 0.0))); + for _ in 0..20 { + simulation_set.tick(); + } + simulation_set.despawn(entity); + }) +} + +fn bench_pathfinder(c: &mut Criterion) { + c.bench_function("physics", |b| { + run_physics_benchmark(b); + }); +} + +criterion_group!(benches, bench_pathfinder); +criterion_main!(benches); diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index bea99e934..a5e8113f7 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -113,8 +113,7 @@ fn create_simulation_player( )); entity.insert(player); - let entity_id = entity.id(); - entity_id + entity.id() } /// Simulate the Minecraft world to see if certain movements would be possible.