Skip to content

Commit

Permalink
optimize pathfinder
Browse files Browse the repository at this point in the history
  • Loading branch information
mat-1 committed Dec 25, 2024
1 parent d67aa07 commit 0ee9ed5
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 93 deletions.
4 changes: 3 additions & 1 deletion azalea-client/src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ pub enum ChatPacket {

macro_rules! regex {
($re:literal $(,)?) => {{
std::sync::LazyLock::new(|| regex::Regex::new($re).unwrap())
static RE: std::sync::LazyLock<regex::Regex> =
std::sync::LazyLock::new(|| regex::Regex::new($re).unwrap());
&RE
}};
}

Expand Down
4 changes: 2 additions & 2 deletions azalea-core/src/direction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ pub enum AxisCycle {

impl CardinalDirection {
#[inline]
pub fn x(self) -> i32 {
pub fn x(self) -> i16 {
match self {
CardinalDirection::East => 1,
CardinalDirection::West => -1,
_ => 0,
}
}
#[inline]
pub fn z(self) -> i32 {
pub fn z(self) -> i16 {
match self {
CardinalDirection::South => 1,
CardinalDirection::North => -1,
Expand Down
2 changes: 1 addition & 1 deletion azalea/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ bevy_app = { workspace = true }
bevy_ecs = { workspace = true }
bevy_log = { workspace = true }
bevy_tasks = { workspace = true, features = ["multi_threaded"] }
#bevy_time = { workspace = true }
# bevy_time = { workspace = true }
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
futures = { workspace = true }
futures-lite = { workspace = true }
Expand Down
13 changes: 8 additions & 5 deletions azalea/benches/pathfinder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use azalea::{
astar::{self, a_star, PathfinderTimeout},
goals::{BlockPosGoal, Goal},
mining::MiningCache,
rel_block_pos::RelBlockPos,
world::CachedWorld,
},
BlockPos,
Expand Down Expand Up @@ -124,21 +125,23 @@ fn run_pathfinder_benchmark(

let (world, start, end) = generate_world(&mut partial_chunks, 4);

let origin = start;

b.iter(|| {
let cached_world = CachedWorld::new(Arc::new(RwLock::new(world.clone().into())));
let cached_world = CachedWorld::new(Arc::new(RwLock::new(world.clone().into())), origin);
let mining_cache =
MiningCache::new(Some(Menu::Player(azalea_inventory::Player::default())));
let goal = BlockPosGoal(end);

let successors = |pos: BlockPos| {
let successors = |pos: RelBlockPos| {
azalea::pathfinder::call_successors_fn(&cached_world, &mining_cache, successors_fn, pos)
};

let astar::Path { movements, partial } = a_star(
start,
|n| goal.heuristic(n),
RelBlockPos::get_origin(origin),
|n| goal.heuristic(n.apply(origin)),
successors,
|n| goal.success(n),
|n| goal.success(n.apply(origin)),
PathfinderTimeout::Time(Duration::MAX),
);

Expand Down
1 change: 1 addition & 0 deletions azalea/src/pathfinder/astar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ where
num_nodes += 1;
if success(current_node) {
debug!("Nodes considered: {num_nodes}");

return Path {
movements: reconstruct_path(nodes, current_node),
partial: false,
Expand Down
63 changes: 43 additions & 20 deletions azalea/src/pathfinder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod debug;
pub mod goals;
pub mod mining;
pub mod moves;
pub mod rel_block_pos;
pub mod simulation;
pub mod world;

Expand Down Expand Up @@ -35,6 +36,7 @@ use bevy_ecs::schedule::IntoSystemConfigs;
use bevy_tasks::{AsyncComputeTaskPool, Task};
use futures_lite::future;
use parking_lot::RwLock;
use rel_block_pos::RelBlockPos;
use tracing::{debug, error, info, trace, warn};

use self::debug::debug_render_path_with_particles;
Expand Down Expand Up @@ -321,8 +323,9 @@ pub async fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {

let goto_id = opts.goto_id_atomic.fetch_add(1, atomic::Ordering::SeqCst) + 1;

let cached_world = CachedWorld::new(opts.world_lock);
let successors = |pos: BlockPos| {
let origin = opts.start;
let cached_world = CachedWorld::new(opts.world_lock, origin);
let successors = |pos: RelBlockPos| {
call_successors_fn(&cached_world, &opts.mining_cache, opts.successors_fn, pos)
};

Expand All @@ -345,10 +348,10 @@ pub async fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
};

let astar::Path { movements, partial } = a_star(
opts.start,
|n| opts.goal.heuristic(n),
RelBlockPos::get_origin(origin),
|n| opts.goal.heuristic(n.apply(origin)),
successors,
|n| opts.goal.success(n),
|n| opts.goal.success(n.apply(origin)),
timeout,
);
let end_time = Instant::now();
Expand All @@ -368,7 +371,7 @@ pub async fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {

debug!("Path:");
for movement in &movements {
debug!(" {}", movement.target);
debug!(" {}", movement.target.apply(origin));
}

path = movements.into_iter().collect::<VecDeque<_>>();
Expand All @@ -394,10 +397,19 @@ pub async fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
break;
}

// replace the RelBlockPos types with BlockPos
let mapped_path = path
.into_iter()
.map(|movement| astar::Movement {
target: movement.target.apply(origin),
data: movement.data,
})
.collect();

Some(PathFoundEvent {
entity: opts.entity,
start: opts.start,
path: Some(path),
path: Some(mapped_path),
is_partial,
successors_fn: opts.successors_fn,
allow_mining: opts.allow_mining,
Expand Down Expand Up @@ -448,21 +460,27 @@ pub 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 origin = event.start;
let successors_fn: moves::SuccessorsFn = event.successors_fn;
let cached_world = CachedWorld::new(world_lock);
let cached_world = CachedWorld::new(world_lock, origin);
let mining_cache = MiningCache::new(if event.allow_mining {
Some(inventory.inventory_menu.clone())
} else {
None
});
let successors = |pos: BlockPos| {
let successors = |pos: RelBlockPos| {
call_successors_fn(&cached_world, &mining_cache, successors_fn, pos)
};

if let Some(first_node_of_new_path) = path.front() {
if successors(last_node_of_current_path.target)
let last_target_of_current_path =
RelBlockPos::from_origin(origin, last_node_of_current_path.target);
let first_target_of_new_path =
RelBlockPos::from_origin(origin, first_node_of_new_path.target);

if successors(last_target_of_current_path)
.iter()
.any(|edge| edge.movement.target == first_node_of_new_path.target)
.any(|edge| edge.movement.target == first_target_of_new_path)
{
debug!("combining old and new paths");
debug!(
Expand Down Expand Up @@ -655,17 +673,19 @@ pub fn check_for_path_obstruction(
.expect("Entity tried to pathfind but the entity isn't in a valid world");

// obstruction check (the path we're executing isn't possible anymore)
let cached_world = CachedWorld::new(world_lock);
let origin = executing_path.last_reached_node;
let cached_world = CachedWorld::new(world_lock, origin);
let mining_cache = MiningCache::new(if pathfinder.allow_mining {
Some(inventory.inventory_menu.clone())
} else {
None
});
let successors =
|pos: BlockPos| call_successors_fn(&cached_world, &mining_cache, successors_fn, pos);
|pos: RelBlockPos| call_successors_fn(&cached_world, &mining_cache, successors_fn, pos);

if let Some(obstructed_index) = check_path_obstructed(
executing_path.last_reached_node,
origin,
RelBlockPos::from_origin(origin, executing_path.last_reached_node),
&executing_path.path,
successors,
) {
Expand Down Expand Up @@ -873,18 +893,21 @@ pub fn stop_pathfinding_on_instance_change(
/// Checks whether the path has been obstructed, and returns Some(index) if it
/// has been. The index is of the first obstructed node.
pub fn check_path_obstructed<SuccessorsFn>(
mut current_position: BlockPos,
origin: BlockPos,
mut current_position: RelBlockPos,
path: &VecDeque<astar::Movement<BlockPos, moves::MoveData>>,
successors_fn: SuccessorsFn,
) -> Option<usize>
where
SuccessorsFn: Fn(BlockPos) -> Vec<astar::Edge<BlockPos, moves::MoveData>>,
SuccessorsFn: Fn(RelBlockPos) -> Vec<astar::Edge<RelBlockPos, moves::MoveData>>,
{
for (i, movement) in path.iter().enumerate() {
let movement_target = RelBlockPos::from_origin(origin, movement.target);

let mut found_obstruction = false;
for edge in successors_fn(current_position) {
if edge.movement.target == movement.target {
current_position = movement.target;
if edge.movement.target == movement_target {
current_position = movement_target;
found_obstruction = false;
break;
} else {
Expand All @@ -903,8 +926,8 @@ pub fn call_successors_fn(
cached_world: &CachedWorld,
mining_cache: &MiningCache,
successors_fn: SuccessorsFn,
pos: BlockPos,
) -> Vec<astar::Edge<BlockPos, moves::MoveData>> {
pos: RelBlockPos,
) -> Vec<astar::Edge<RelBlockPos, moves::MoveData>> {
let mut edges = Vec::with_capacity(16);
let mut ctx = PathfinderCtx {
edges: &mut edges,
Expand Down
32 changes: 16 additions & 16 deletions azalea/src/pathfinder/moves/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use azalea_core::{
};

use super::{default_is_reached, Edge, ExecuteCtx, IsReachedCtx, MoveData, PathfinderCtx};
use crate::pathfinder::{astar, costs::*};
use crate::pathfinder::{astar, costs::*, rel_block_pos::RelBlockPos};

pub fn basic_move(ctx: &mut PathfinderCtx, node: BlockPos) {
pub fn basic_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
forward_move(ctx, node);
ascend_move(ctx, node);
descend_move(ctx, node);
Expand All @@ -18,9 +18,9 @@ pub fn basic_move(ctx: &mut PathfinderCtx, node: BlockPos) {
downward_move(ctx, node);
}

fn forward_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
fn forward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
for dir in CardinalDirection::iter() {
let offset = BlockPos::new(dir.x(), 0, dir.z());
let offset = RelBlockPos::new(dir.x(), 0, dir.z());

let mut cost = SPRINT_ONE_BLOCK_COST;

Expand Down Expand Up @@ -57,9 +57,9 @@ fn execute_forward_move(mut ctx: ExecuteCtx) {
ctx.sprint(SprintDirection::Forward);
}

fn ascend_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
fn ascend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
for dir in CardinalDirection::iter() {
let offset = BlockPos::new(dir.x(), 1, dir.z());
let offset = RelBlockPos::new(dir.x(), 1, dir.z());

let break_cost_1 = ctx
.world
Expand Down Expand Up @@ -126,7 +126,7 @@ fn execute_ascend_move(mut ctx: ExecuteCtx) {
+ x_axis as f64 * (target_center.z - position.z).abs();

let lateral_motion = x_axis as f64 * physics.velocity.z + z_axis as f64 * physics.velocity.x;
if lateral_motion > 0.1 {
if lateral_motion.abs() > 0.1 {
return;
}

Expand All @@ -147,9 +147,9 @@ pub fn ascend_is_reached(
BlockPos::from(position) == target || BlockPos::from(position) == target.down(1)
}

fn descend_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
fn descend_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
for dir in CardinalDirection::iter() {
let dir_delta = BlockPos::new(dir.x(), 0, dir.z());
let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
let new_horizontal_position = pos + dir_delta;

let break_cost_1 = ctx
Expand Down Expand Up @@ -271,9 +271,9 @@ pub fn descend_is_reached(
&& (position.y - target.y as f64) < 0.5
}

fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
for dir in CardinalDirection::iter() {
let dir_delta = BlockPos::new(dir.x(), 0, dir.z());
let dir_delta = RelBlockPos::new(dir.x(), 0, dir.z());
let gap_horizontal_position = pos + dir_delta;
let new_horizontal_position = pos + dir_delta * 2;

Expand Down Expand Up @@ -323,12 +323,12 @@ fn descend_forward_1_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
}
}

fn diagonal_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
fn diagonal_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
for dir in CardinalDirection::iter() {
let right = dir.right();
let offset = BlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z());
let left_pos = BlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z());
let right_pos = BlockPos::new(pos.x + right.x(), pos.y, pos.z + right.z());
let offset = RelBlockPos::new(dir.x() + right.x(), 0, dir.z() + right.z());
let left_pos = RelBlockPos::new(pos.x + dir.x(), pos.y, pos.z + dir.z());
let right_pos = RelBlockPos::new(pos.x + right.x(), pos.y, pos.z + right.z());

// +0.001 so it doesn't unnecessarily go diagonal sometimes
let mut cost = SPRINT_ONE_BLOCK_COST * SQRT_2 + 0.001;
Expand Down Expand Up @@ -369,7 +369,7 @@ fn execute_diagonal_move(mut ctx: ExecuteCtx) {
}

/// Go directly down, usually by mining.
fn downward_move(ctx: &mut PathfinderCtx, pos: BlockPos) {
fn downward_move(ctx: &mut PathfinderCtx, pos: RelBlockPos) {
// make sure we land on a solid block after breaking the one below us
if !ctx.world.is_block_solid(pos.down(2)) {
return;
Expand Down
7 changes: 4 additions & 3 deletions azalea/src/pathfinder/moves/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ use parking_lot::RwLock;
use super::{
astar,
mining::MiningCache,
rel_block_pos::RelBlockPos,
world::{is_block_state_passable, CachedWorld},
};
use crate::{auto_tool::best_tool_in_hotbar_for_block, JumpEvent, LookAtEvent};

type Edge = astar::Edge<BlockPos, MoveData>;
type Edge = astar::Edge<RelBlockPos, MoveData>;

pub type SuccessorsFn = fn(&mut PathfinderCtx, BlockPos);
pub type SuccessorsFn = fn(&mut PathfinderCtx, RelBlockPos);

pub fn default_move(ctx: &mut PathfinderCtx, node: BlockPos) {
pub fn default_move(ctx: &mut PathfinderCtx, node: RelBlockPos) {
basic::basic_move(ctx, node);
parkour::parkour_move(ctx, node);
}
Expand Down
Loading

0 comments on commit 0ee9ed5

Please sign in to comment.