diff --git a/benches/comparison_bench.rs b/benches/comparison_bench.rs index bdbeaac..26a51fa 100644 --- a/benches/comparison_bench.rs +++ b/benches/comparison_bench.rs @@ -1,24 +1,27 @@ use criterion::{criterion_group, criterion_main, Criterion}; use grid_pathfinding::{ pathing_grid::PathingGrid, - solver::{astar::AstarSolver, dijkstra::DijkstraSolver, jps::JPSSolver, GridSolver}, + solver::{ + alt::ALTSolver, astar::AstarSolver, dijkstra::DijkstraSolver, jps::JPSSolver, GridSolver, + }, Pathfinder, }; use grid_pathfinding_benchmark::*; -use grid_util::grid::ValueGrid; +use grid_util::{grid::ValueGrid, Point}; +use rand::{rngs::StdRng, Rng, SeedableRng}; use smallvec::{smallvec, SmallVec}; use std::hint::black_box; fn dao_bench(c: &mut Criterion) { let arr: SmallVec<[bool; 2]> = if ALLOW_DIAGONAL { - smallvec![false, true] + smallvec![false] } else { smallvec![false] }; let bench_set = if ALLOW_DIAGONAL { - ["dao/arena", "dao/den312d", "dao/arena2"] + ["dao/arena2"] } else { - ["dao/arena", "dao/den009d", "dao/den312d"] + ["dao/arena2"] }; for pruning in arr { for name in bench_set { @@ -43,54 +46,29 @@ fn dao_bench(c: &mut Criterion) { } } -fn dao_bench_jps(c: &mut Criterion) { - let arr: SmallVec<[bool; 2]> = if ALLOW_DIAGONAL { - smallvec![false, true] - } else { - smallvec![false] - }; +fn dao_bench_solver( + c: &mut Criterion, + solver_name: &str, + create_solver: FS, +) where + S: GridSolver, + FS: Fn(&mut PathingGrid) -> S, +{ let bench_set = if ALLOW_DIAGONAL { - ["dao/arena", "dao/den312d", "dao/arena2"] + ["dao/arena2"] } else { - ["dao/arena", "dao/den009d", "dao/den312d"] + ["dao/arena2"] }; - for pruning in arr { - for name in bench_set { - let (bool_grid, scenarios) = get_benchmark(name.to_owned()); - let mut pathing_grid: PathingGrid = - PathingGrid::new(bool_grid.width, bool_grid.height, true); - pathing_grid.grid = bool_grid.clone(); - pathing_grid.generate_components(); - let mut solver = JPSSolver::new(&pathing_grid, pruning); - solver.initialize(&pathing_grid); - let diag_str = if ALLOW_DIAGONAL { "8-grid" } else { "4-grid" }; - let improved_str = if pruning { " (improved pruning)" } else { "" }; - - c.bench_function( - format!("{name}, JPS {diag_str}{improved_str}").as_str(), - |b| { - b.iter(|| { - for (start, end, _) in &scenarios { - black_box(solver.get_path_single_goal(&mut pathing_grid, *start, *end)); - } - }) - }, - ); - } - } -} -fn dao_bench_astar(c: &mut Criterion) { - let bench_set = ["dao/arena", "dao/den009d", "dao/den312d"]; for name in bench_set { let (bool_grid, scenarios) = get_benchmark(name.to_owned()); let mut pathing_grid: PathingGrid = PathingGrid::new(bool_grid.width, bool_grid.height, true); pathing_grid.grid = bool_grid.clone(); pathing_grid.generate_components(); - let solver = AstarSolver::new(); + let solver = create_solver(&mut pathing_grid); let diag_str = if ALLOW_DIAGONAL { "8-grid" } else { "4-grid" }; - c.bench_function(format!("{name}, Astar {diag_str}").as_str(), |b| { + c.bench_function(format!("{name}, {solver_name} {diag_str}").as_str(), |b| { b.iter(|| { for (start, end, _) in &scenarios { black_box(solver.get_path_single_goal(&mut pathing_grid, *start, *end)); @@ -99,36 +77,82 @@ fn dao_bench_astar(c: &mut Criterion) { }); } } -fn dao_bench_dijkstra(c: &mut Criterion) { - let bench_set = ["dao/arena", "dao/den009d", "dao/den312d"]; - for name in bench_set { - let (bool_grid, scenarios) = get_benchmark(name.to_owned()); - let mut pathing_grid: PathingGrid = - PathingGrid::new(bool_grid.width, bool_grid.height, true); - pathing_grid.grid = bool_grid.clone(); - pathing_grid.generate_components(); - let solver = DijkstraSolver; - let diag_str = if ALLOW_DIAGONAL { "8-grid" } else { "4-grid" }; +pub fn generate_landmarks_mc( + pathing_grid: &PathingGrid, + number: usize, +) -> Vec { + let mut landmarks = Vec::new(); + let mut rng = StdRng::seed_from_u64(0); + while landmarks.len() < number { + let p = Point::new( + rng.random_range(0..pathing_grid.width() as i32), + rng.random_range(0..pathing_grid.height() as i32), + ); + if pathing_grid.can_move_to_simple(p) { + landmarks.push(p); + } + } + landmarks +} - c.bench_function(format!("{name}, Dijkstra {diag_str}").as_str(), |b| { - b.iter(|| { - for (start, end, _) in &scenarios { - black_box(solver.get_path_single_goal(&mut pathing_grid, *start, *end)); - } - }) - }); +fn dao_bench_jps(c: &mut Criterion) { + let arr: SmallVec<[bool; 2]> = if ALLOW_DIAGONAL { + smallvec![false] + } else { + smallvec![false] + }; + for pruning in arr { + let improved_str = if pruning { " (improved pruning)" } else { "" }; + dao_bench_solver( + c, + format!("JPS{improved_str}").as_str(), + |pathing_grid: &mut PathingGrid| { + let mut solver = JPSSolver::new(&pathing_grid, pruning); + solver.initialize(&pathing_grid); + solver + }, + ); } } +const N_LANDMARKS: usize = 64; +fn dao_bench_alt_mc(c: &mut Criterion) { + dao_bench_solver( + c, + "ALT (MC)", + |pathing_grid: &mut PathingGrid| { + let landmarks = generate_landmarks_mc(&pathing_grid, N_LANDMARKS); + ALTSolver::new_from_landmarks(landmarks, pathing_grid) + }, + ); +} +fn dao_bench_alt_greedy(c: &mut Criterion) { + dao_bench_solver( + c, + "ALT (greedy)", + |pathing_grid: &mut PathingGrid| { + let landmarks = generate_landmarks_mc(&pathing_grid, 1); + ALTSolver::new_greedy(landmarks[0], N_LANDMARKS, pathing_grid) + }, + ); +} +fn dao_bench_astar(c: &mut Criterion) { + dao_bench_solver(c, "Astar", |_: &mut PathingGrid| { + AstarSolver::new() + }); +} + +fn dao_bench_dijkstra(c: &mut Criterion) { + dao_bench_solver(c, "Dijkstra", |_: &mut PathingGrid| { + DijkstraSolver + }); +} + criterion_group!( benches, - // dao_bench, dao_bench, - // dao_bench_jps, dao_bench_jps, - // dao_bench_astar, - // dao_bench_astar, - // dao_bench_dijkstra, - // dao_bench_dijkstra + dao_bench_alt_greedy, + dao_bench_alt_mc, ); criterion_main!(benches); diff --git a/src/astar_jps.rs b/src/astar_jps.rs index 507ac06..93d83d0 100644 --- a/src/astar_jps.rs +++ b/src/astar_jps.rs @@ -97,8 +97,8 @@ where /// [AstarContext] represents the search fringe and node parent map, facilitating reuse of memory allocations. #[derive(Clone, Debug)] pub struct SearchContext { - fringe: F, - parents: FxIndexMap, + pub(crate) fringe: F, + pub(crate) parents: FxIndexMap, } pub type BinaryHeapSearchContext = SearchContext>>; @@ -120,15 +120,15 @@ where pub fn astar_jps( &mut self, start: &N, - mut successors: FN, - mut heuristic: FH, - mut success: FS, + successors: FN, + heuristic: FH, + success: FS, ) -> Option<(Vec, C)> where - FN: FnMut(&Option<&N>, &N) -> IN, + FN: Fn(&Option<&N>, &N) -> IN, IN: IntoIterator, - FH: FnMut(&N) -> C, - FS: FnMut(&N) -> bool, + FH: Fn(&N) -> C, + FS: Fn(&N) -> bool, { self.fringe.clear(); self.parents.clear(); @@ -199,10 +199,10 @@ pub fn astar_jps( where N: Eq + Hash + Clone, C: Zero + Ord + Copy + Hash, - FN: FnMut(&Option<&N>, &N) -> IN, + FN: Fn(&Option<&N>, &N) -> IN, IN: IntoIterator, - FH: FnMut(&N) -> C, - FS: FnMut(&N) -> bool, + FH: Fn(&N) -> C, + FS: Fn(&N) -> bool, { let mut search = DefaultSearchContext::new(); search.astar_jps(start, successors, heuristic, success) diff --git a/src/lib.rs b/src/lib.rs index 1246142..f150dc9 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ use std::sync::{Arc, Mutex}; use crate::astar_jps::DefaultSearchContext; -pub const ALLOW_CORNER_CUTTING: bool = false; +pub const ALLOW_CORNER_CUTTING: bool = true; const EQUAL_EDGE_COST: bool = false; const GRAPH_PRUNING: bool = true; const N_SMALLVEC_SIZE: usize = 8; @@ -516,14 +516,14 @@ impl Pathfinder { |parent, node| { if GRAPH_PRUNING { self.jps_neighbours(*parent, node, &|node_pos| { - self.heuristic(node_pos, &goal) <= if EQUAL_EDGE_COST { 1 } else { 99 } + self.heuristic(node_pos, &goal) <= D }) } else { self.neighborhood_points_and_cost(node) } }, |point| (self.heuristic(point, &goal) as f32 * self.heuristic_factor) as i32, - |point| self.heuristic(point, &goal) <= if EQUAL_EDGE_COST { 1 } else { 99 }, + |point| self.heuristic(point, &goal) <= D, ) .map(|(v, _c)| v) } diff --git a/src/solver/alt.rs b/src/solver/alt.rs new file mode 100644 index 0000000..0c8ea79 --- /dev/null +++ b/src/solver/alt.rs @@ -0,0 +1,54 @@ +use crate::solver::landmarks::LandmarkMap; +use crate::{pathing_grid::PathingGrid, solver::GridSolver, N_SMALLVEC_SIZE}; +use grid_util::Point; +use smallvec::SmallVec; + +#[derive(Clone, Debug)] +pub struct ALTSolver { + landmark_map: LandmarkMap, +} +impl ALTSolver { + pub fn new_from_landmarks( + landmarks: Vec, + grid: &mut PathingGrid, + ) -> ALTSolver { + ALTSolver { + landmark_map: LandmarkMap::new_from_landmarks(landmarks, grid), + } + } + pub fn new_from_landmark_map(landmark_map: LandmarkMap) -> ALTSolver { + ALTSolver { landmark_map } + } + pub fn new_greedy( + initial_landmark: Point, + landmark_count: usize, + grid: &mut PathingGrid, + ) -> ALTSolver { + ALTSolver { + landmark_map: LandmarkMap::new_minmax_greedy(initial_landmark, landmark_count, grid), + } + } +} + +impl GridSolver for ALTSolver { + type Successors = SmallVec<[(Point, i32); N_SMALLVEC_SIZE]>; + + fn successors( + &self, + grid: &PathingGrid, + _parent: Option<&Point>, + node: &Point, + _goal: &F, + ) -> Self::Successors + where + F: Fn(&Point) -> bool, + { + grid.neighborhood_points_and_cost(node) + } + + fn heuristic(&self, p1: &Point, p2: &Point) -> i32 { + self.landmark_map + .triangle_heuristic(p1, p2) + .max(self.cost::(p1, p2)) + } +} diff --git a/src/solver/astar.rs b/src/solver/astar.rs index b926c74..17a857d 100644 --- a/src/solver/astar.rs +++ b/src/solver/astar.rs @@ -33,13 +33,8 @@ impl GridSolver for AstarSolver { } /// Just the normal cost times a heuristic factor. - fn heuristic( - &self, - grid: &PathingGrid, - p1: &Point, - p2: &Point, - ) -> i32 { - (self.cost(grid, p1, p2) as f32 * self.heuristic_factor) as i32 + fn heuristic(&self, p1: &Point, p2: &Point) -> i32 { + (self.cost::(p1, p2) as f32 * self.heuristic_factor) as i32 } } diff --git a/src/solver/dijkstra.rs b/src/solver/dijkstra.rs index a45a3e3..f631742 100644 --- a/src/solver/dijkstra.rs +++ b/src/solver/dijkstra.rs @@ -23,12 +23,7 @@ impl GridSolver for DijkstraSolver { } /// Just the cost times a heuristic factor. - fn heuristic( - &self, - _: &PathingGrid, - _: &Point, - _: &Point, - ) -> i32 { + fn heuristic(&self, _: &Point, _: &Point) -> i32 { 0 } } diff --git a/src/solver/jps.rs b/src/solver/jps.rs index 3fb7584..569407e 100644 --- a/src/solver/jps.rs +++ b/src/solver/jps.rs @@ -61,13 +61,8 @@ impl GridSolver for JPSSolver { } /// Uses C as cost for cardinal (straight) moves and D for diagonal moves. - fn heuristic( - &self, - grid: &PathingGrid, - p1: &Point, - p2: &Point, - ) -> i32 { - self.cost(grid, p1, p2) + fn heuristic(&self, p1: &Point, p2: &Point) -> i32 { + self.cost::(p1, p2) } } impl JPSSolver { diff --git a/src/solver/landmarks.rs b/src/solver/landmarks.rs new file mode 100644 index 0000000..b5a20dd --- /dev/null +++ b/src/solver/landmarks.rs @@ -0,0 +1,105 @@ +use core::panic; + +use crate::solver::dijkstra::DijkstraSolver; +use crate::{pathing_grid::PathingGrid, solver::GridSolver}; +use grid_util::{Grid, Point, SimpleGrid, ValueGrid}; +use log::warn; +use smallvec::SmallVec; +use std::iter::zip; + +#[derive(Clone, Debug)] +pub struct LandmarkMap { + landmarks: Vec, + landmark_distances: SimpleGrid>, +} +impl LandmarkMap { + pub fn len(&self) -> usize { + self.landmarks.len() + } + #[inline(always)] + pub fn triangle_heuristic(&self, p1: &Point, p2: &Point) -> i32 { + let g1 = self.landmark_distances.get_point(*p1).unwrap(); + let g2 = self.landmark_distances.get_point(*p2).unwrap(); + zip(g1, g2).map(|(x, y)| (x - y).abs()).max().unwrap() + } + + pub fn new_from_landmarks( + landmarks: Vec, + grid: &mut PathingGrid, + ) -> LandmarkMap { + let d = DijkstraSolver; + let mut landmark_distances = SimpleGrid::new(grid.width(), grid.height(), SmallVec::new()); + for landmark in &landmarks { + let mut ct = grid.context.lock().unwrap(); + ct.astar_jps( + &landmark, + |parent, node| d.successors(grid, *parent, node, &|_| false), + |_| 0, + |_| false, + ); + for (p, (_, c)) in &ct.parents { + landmark_distances.get_point_mut(*p).unwrap().push(*c); + } + } + LandmarkMap { + landmarks, + landmark_distances, + } + } + pub fn new_minmax_greedy( + initial_landmark: Point, + landmark_count: usize, + grid: &mut PathingGrid, + ) -> LandmarkMap { + let d = DijkstraSolver; + let mut landmarks = vec![]; + let mut landmark_distances = SimpleGrid::new(grid.width(), grid.height(), SmallVec::new()); + + // Look to maximize the minimal distance to any existing landmark for a greedy choice + while landmarks.len() < landmark_count { + if landmarks.len() > 0 { + let mut max_point = None; + let mut max_cost = 0; + for x in 0..grid.width() as i32 { + for y in 0..grid.height() as i32 { + let min_c = *landmark_distances + .get(x, y) + .unwrap() + .iter() + .min() + .unwrap_or(&0); + if min_c > max_cost { + max_point = Some(Point::new(x, y)); + max_cost = min_c; + } + } + } + match max_point { + Some(p) => landmarks.push(p), + None => { + let l = landmarks.len(); + warn!("No new landmark maximally far away from existing ones was found, returning only {l}/{landmark_count} landmarks"); + break; + } + } + } else { + landmarks.push(initial_landmark); + } + let landmark = landmarks.last().unwrap(); + let mut ct = grid.context.lock().unwrap(); + ct.astar_jps( + &landmark, + |parent, node| d.successors(grid, *parent, node, &|_| false), + |_| 0, + |_| false, + ); + for (p, (_, c)) in &ct.parents { + landmark_distances.get_point_mut(*p).unwrap().push(*c); + } + } + LandmarkMap { + landmarks, + landmark_distances, + } + } +} diff --git a/src/solver/mod.rs b/src/solver/mod.rs index fc132cb..9b94804 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -1,9 +1,11 @@ -use crate::{pathing_grid::PathingGrid, waypoints_to_path, C, D, E, EQUAL_EDGE_COST}; +use crate::{pathing_grid::PathingGrid, waypoints_to_path, C, D, E}; use grid_util::Point; +pub mod alt; pub mod astar; pub mod dijkstra; pub mod jps; +pub mod landmarks; /// Converts the integer cost to an approximate floating point equivalent where cardinal directions have cost 1.0. pub fn convert_cost_to_unit_cost_float(cost: i32) -> f64 { @@ -13,20 +15,10 @@ pub fn convert_cost_to_unit_cost_float(cost: i32) -> f64 { pub trait GridSolver { type Successors: IntoIterator; - fn heuristic( - &self, - grid: &PathingGrid, - p1: &Point, - p2: &Point, - ) -> i32; + fn heuristic(&self, p1: &Point, p2: &Point) -> i32; /// Uses C as cost for cardinal (straight) moves and D for diagonal moves. - fn cost( - &self, - grid: &PathingGrid, - p1: &Point, - p2: &Point, - ) -> i32 { + fn cost(&self, p1: &Point, p2: &Point) -> i32 { if ALLOW_DIAGONAL { let delta_x = (p1.x - p2.x).abs(); let delta_y = (p1.y - p2.y).abs(); @@ -49,28 +41,20 @@ pub trait GridSolver { where F: Fn(&Point) -> bool; - fn get_path_cost( - &self, - path: &Vec, - pathing_grid: &PathingGrid, - ) -> i32 { + fn get_path_cost(&self, path: &Vec) -> i32 { let mut v = path[0]; let n = path.len(); let mut total_cost_int = 0; for i in 1..n { let v_old = v; v = path[i]; - let cost = self.cost(&pathing_grid, &v_old, &v); + let cost = self.cost::(&v_old, &v); total_cost_int += cost; } total_cost_int } - fn get_path_cost_float( - &self, - path: &Vec, - pathing_grid: &PathingGrid, - ) -> f64 { - convert_cost_to_unit_cost_float(self.get_path_cost(path, pathing_grid)) + fn get_path_cost_float(&self, path: &Vec) -> f64 { + convert_cost_to_unit_cost_float(self.get_path_cost::(path)) } fn get_path_single_goal( &self, @@ -106,7 +90,7 @@ pub trait GridSolver { ct.astar_jps( &start, |parent, node| self.successors(grid, *parent, node, &|node_pos| *node_pos == goal), - |point| self.heuristic(grid, point, &goal), + |point| self.heuristic::(point, &goal), |point| *point == goal, ) .map(|(v, _c)| v) @@ -130,11 +114,11 @@ pub trait GridSolver { &start, |parent, node| { self.successors(grid, *parent, node, &|node_pos| { - self.heuristic(grid, node_pos, &goal) <= if EQUAL_EDGE_COST { 1 } else { 99 } + self.heuristic::(node_pos, &goal) <= D }) }, - |point| self.heuristic(grid, point, &goal), - |point| self.heuristic(grid, point, &goal) <= if EQUAL_EDGE_COST { 1 } else { 99 }, + |point| self.heuristic::(point, &goal), + |point| self.heuristic::(point, &goal) <= D, ) .map(|(v, _c)| v) } @@ -165,7 +149,7 @@ pub trait GridSolver { |point| { goals .iter() - .map(|x| self.heuristic(grid, point, x)) + .map(|x| self.heuristic::(point, x)) .min() .unwrap() }, diff --git a/tests/benchmark_distances.rs b/tests/benchmark_distances.rs index 20a737a..183363e 100644 --- a/tests/benchmark_distances.rs +++ b/tests/benchmark_distances.rs @@ -33,7 +33,7 @@ fn verify_solution_distance_jps() { .get_path_single_goal(&mut pathing_grid, *start, *end) .unwrap(); save_path(path.clone(), "path.csv").unwrap(); - let float_cost = solver.get_path_cost_float(&path, &pathing_grid); + let float_cost = solver.get_path_cost_float::(&path); println!("My distance: {float_cost}"); if *distance >= 0.01 { let delta_dist = (float_cost - distance).abs() / distance; @@ -60,7 +60,7 @@ fn verify_solution_distance_astar() { .get_path_single_goal(&mut pathing_grid, *start, *end) .unwrap(); save_path(path.clone(), "path.csv").unwrap(); - let float_cost = solver.get_path_cost_float(&path, &pathing_grid); + let float_cost = solver.get_path_cost_float::(&path); println!("My distance: {float_cost}"); if *distance >= 0.01 { let delta_dist = (float_cost - distance).abs() / distance; diff --git a/tests/fuzz_test.rs b/tests/fuzz_test.rs index e657805..81e1a46 100644 --- a/tests/fuzz_test.rs +++ b/tests/fuzz_test.rs @@ -2,7 +2,7 @@ /// by being part of the same connected component. All system settings (diagonals, improved pruning) are tested. use grid_pathfinding::{ pathing_grid::PathingGrid, - solver::{astar::AstarSolver, jps::JPSSolver, GridSolver}, + solver::{alt::ALTSolver, astar::AstarSolver, jps::JPSSolver, GridSolver}, ALLOW_CORNER_CUTTING, }; use grid_util::*; @@ -47,36 +47,32 @@ fn visualize_grid( } } -fn reachable_fuzzer() { +fn reachable_fuzzer(create_solver: FS) +where + S: GridSolver, + FS: Fn(&mut PathingGrid) -> S, +{ const N: usize = 10; const N_GRIDS: usize = 10000; let mut rng = StdRng::seed_from_u64(0); - let arr: SmallVec<[bool; 2]> = if ALLOW_DIAGONAL { - smallvec![false, true] - } else { - smallvec![false] - }; - for improved_pruning in arr { - let mut random_grids: Vec> = Vec::new(); - for _ in 0..N_GRIDS { - random_grids.push(random_grid(N, N, &mut rng)) - } + let mut random_grids: Vec> = Vec::new(); + for _ in 0..N_GRIDS { + random_grids.push(random_grid(N, N, &mut rng)) + } - let start = Point::new(0, 0); - let end = Point::new(N as i32 - 1, N as i32 - 1); - for mut random_grid in random_grids { - let mut solver = JPSSolver::new(&random_grid, improved_pruning); - random_grid.set_point(start, false); - random_grid.set_point(end, false); - solver.initialize(&random_grid); - let reachable = random_grid.reachable(&start, &end); - let path = solver.get_path_single_goal(&mut random_grid, start, end); - // Show the grid if a path is not found - if path.is_some() != reachable { - visualize_grid(&random_grid, &start, &end); - } - assert!(path.is_some() == reachable); + let start = Point::new(0, 0); + let end = Point::new(N as i32 - 1, N as i32 - 1); + for mut random_grid in random_grids { + random_grid.set_point(start, false); + random_grid.set_point(end, false); + let solver = create_solver(&mut random_grid); + let reachable = random_grid.reachable(&start, &end); + let path = solver.get_path_single_goal(&mut random_grid, start, end); + // Show the grid if a path is not found + if path.is_some() != reachable { + visualize_grid(&random_grid, &start, &end); } + assert!(path.is_some() == reachable); } } @@ -114,8 +110,8 @@ fn distance_fuzzer() { .get_path_single_goal(&mut random_grid, start, end) .unwrap(); - let astar_cost = astar_solver.get_path_cost_float(&astar_path, &random_grid); - let jps_cost = jps_solver.get_path_cost_float(&jps_path, &random_grid); + let astar_cost = astar_solver.get_path_cost_float::(&astar_path); + let jps_cost = jps_solver.get_path_cost_float::(&jps_path); if astar_cost >= tolerance { let delta_dist = (jps_cost - astar_cost).abs() / astar_cost; if delta_dist >= tolerance { @@ -126,12 +122,12 @@ fn distance_fuzzer() { for (idx, &p) in jps_path.iter().enumerate().rev() { let jps_suffix = &jps_path[idx..].to_vec(); let jps_suffix_cost = - jps_solver.get_path_cost_float(jps_suffix, &random_grid); + jps_solver.get_path_cost_float::(jps_suffix); let astar_suffix_path = astar_solver .get_path_single_goal(&mut random_grid, p, end) .expect("A* should find a path from intermediate JPS node"); - let astar_suffix_cost = - astar_solver.get_path_cost_float(&astar_suffix_path, &random_grid); + let astar_suffix_cost = astar_solver + .get_path_cost_float::(&astar_suffix_path); let rel_diff = (jps_suffix_cost - astar_suffix_cost).abs() / astar_suffix_cost.max(1e-6); @@ -158,14 +154,39 @@ fn distance_fuzzer() { } } +fn fuzz_reachable_jps_variants() { + let arr: SmallVec<[bool; 2]> = if ALLOW_DIAGONAL { + smallvec![false, true] + } else { + smallvec![false] + }; + for improved_pruning in arr { + reachable_fuzzer::(|pathing_grid| { + let mut solver = JPSSolver::new(&pathing_grid, improved_pruning); + solver.initialize(&pathing_grid); + solver + }) + } +} + +#[test] +fn fuzz_reachable_jps() { + fuzz_reachable_jps_variants::() +} + #[test] -fn fuzz_reachable() { - reachable_fuzzer::() +fn fuzz_reachable_jps_diagonal() { + fuzz_reachable_jps_variants::() } #[test] -fn fuzz_reachable_diagonal() { - reachable_fuzzer::() +fn fuzz_reachable_alt() { + reachable_fuzzer::(|pathing_grid| { + ALTSolver::new_greedy(Point::new(0, 0), 2, pathing_grid) + }); + reachable_fuzzer::(|pathing_grid| { + ALTSolver::new_greedy(Point::new(0, 0), 2, pathing_grid) + }) } #[test]