diff options
| author | mat <git@matdoes.dev> | 2023-10-02 17:51:38 -0500 |
|---|---|---|
| committer | mat <git@matdoes.dev> | 2023-10-02 17:51:38 -0500 |
| commit | d0505f7de30e4a9a330ef99d0082849ee44723e0 (patch) | |
| tree | f91879b0423eb0329efd2cb12a10a4e98b3b366d /azalea/src | |
| parent | c3d27487cae6af5d593193b922d1e81e93a1c45d (diff) | |
| download | azalea-drasl-d0505f7de30e4a9a330ef99d0082849ee44723e0.tar.xz | |
optimize pathfinder more
Diffstat (limited to 'azalea/src')
| -rw-r--r-- | azalea/src/pathfinder/astar.rs | 4 | ||||
| -rw-r--r-- | azalea/src/pathfinder/mod.rs | 31 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/basic.rs | 52 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/mod.rs | 175 | ||||
| -rw-r--r-- | azalea/src/pathfinder/moves/parkour.rs | 54 |
5 files changed, 178 insertions, 138 deletions
diff --git a/azalea/src/pathfinder/astar.rs b/azalea/src/pathfinder/astar.rs index 505b09c0..bc7b2309 100644 --- a/azalea/src/pathfinder/astar.rs +++ b/azalea/src/pathfinder/astar.rs @@ -26,14 +26,14 @@ const MIN_IMPROVEMENT: f32 = 0.01; pub fn a_star<P, M, HeuristicFn, SuccessorsFn, SuccessFn>( start: P, heuristic: HeuristicFn, - successors: SuccessorsFn, + mut successors: SuccessorsFn, success: SuccessFn, timeout: Duration, ) -> Path<P, M> where P: Eq + Hash + Copy + Debug, HeuristicFn: Fn(P) -> f32, - SuccessorsFn: Fn(P) -> Vec<Edge<P, M>>, + SuccessorsFn: FnMut(P) -> Vec<Edge<P, M>>, SuccessFn: Fn(P) -> bool, { let start_time = Instant::now(); diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 02b7d935..831a5524 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -19,6 +19,7 @@ use crate::ecs::{ query::{With, Without}, system::{Commands, Query, Res}, }; +use crate::pathfinder::moves::PathfinderCtx; use azalea_client::movement::walk_listener; use azalea_client::{StartSprintEvent, StartWalkEvent}; use azalea_core::position::BlockPos; @@ -178,7 +179,8 @@ fn goto_listener( debug!("start: {start:?}"); let world = &world_lock.read().chunks; - let successors = |pos: BlockPos| successors_fn(world, pos); + let ctx = PathfinderCtx::new(world); + let successors = |pos: BlockPos| successors_fn(&ctx, pos); let mut attempt_number = 0; @@ -192,15 +194,20 @@ fn goto_listener( |n| goal.heuristic(n), successors, |n| goal.success(n), - Duration::from_secs(if attempt_number == 0 { 1 } else { 5 }), + Duration::from_secs(if attempt_number == 0 { 10 } else { 10 }), ); let end_time = std::time::Instant::now(); debug!("partial: {partial:?}"); - debug!("time: {:?}", end_time - start_time); + let duration = end_time - start_time; + if partial { + info!("Pathfinder took {duration:?} (timed out)"); + } else { + info!("Pathfinder took {duration:?}"); + } - info!("Path:"); + debug!("Path:"); for movement in &movements { - info!(" {:?}", movement.target); + debug!(" {:?}", movement.target); } path = movements.into_iter().collect::<VecDeque<_>>(); @@ -275,11 +282,10 @@ fn path_found_listener( let world_lock = instance_container.get(instance_name).expect( "Entity tried to pathfind but the entity isn't in a valid world", ); + let world = &world_lock.read().chunks; + let ctx = PathfinderCtx::new(&world); let successors_fn: moves::SuccessorsFn = event.successors_fn; - let successors = |pos: BlockPos| { - let world = &world_lock.read().chunks; - successors_fn(world, pos) - }; + let successors = |pos: BlockPos| successors_fn(&ctx, pos); if successors(last_node.target) .iter() @@ -439,10 +445,9 @@ fn tick_execute_path( { // obstruction check (the path we're executing isn't possible anymore) - let successors = |pos: BlockPos| { - let world = &world_lock.read().chunks; - successors_fn(world, pos) - }; + let world = &world_lock.read().chunks; + let ctx = PathfinderCtx::new(&world); + let successors = |pos: BlockPos| successors_fn(&ctx, pos); if let Some(last_reached_node) = pathfinder.last_reached_node { if let Some(obstructed_index) = diff --git a/azalea/src/pathfinder/moves/basic.rs b/azalea/src/pathfinder/moves/basic.rs index 1785ec2e..b7bb1116 100644 --- a/azalea/src/pathfinder/moves/basic.rs +++ b/azalea/src/pathfinder/moves/basic.rs @@ -5,33 +5,29 @@ use azalea_core::{ direction::CardinalDirection, position::{BlockPos, Vec3}, }; -use azalea_world::ChunkStorage; use crate::{ pathfinder::{astar, costs::*}, JumpEvent, LookAtEvent, }; -use super::{ - default_is_reached, fall_distance, is_block_passable, is_passable, is_standable, Edge, - ExecuteCtx, IsReachedCtx, MoveData, -}; +use super::{default_is_reached, Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx}; -pub fn basic_move(world: &ChunkStorage, node: BlockPos) -> Vec<Edge> { +pub fn basic_move(ctx: &PathfinderCtx, node: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); - edges.extend(forward_move(world, node)); - edges.extend(ascend_move(world, node)); - edges.extend(descend_move(world, node)); - edges.extend(diagonal_move(world, node)); + edges.extend(forward_move(ctx, node)); + edges.extend(ascend_move(ctx, node)); + edges.extend(descend_move(ctx, node)); + edges.extend(diagonal_move(ctx, node)); edges } -fn forward_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn forward_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let offset = BlockPos::new(dir.x(), 0, dir.z()); - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } @@ -72,15 +68,15 @@ fn execute_forward_move( }); } -fn ascend_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn ascend_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let offset = BlockPos::new(dir.x(), 1, dir.z()); - if !is_block_passable(&pos.up(2), world) { + if !ctx.is_block_passable(&pos.up(2)) { continue; } - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } @@ -156,23 +152,23 @@ pub fn ascend_is_reached( BlockPos::from(position) == target || BlockPos::from(position) == target.down(1) } -fn descend_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn descend_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let dir_delta = BlockPos::new(dir.x(), 0, dir.z()); let new_horizontal_position = pos + dir_delta; - let fall_distance = fall_distance(&new_horizontal_position, world); + let fall_distance = ctx.fall_distance(&new_horizontal_position); if fall_distance == 0 || fall_distance > 3 { continue; } let new_position = new_horizontal_position.down(fall_distance as i32); // check whether 3 blocks vertically forward are passable - if !is_passable(&new_horizontal_position, world) { + if !ctx.is_passable(&new_horizontal_position) { continue; } // check whether we can stand on the target position - if !is_standable(&new_position, world) { + if !ctx.is_standable(&new_position) { continue; } @@ -258,22 +254,22 @@ pub fn descend_is_reached( && (position.y - target.y as f64) < 0.5 } -fn diagonal_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn diagonal_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let right = dir.right(); let offset = BlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z()); - if !is_passable( - &BlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z()), - world, - ) && !is_passable( - &BlockPos::new(pos.x + dir.right().x(), pos.y, pos.z + dir.right().z()), - world, - ) { + if !ctx.is_passable(&BlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z())) + && !ctx.is_passable(&BlockPos::new( + pos.x + dir.right().x(), + pos.y, + pos.z + dir.right().z(), + )) + { continue; } - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } // +0.001 so it doesn't unnecessarily go diagonal sometimes diff --git a/azalea/src/pathfinder/moves/mod.rs b/azalea/src/pathfinder/moves/mod.rs index 197229fd..caf4bbb4 100644 --- a/azalea/src/pathfinder/moves/mod.rs +++ b/azalea/src/pathfinder/moves/mod.rs @@ -1,21 +1,22 @@ pub mod basic; pub mod parkour; -use std::fmt::Debug; +use std::{cell::RefCell, fmt::Debug}; use crate::{JumpEvent, LookAtEvent}; use super::astar; +use azalea_block::BlockState; use azalea_client::{StartSprintEvent, StartWalkEvent}; -use azalea_core::position::{BlockPos, Vec3}; +use azalea_core::position::{BlockPos, ChunkBlockPos, ChunkPos, Vec3}; use azalea_physics::collision::{self, BlockWithShape}; use azalea_world::ChunkStorage; use bevy_ecs::{entity::Entity, event::EventWriter}; +use nohash_hasher::IntMap; type Edge = astar::Edge<BlockPos, MoveData>; -pub type SuccessorsFn = - fn(&azalea_world::ChunkStorage, BlockPos) -> Vec<astar::Edge<BlockPos, MoveData>>; +pub type SuccessorsFn = fn(&PathfinderCtx, BlockPos) -> Vec<astar::Edge<BlockPos, MoveData>>; #[derive(Clone)] pub struct MoveData { @@ -33,71 +34,110 @@ impl Debug for MoveData { } } -/// whether this block is passable -fn is_block_passable(pos: &BlockPos, world: &ChunkStorage) -> bool { - let Some(block) = world.get_block_state(pos) else { - return false; - }; - if block.is_air() { - // fast path - return true; - } - if block.shape() != &*collision::EMPTY_SHAPE { - return false; - } - if block == azalea_registry::Block::Water.into() { - return false; - } - if block.waterlogged() { - return false; +pub struct PathfinderCtx<'a> { + world: &'a ChunkStorage, + cached_chunks: RefCell<IntMap<ChunkPos, Vec<azalea_world::Section>>>, +} + +impl<'a> PathfinderCtx<'a> { + pub fn new(world: &'a ChunkStorage) -> Self { + Self { + world, + cached_chunks: Default::default(), + } } - // block.waterlogged currently doesn't account for seagrass and some other water - // blocks - if block == azalea_registry::Block::Seagrass.into() { - return false; + + fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> { + let chunk_pos = ChunkPos::from(pos); + + let mut cached_chunks = self.cached_chunks.borrow_mut(); + if let Some(sections) = cached_chunks.get(&chunk_pos) { + return azalea_world::chunk_storage::get_block_state_from_sections( + sections, + &ChunkBlockPos::from(pos), + self.world.min_y, + ); + } + + let chunk = self.world.get(&chunk_pos)?; + let chunk = chunk.read(); + + cached_chunks.insert(chunk_pos, chunk.sections.clone()); + + azalea_world::chunk_storage::get_block_state_from_sections( + &chunk.sections, + &ChunkBlockPos::from(pos), + self.world.min_y, + ) } - true -} + /// whether this block is passable + pub fn is_block_passable(&self, pos: &BlockPos) -> bool { + let Some(block) = self.get_block_state(pos) else { + return false; + }; + if block.is_air() { + // fast path + return true; + } + if block.shape() != &*collision::EMPTY_SHAPE { + return false; + } + if block == azalea_registry::Block::Water.into() { + return false; + } + if block.waterlogged() { + return false; + } + // block.waterlogged currently doesn't account for seagrass and some other water + // blocks + if block == azalea_registry::Block::Seagrass.into() { + return false; + } -/// whether this block has a solid hitbox (i.e. we can stand on it) -fn is_block_solid(pos: &BlockPos, world: &ChunkStorage) -> bool { - let Some(block) = world.get_block_state(pos) else { - return false; - }; - if block.is_air() { - // fast path - return false; + true } - block.shape() == &*collision::BLOCK_SHAPE -} -/// Whether this block and the block above are passable -fn is_passable(pos: &BlockPos, world: &ChunkStorage) -> bool { - is_block_passable(pos, world) && is_block_passable(&pos.up(1), world) -} + /// whether this block has a solid hitbox (i.e. we can stand on it) + pub fn is_block_solid(&self, pos: &BlockPos) -> bool { + let Some(block) = self.get_block_state(pos) else { + return false; + }; + if block.is_air() { + // fast path + return false; + } + block.shape() == &*collision::BLOCK_SHAPE + } -/// Whether we can stand in this position. Checks if the block below is solid, -/// and that the two blocks above that are passable. + /// Whether this block and the block above are passable + pub fn is_passable(&self, pos: &BlockPos) -> bool { + self.is_block_passable(pos) && self.is_block_passable(&pos.up(1)) + } -fn is_standable(pos: &BlockPos, world: &ChunkStorage) -> bool { - is_block_solid(&pos.down(1), world) && is_passable(pos, world) -} + /// Whether we can stand in this position. Checks if the block below is + /// solid, and that the two blocks above that are passable. -/// Get the amount of air blocks until the next solid block below this one. -fn fall_distance(pos: &BlockPos, world: &ChunkStorage) -> u32 { - let mut distance = 0; - let mut current_pos = pos.down(1); - while is_block_passable(¤t_pos, world) { - distance += 1; - current_pos = current_pos.down(1); + pub fn is_standable(&self, pos: &BlockPos) -> bool { + self.is_block_solid(&pos.down(1)) && self.is_passable(pos) + } + + /// Get the amount of air blocks until the next solid block below this one. + pub fn fall_distance(&self, pos: &BlockPos) -> u32 { + let mut distance = 0; + let mut current_pos = pos.down(1); + while self.is_block_passable(¤t_pos) { + distance += 1; + current_pos = current_pos.down(1); - if current_pos.y < world.min_y { - return u32::MAX; + if current_pos.y < self.world.min_y { + return u32::MAX; + } } + distance } - distance } + pub struct ExecuteCtx<'w1, 'w2, 'w3, 'w4, 'a> { pub entity: Entity, /// The node that we're trying to reach. @@ -121,10 +161,10 @@ pub struct IsReachedCtx<'a> { pub physics: &'a azalea_entity::Physics, } -pub fn default_move(world: &ChunkStorage, node: BlockPos) -> Vec<Edge> { +pub fn default_move(ctx: &PathfinderCtx, node: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); - edges.extend(basic::basic_move(world, node)); - edges.extend(parkour::parkour_move(world, node)); + edges.extend(basic::basic_move(ctx, node)); + edges.extend(parkour::parkour_move(ctx, node)); edges } @@ -163,8 +203,9 @@ mod tests { .chunks .set_block_state(&BlockPos::new(0, 1, 0), BlockState::AIR, &world); - assert!(!is_block_passable(&BlockPos::new(0, 0, 0), &world)); - assert!(is_block_passable(&BlockPos::new(0, 1, 0), &world)); + let ctx = PathfinderCtx::new(&world); + assert!(!ctx.is_block_passable(&BlockPos::new(0, 0, 0))); + assert!(ctx.is_block_passable(&BlockPos::new(0, 1, 0),)); } #[test] @@ -183,8 +224,9 @@ mod tests { .chunks .set_block_state(&BlockPos::new(0, 1, 0), BlockState::AIR, &world); - assert!(is_block_solid(&BlockPos::new(0, 0, 0), &world)); - assert!(!is_block_solid(&BlockPos::new(0, 1, 0), &world)); + let ctx = PathfinderCtx::new(&world); + assert!(ctx.is_block_solid(&BlockPos::new(0, 0, 0))); + assert!(!ctx.is_block_solid(&BlockPos::new(0, 1, 0))); } #[test] @@ -209,8 +251,9 @@ mod tests { .chunks .set_block_state(&BlockPos::new(0, 3, 0), BlockState::AIR, &world); - assert!(is_standable(&BlockPos::new(0, 1, 0), &world)); - assert!(!is_standable(&BlockPos::new(0, 0, 0), &world)); - assert!(!is_standable(&BlockPos::new(0, 2, 0), &world)); + let ctx = PathfinderCtx::new(&world); + assert!(ctx.is_standable(&BlockPos::new(0, 1, 0))); + assert!(!ctx.is_standable(&BlockPos::new(0, 0, 0))); + assert!(!ctx.is_standable(&BlockPos::new(0, 2, 0))); } } diff --git a/azalea/src/pathfinder/moves/parkour.rs b/azalea/src/pathfinder/moves/parkour.rs index 03635faa..53f5a348 100644 --- a/azalea/src/pathfinder/moves/parkour.rs +++ b/azalea/src/pathfinder/moves/parkour.rs @@ -1,46 +1,42 @@ use azalea_client::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection}; use azalea_core::{direction::CardinalDirection, position::BlockPos}; -use azalea_world::ChunkStorage; use crate::{ pathfinder::{astar, costs::*}, JumpEvent, LookAtEvent, }; -use super::{ - default_is_reached, is_block_passable, is_block_solid, is_passable, is_standable, Edge, - ExecuteCtx, IsReachedCtx, MoveData, -}; +use super::{default_is_reached, Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx}; -pub fn parkour_move(world: &ChunkStorage, node: BlockPos) -> Vec<Edge> { +pub fn parkour_move(ctx: &PathfinderCtx, node: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); - edges.extend(parkour_forward_1_move(world, node)); - edges.extend(parkour_headhitter_forward_1_move(world, node)); - edges.extend(parkour_forward_2_move(world, node)); + edges.extend(parkour_forward_1_move(ctx, node)); + edges.extend(parkour_headhitter_forward_1_move(ctx, node)); + edges.extend(parkour_forward_2_move(ctx, node)); edges } -fn parkour_forward_1_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn parkour_forward_1_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let gap_offset = BlockPos::new(dir.x(), 0, dir.z()); let offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2); - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } - if !is_passable(&(pos + gap_offset), world) { + if !ctx.is_passable(&(pos + gap_offset)) { continue; } - if !is_block_passable(&(pos + gap_offset).up(2), world) { + if !ctx.is_block_passable(&(pos + gap_offset).up(2)) { continue; } // make sure we actually have to jump - if is_block_solid(&(pos + gap_offset).down(1), world) { + if ctx.is_block_solid(&(pos + gap_offset).down(1)) { continue; } // make sure it's not a headhitter - if !is_block_passable(&pos.up(2), world) { + if !ctx.is_block_passable(&pos.up(2)) { continue; } @@ -61,34 +57,34 @@ fn parkour_forward_1_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { edges } -fn parkour_forward_2_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn parkour_forward_2_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let gap_1_offset = BlockPos::new(dir.x(), 0, dir.z()); let gap_2_offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2); let offset = BlockPos::new(dir.x() * 3, 0, dir.z() * 3); - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } - if !is_passable(&(pos + gap_1_offset), world) { + if !ctx.is_passable(&(pos + gap_1_offset)) { continue; } - if !is_block_passable(&(pos + gap_1_offset).up(2), world) { + if !ctx.is_block_passable(&(pos + gap_1_offset).up(2)) { continue; } - if !is_passable(&(pos + gap_2_offset), world) { + if !ctx.is_passable(&(pos + gap_2_offset)) { continue; } - if !is_block_passable(&(pos + gap_2_offset).up(2), world) { + if !ctx.is_block_passable(&(pos + gap_2_offset).up(2)) { continue; } // make sure we actually have to jump - if is_block_solid(&(pos + gap_1_offset).down(1), world) { + if ctx.is_block_solid(&(pos + gap_1_offset).down(1)) { continue; } // make sure it's not a headhitter - if !is_block_passable(&pos.up(2), world) { + if !ctx.is_block_passable(&pos.up(2)) { continue; } @@ -112,27 +108,27 @@ fn parkour_forward_2_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { edges } -fn parkour_headhitter_forward_1_move(world: &ChunkStorage, pos: BlockPos) -> Vec<Edge> { +fn parkour_headhitter_forward_1_move(ctx: &PathfinderCtx, pos: BlockPos) -> Vec<Edge> { let mut edges = Vec::new(); for dir in CardinalDirection::iter() { let gap_offset = BlockPos::new(dir.x(), 0, dir.z()); let offset = BlockPos::new(dir.x() * 2, 0, dir.z() * 2); - if !is_standable(&(pos + offset), world) { + if !ctx.is_standable(&(pos + offset)) { continue; } - if !is_passable(&(pos + gap_offset), world) { + if !ctx.is_passable(&(pos + gap_offset)) { continue; } - if !is_block_passable(&(pos + gap_offset).up(2), world) { + if !ctx.is_block_passable(&(pos + gap_offset).up(2)) { continue; } // make sure we actually have to jump - if is_block_solid(&(pos + gap_offset).down(1), world) { + if ctx.is_block_solid(&(pos + gap_offset).down(1)) { continue; } // make sure it is a headhitter - if !is_block_solid(&pos.up(2), world) { + if !ctx.is_block_solid(&pos.up(2)) { continue; } |
