Skip to content

Commit

Permalink
better pathfinder timeouts
Browse files Browse the repository at this point in the history
  • Loading branch information
mat-1 committed Dec 26, 2024
1 parent adb56b7 commit 344834c
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 73 deletions.
30 changes: 24 additions & 6 deletions azalea/src/pathfinder/astar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ pub fn a_star<P, M, HeuristicFn, SuccessorsFn, SuccessFn>(
heuristic: HeuristicFn,
mut successors: SuccessorsFn,
success: SuccessFn,
timeout: PathfinderTimeout,
min_timeout: PathfinderTimeout,
max_timeout: PathfinderTimeout,
) -> Path<P, M>
where
P: Eq + Hash + Copy + Debug,
Expand Down Expand Up @@ -150,14 +151,31 @@ where

// check for timeout every ~10ms
if num_nodes % 10000 == 0 {
let timed_out = match timeout {
let min_timeout_reached = match min_timeout {
PathfinderTimeout::Time(max_duration) => start_time.elapsed() >= max_duration,
PathfinderTimeout::Nodes(max_nodes) => num_nodes >= max_nodes,
};
if timed_out {
// timeout, just return the best path we have so far
trace!("A* couldn't find a path in time, returning best path");
break;

if min_timeout_reached {
// means we have a non-empty path
if best_paths[6] != 0 {
break;
}

if min_timeout_reached {
let max_timeout_reached = match max_timeout {
PathfinderTimeout::Time(max_duration) => {
start_time.elapsed() >= max_duration
}
PathfinderTimeout::Nodes(max_nodes) => num_nodes >= max_nodes,
};

if max_timeout_reached {
// timeout, we're gonna be returning an empty path :(
trace!("A* couldn't find a path in time, returning best path");
break;
}
}
}
}
}
Expand Down
115 changes: 48 additions & 67 deletions azalea/src/pathfinder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ pub struct Pathfinder {
pub is_calculating: bool,
pub allow_mining: bool,

pub default_timeout: Option<PathfinderTimeout>,
pub min_timeout: Option<PathfinderTimeout>,
pub max_timeout: Option<PathfinderTimeout>,

pub goto_id: Arc<AtomicUsize>,
Expand Down Expand Up @@ -142,7 +142,7 @@ pub struct GotoEvent {
pub allow_mining: bool,

/// Also see [`PathfinderTimeout::Nodes`]
pub default_timeout: PathfinderTimeout,
pub min_timeout: PathfinderTimeout,
pub max_timeout: PathfinderTimeout,
}
#[derive(Event, Clone, Debug)]
Expand Down Expand Up @@ -187,7 +187,7 @@ impl PathfinderClientExt for azalea_client::Client {
goal: Arc::new(goal),
successors_fn: moves::default_move,
allow_mining: true,
default_timeout: PathfinderTimeout::Time(Duration::from_secs(1)),
min_timeout: PathfinderTimeout::Time(Duration::from_secs(1)),
max_timeout: PathfinderTimeout::Time(Duration::from_secs(5)),
});
}
Expand All @@ -200,7 +200,7 @@ impl PathfinderClientExt for azalea_client::Client {
goal: Arc::new(goal),
successors_fn: moves::default_move,
allow_mining: false,
default_timeout: PathfinderTimeout::Time(Duration::from_secs(1)),
min_timeout: PathfinderTimeout::Time(Duration::from_secs(1)),
max_timeout: PathfinderTimeout::Time(Duration::from_secs(5)),
});
}
Expand Down Expand Up @@ -250,7 +250,7 @@ pub fn goto_listener(
pathfinder.successors_fn = Some(event.successors_fn);
pathfinder.is_calculating = true;
pathfinder.allow_mining = event.allow_mining;
pathfinder.default_timeout = Some(event.default_timeout);
pathfinder.min_timeout = Some(event.min_timeout);
pathfinder.max_timeout = Some(event.max_timeout);

let start = if let Some(executing_path) = executing_path
Expand Down Expand Up @@ -284,7 +284,7 @@ pub fn goto_listener(
None
});

let default_timeout = event.default_timeout;
let min_timeout = event.min_timeout;
let max_timeout = event.max_timeout;

let task = thread_pool.spawn(async move {
Expand All @@ -297,7 +297,7 @@ pub fn goto_listener(
goto_id_atomic,
allow_mining,
mining_cache,
default_timeout,
min_timeout,
max_timeout,
})
});
Expand All @@ -316,7 +316,7 @@ pub struct CalculatePathOpts {
pub allow_mining: bool,
pub mining_cache: MiningCache,
/// Also see [`GotoEvent::deterministic_timeout`]
pub default_timeout: PathfinderTimeout,
pub min_timeout: PathfinderTimeout,
pub max_timeout: PathfinderTimeout,
}

Expand Down Expand Up @@ -344,63 +344,48 @@ pub fn calculate_path(opts: CalculatePathOpts) -> Option<PathFoundEvent> {
let mut path;

Check warning on line 344 in azalea/src/pathfinder/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

variable does not need to be mutable

warning: variable does not need to be mutable --> azalea/src/pathfinder/mod.rs:344:9 | 344 | let mut path; | ----^^^^ | | | help: remove this `mut`
let mut is_partial: bool;

Check warning on line 345 in azalea/src/pathfinder/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

variable does not need to be mutable

warning: variable does not need to be mutable --> azalea/src/pathfinder/mod.rs:345:9 | 345 | let mut is_partial: bool; | ----^^^^^^^^^^ | | | help: remove this `mut`

'calculate: loop {
let start_time = Instant::now();

let timeout = if attempt_number == 0 {
opts.default_timeout
} else {
opts.max_timeout
};

let astar::Path { movements, partial } = a_star(
RelBlockPos::get_origin(origin),
|n| opts.goal.heuristic(n.apply(origin)),
successors,
|n| opts.goal.success(n.apply(origin)),
timeout,
);
let end_time = Instant::now();
debug!("partial: {partial:?}");
let duration = end_time - start_time;
if partial {
if movements.is_empty() {
info!("Pathfinder took {duration:?} (empty path)");
} else {
info!("Pathfinder took {duration:?} (incomplete path)");
}
// wait a bit so it's not a busy loop
thread::sleep(Duration::from_millis(100));
let start_time = Instant::now();

let astar::Path { movements, partial } = a_star(
RelBlockPos::get_origin(origin),
|n| opts.goal.heuristic(n.apply(origin)),
successors,
|n| opts.goal.success(n.apply(origin)),
opts.min_timeout,
opts.max_timeout,
);
let end_time = Instant::now();
debug!("partial: {partial:?}");
let duration = end_time - start_time;
if partial {
if movements.is_empty() {
info!("Pathfinder took {duration:?} (empty path)");
} else {
info!("Pathfinder took {duration:?}");
info!("Pathfinder took {duration:?} (incomplete path)");
}
// wait a bit so it's not a busy loop
thread::sleep(Duration::from_millis(100));
} else {
info!("Pathfinder took {duration:?}");
}

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

path = movements.into_iter().collect::<VecDeque<_>>();
is_partial = partial;
path = movements.into_iter().collect::<VecDeque<_>>();
is_partial = partial;

let goto_id_now = opts.goto_id_atomic.load(atomic::Ordering::SeqCst);
if goto_id != goto_id_now {
// we must've done another goto while calculating this path, so throw it away
warn!("finished calculating a path, but it's outdated");
return None;
}
let goto_id_now = opts.goto_id_atomic.load(atomic::Ordering::SeqCst);
if goto_id != goto_id_now {
// we must've done another goto while calculating this path, so throw it away
warn!("finished calculating a path, but it's outdated");
return None;
}

if path.is_empty() && partial {
if attempt_number == 0 && opts.default_timeout != opts.max_timeout {
debug!("this path is empty, retrying with a higher timeout");
attempt_number += 1;
continue 'calculate;
} else {
debug!("this path is empty, giving up");
break 'calculate;
}
}
break;
if path.is_empty() && partial {
debug!("this path is empty, we might be stuck :(");
}

// replace the RelBlockPos types with BlockPos
Expand Down Expand Up @@ -758,7 +743,7 @@ pub fn check_for_path_obstruction(
goto_id_atomic,
allow_mining,
mining_cache,
default_timeout: PathfinderTimeout::Nodes(10_000),
min_timeout: PathfinderTimeout::Nodes(10_000),
max_timeout: PathfinderTimeout::Nodes(10_000),
});
debug!("obstruction patch: {path_found_event:?}");
Expand Down Expand Up @@ -825,9 +810,7 @@ pub fn recalculate_near_end_of_path(
goal,
successors_fn,
allow_mining: pathfinder.allow_mining,
default_timeout: pathfinder
.default_timeout
.expect("default_timeout should be set"),
min_timeout: pathfinder.min_timeout.expect("min_timeout should be set"),
max_timeout: pathfinder.max_timeout.expect("max_timeout should be set"),
});
pathfinder.is_calculating = true;
Expand Down Expand Up @@ -925,9 +908,7 @@ pub fn recalculate_if_has_goal_but_no_path(
goal,
successors_fn: pathfinder.successors_fn.unwrap(),
allow_mining: pathfinder.allow_mining,
default_timeout: pathfinder
.default_timeout
.expect("default_timeout should be set"),
min_timeout: pathfinder.min_timeout.expect("min_timeout should be set"),
max_timeout: pathfinder.max_timeout.expect("max_timeout should be set"),
});
pathfinder.is_calculating = true;
Expand Down Expand Up @@ -1082,7 +1063,7 @@ mod tests {
goal: Arc::new(BlockPosGoal(end_pos)),
successors_fn: moves::default_move,
allow_mining: false,
default_timeout: PathfinderTimeout::Nodes(1_000_000),
min_timeout: PathfinderTimeout::Nodes(1_000_000),
max_timeout: PathfinderTimeout::Nodes(5_000_000),
});
simulation
Expand Down

0 comments on commit 344834c

Please sign in to comment.