mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
optimize physics
This commit is contained in:
parent
13426b035e
commit
018ab55bdb
10 changed files with 201 additions and 23 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -457,6 +457,7 @@ dependencies = [
|
||||||
"bevy_app",
|
"bevy_app",
|
||||||
"bevy_ecs",
|
"bevy_ecs",
|
||||||
"bevy_time",
|
"bevy_time",
|
||||||
|
"nohash-hasher",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -543,10 +544,10 @@ dependencies = [
|
||||||
"bevy_ecs",
|
"bevy_ecs",
|
||||||
"criterion",
|
"criterion",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"enum-as-inner",
|
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"simdnbt",
|
"simdnbt",
|
||||||
|
|
0
Cargo.toml
Executable file → Normal file
0
Cargo.toml
Executable file → Normal file
|
@ -397,9 +397,7 @@ impl simdnbt::FromNbtTag for FormattedText {
|
||||||
trace!("keybind text components aren't yet supported");
|
trace!("keybind text components aren't yet supported");
|
||||||
return None;
|
return None;
|
||||||
} else {
|
} else {
|
||||||
let Some(_nbt) = compound.get("nbt") else {
|
let _nbt = compound.get("nbt")?;
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let _separator = FormattedText::parse_separator_nbt(compound)?;
|
let _separator = FormattedText::parse_separator_nbt(compound)?;
|
||||||
|
|
||||||
let _interpret = match compound.get("interpret") {
|
let _interpret = match compound.get("interpret") {
|
||||||
|
|
|
@ -20,6 +20,7 @@ bevy_ecs = "0.13.0"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
parking_lot = "^0.12.1"
|
parking_lot = "^0.12.1"
|
||||||
|
nohash-hasher = "0.2.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bevy_time = "0.13.0"
|
bevy_time = "0.13.0"
|
||||||
|
|
|
@ -4,11 +4,11 @@ use azalea_block::BlockState;
|
||||||
use azalea_core::{
|
use azalea_core::{
|
||||||
cursor3d::{Cursor3d, CursorIterationType},
|
cursor3d::{Cursor3d, CursorIterationType},
|
||||||
math::EPSILON,
|
math::EPSILON,
|
||||||
position::{ChunkPos, ChunkSectionPos},
|
position::{BlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos},
|
||||||
};
|
};
|
||||||
use azalea_world::{Chunk, Instance};
|
use azalea_world::{Chunk, Instance};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::{ops::Deref, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn get_block_collisions(world: &Instance, aabb: AABB) -> BlockCollisions<'_> {
|
pub fn get_block_collisions(world: &Instance, aabb: AABB) -> BlockCollisions<'_> {
|
||||||
BlockCollisions::new(world, aabb)
|
BlockCollisions::new(world, aabb)
|
||||||
|
@ -20,6 +20,8 @@ pub struct BlockCollisions<'a> {
|
||||||
pub entity_shape: VoxelShape,
|
pub entity_shape: VoxelShape,
|
||||||
pub cursor: Cursor3d,
|
pub cursor: Cursor3d,
|
||||||
pub only_suffocating_blocks: bool,
|
pub only_suffocating_blocks: bool,
|
||||||
|
|
||||||
|
cached_sections: Vec<(ChunkSectionPos, azalea_world::Section)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BlockCollisions<'a> {
|
impl<'a> BlockCollisions<'a> {
|
||||||
|
@ -40,6 +42,8 @@ impl<'a> BlockCollisions<'a> {
|
||||||
entity_shape: VoxelShape::from(aabb),
|
entity_shape: VoxelShape::from(aabb),
|
||||||
cursor,
|
cursor,
|
||||||
only_suffocating_blocks: false,
|
only_suffocating_blocks: false,
|
||||||
|
|
||||||
|
cached_sections: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +67,36 @@ impl<'a> BlockCollisions<'a> {
|
||||||
|
|
||||||
self.world.chunks.get(&chunk_pos)
|
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> {
|
impl<'a> Iterator for BlockCollisions<'a> {
|
||||||
|
@ -74,24 +108,18 @@ impl<'a> Iterator for BlockCollisions<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let chunk = self.get_chunk(item.pos.x, item.pos.z);
|
let block_state = self.get_block_state(item.pos);
|
||||||
let Some(chunk) = chunk else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let pos = item.pos;
|
if block_state.is_air() {
|
||||||
let block_state: BlockState = chunk
|
// fast path since we can't collide with air
|
||||||
.read()
|
continue;
|
||||||
.get(&(&pos).into(), self.world.chunks.min_y)
|
}
|
||||||
.unwrap_or(BlockState::AIR);
|
|
||||||
|
|
||||||
// TODO: continue if self.only_suffocating_blocks and the block is not
|
// TODO: continue if self.only_suffocating_blocks and the block is not
|
||||||
// suffocating
|
// suffocating
|
||||||
|
|
||||||
let block_shape = block_state.shape();
|
|
||||||
|
|
||||||
// if it's a full block do a faster collision check
|
// 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 {
|
if !self.aabb.intersects_aabb(&AABB {
|
||||||
min_x: item.pos.x as f64,
|
min_x: item.pos.x as f64,
|
||||||
min_y: item.pos.y as f64,
|
min_y: item.pos.y as f64,
|
||||||
|
@ -103,13 +131,15 @@ impl<'a> Iterator for BlockCollisions<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Some(block_shape.move_relative(
|
return Some(BLOCK_SHAPE.move_relative(
|
||||||
item.pos.x as f64,
|
item.pos.x as f64,
|
||||||
item.pos.y as f64,
|
item.pos.y as f64,
|
||||||
item.pos.z as f64,
|
item.pos.z as f64,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let block_shape = block_state.shape();
|
||||||
|
|
||||||
let block_shape =
|
let block_shape =
|
||||||
block_shape.move_relative(item.pos.x as f64, item.pos.y as f64, item.pos.z as f64);
|
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
|
// if the entity shape and block shape don't collide, continue
|
||||||
|
|
|
@ -19,7 +19,6 @@ azalea-inventory = { version = "0.9.0", path = "../azalea-inventory" }
|
||||||
azalea-registry = { path = "../azalea-registry", version = "0.9.0" }
|
azalea-registry = { path = "../azalea-registry", version = "0.9.0" }
|
||||||
bevy_ecs = "0.13.0"
|
bevy_ecs = "0.13.0"
|
||||||
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
|
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
|
||||||
enum-as-inner = "0.6.0"
|
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
nohash-hasher = "0.2.0"
|
nohash-hasher = "0.2.0"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
|
@ -28,6 +27,7 @@ thiserror = "1.0.57"
|
||||||
uuid = "1.7.0"
|
uuid = "1.7.0"
|
||||||
serde_json = "1.0.113"
|
serde_json = "1.0.113"
|
||||||
serde = "1.0.196"
|
serde = "1.0.196"
|
||||||
|
rustc-hash = "1.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
azalea-client = { path = "../azalea-client" }
|
azalea-client = { path = "../azalea-client" }
|
||||||
|
|
|
@ -3,6 +3,7 @@ use bevy_ecs::{component::Component, system::Resource};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use nohash_hasher::IntMap;
|
use nohash_hasher::IntMap;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{Arc, Weak},
|
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
|
// 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
|
// issue when there's multiple clients with the same WorldContainer in different worlds
|
||||||
// anyways.
|
// anyways.
|
||||||
pub instances: HashMap<ResourceLocation, Weak<RwLock<Instance>>>,
|
pub instances: FxHashMap<ResourceLocation, Weak<RwLock<Instance>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstanceContainer {
|
impl InstanceContainer {
|
||||||
|
|
|
@ -57,3 +57,7 @@ log = ["azalea-client/log"]
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "pathfinder"
|
name = "pathfinder"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "physics"
|
||||||
|
harness = false
|
||||||
|
|
144
azalea/benches/physics.rs
Normal file
144
azalea/benches/physics.rs
Normal file
|
@ -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);
|
|
@ -113,8 +113,7 @@ fn create_simulation_player(
|
||||||
));
|
));
|
||||||
entity.insert(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.
|
/// Simulate the Minecraft world to see if certain movements would be possible.
|
||||||
|
|
Loading…
Add table
Reference in a new issue