hierarchical_pathfinding/lib.rs
1#![deny(
2 missing_docs,
3 // missing_doc_code_examples,
4 missing_debug_implementations,
5 missing_copy_implementations,
6 trivial_casts,
7 trivial_numeric_casts,
8 unsafe_code,
9 unstable_features,
10 unused_import_braces,
11 unused_qualifications
12)]
13#![allow(clippy::upper_case_acronyms)]
14
15//! A crate to quickly approximate Paths on a Grid.
16//!
17//! # Use Case
18//!
19//! Finding Paths on a Grid is an expensive Operation. Consider the following Setup:
20//!
21//! 
22//!
23//! In order to calculate a Path from Start to End using regular A*, it is necessary to check a
24//! lot of Tiles:
25//!
26//! 
27//!
28//! (This is simply a small example, longer Paths require a quadratic increase in Tile checks,
29//! and unreachable Goals require the check of _**every single**_ Tile)
30//!
31//! The Solution that Hierarchical Pathfinding provides is to divide the Grid into Chunks and
32//! cache the Paths between Chunk entrances as a Graph of Nodes:
33//!
34//! 
35//!
36//! This allows Paths to be generated by connecting the Start and End to the Nodes within the
37//! Chunk and using the Graph for the rest:
38//!
39//! 
40//!
41//! Since the Graph is not an exact representation of the Grid, **the resulting Paths will
42//! be slightly worse than the actual best Path** (unless [`config.perfect_paths`](PathCacheConfig::perfect_paths)
43//! is set to `true`). This is usually not a problem, since the purpose of Hierarchical Pathfinding
44//! is to quickly find the next direction to go in or a Heuristic for the Cost and existence
45//! of a Path.
46//!
47//! The only time where the actual best Path would noticeably differ is in the case of short Paths.
48//! That is why this crate calls the regular A* search after HPA* confirmed the length and
49//! existence. (This behavior can be turned of using the Config).
50//!
51//! This crate provides an implementation of a Hierarchical Pathfinding Algorithm for any generic Grid.
52//! Paths can be searched using either A* for a Path to a single Tile, or Dijkstra for searching
53//! multiple Targets. It handles solid walls in the Grid and actually finding a Path to a wall.
54//!
55//! # Examples
56//! ##### Creating the Cache
57//! First is the Grid itself. **How it is stored doesn't matter**, but lookup has to be fast.
58//!
59//! For this example, we shall use a 2D-Array:
60//! ```ignore
61//! // 0 = empty, 1 = swamp, 2 = wall
62//! let mut grid = [
63//! [0, 2, 0, 0, 0],
64//! [0, 2, 2, 2, 0],
65//! [0, 1, 0, 0, 0],
66//! [0, 1, 0, 2, 0],
67//! [0, 0, 0, 2, 0],
68//! ];
69//! let (width, height) = (grid.len(), grid[0].len());
70//!
71//! let cost_map = [
72//! 1, // empty
73//! 10, // swamp
74//! -1, // wall = solid
75//! ];
76//! ```
77//! Now for creating the [`PathCache`]:
78//! ```
79//! # let mut grid = [
80//! # [0, 2, 0, 0, 0],
81//! # [0, 2, 2, 2, 0],
82//! # [0, 1, 0, 0, 0],
83//! # [0, 1, 0, 2, 0],
84//! # [0, 0, 0, 2, 0],
85//! # ];
86//! # let (width, height) = (grid.len(), grid[0].len());
87//! # let cost_map = [
88//! # 1, // empty
89//! # 10, // swamp
90//! # -1, // wall. Negative number == solid
91//! # ];
92//! use hierarchical_pathfinding::prelude::*;
93//!
94//! let mut pathfinding = PathCache::new(
95//! (width, height), // the size of the Grid
96//! |(x, y)| cost_map[grid[y][x]], // get the cost for walking over a Tile
97//! ManhattanNeighborhood::new(width, height), // the Neighborhood
98//! PathCacheConfig::with_chunk_size(3), // config
99//! );
100//! ```
101//! The [`PathCache`] never takes the actual Grid, to allow for any storage format to be used
102//! (`Array`, `Vec`, `HashMap`, `kd-tree`, ...). Instead, it takes a callback function that
103//! indicates, how "expensive" walking across a Tile is (negative numbers for solid obstacles).
104//!
105//! Unfortunately, it is necessary to provide this function to every method of PathCache, since
106//! storing it would make the Grid immutable. See also [Updating the PathCache](#updating-the-pathcache).
107//!
108//! [Currying](https://en.wikipedia.org/wiki/Currying) can be used to reduce duplication:
109//! ```
110//! # use hierarchical_pathfinding::prelude::*;
111//! # // 0 = empty, 1 = swamp, 2 = wall
112//! # let mut grid = [
113//! # [0, 2, 0, 0, 0],
114//! # [0, 2, 2, 2, 0],
115//! # [0, 1, 0, 0, 0],
116//! # [0, 1, 0, 2, 0],
117//! # [0, 0, 0, 2, 0],
118//! # ];
119//! # let (width, height) = (grid.len(), grid[0].len());
120//! # type Grid = [[usize; 5]; 5];
121//! const COST_MAP: [isize; 3] = [1, 10, -1]; // now const for ownership reasons
122//!
123//! // only borrows the Grid when called
124//! fn cost_fn(grid: &Grid) -> impl '_ + Sync + Fn((usize, usize)) -> isize {
125//! move |(x, y)| COST_MAP[grid[y][x]]
126//! }
127//!
128//! let mut pathfinding = PathCache::new(
129//! (width, height), // the size of the Grid
130//!
131//! // simply call the creator function to take a reference of the Grid
132//! cost_fn(&grid),
133//! // ...
134//! # ManhattanNeighborhood::new(width, height), // the Neighborhood
135//! # PathCacheConfig::with_chunk_size(3), // config
136//! # );
137//! ```
138//!
139//! ##### Pathfinding
140//! Finding the Path to a single Goal:
141//! ```
142//! # use hierarchical_pathfinding::prelude::*;
143//! # let mut grid = [
144//! # [0, 2, 0, 0, 0],
145//! # [0, 2, 2, 2, 2],
146//! # [0, 1, 0, 0, 0],
147//! # [0, 1, 0, 2, 0],
148//! # [0, 0, 0, 2, 0],
149//! # ];
150//! # let (width, height) = (grid.len(), grid[0].len());
151//! # fn cost_fn(grid: &[[usize; 5]; 5]) -> impl '_ + Sync + Fn((usize, usize)) -> isize {
152//! # move |(x, y)| [1, 10, -1][grid[y][x]]
153//! # }
154//! # let mut pathfinding = PathCache::new(
155//! # (width, height),
156//! # cost_fn(&grid),
157//! # ManhattanNeighborhood::new(width, height),
158//! # PathCacheConfig::with_chunk_size(3),
159//! # );
160//! #
161//! let start = (0, 0);
162//! let goal = (4, 4);
163//!
164//! // find_path returns Some(Path) on success
165//! let path = pathfinding.find_path(
166//! start,
167//! goal,
168//! cost_fn(&grid),
169//! );
170//!
171//! assert!(path.is_some());
172//! let path = path.unwrap();
173//!
174//! assert_eq!(path.cost(), 12);
175//! ```
176//! For more information, see [`find_path`](PathCache::find_path).
177//!
178//! Finding multiple Goals:
179//! ```
180//! # use hierarchical_pathfinding::prelude::*;
181//! # let mut grid = [
182//! # [0, 2, 0, 0, 0],
183//! # [0, 2, 2, 2, 2],
184//! # [0, 1, 0, 0, 0],
185//! # [0, 1, 0, 2, 0],
186//! # [0, 0, 0, 2, 0],
187//! # ];
188//! # let (width, height) = (grid.len(), grid[0].len());
189//! # fn cost_fn(grid: &[[usize; 5]; 5]) -> impl '_ + Sync + Fn((usize, usize)) -> isize {
190//! # move |(x, y)| [1, 10, -1][grid[y][x]]
191//! # }
192//! # let mut pathfinding = PathCache::new(
193//! # (width, height),
194//! # cost_fn(&grid),
195//! # ManhattanNeighborhood::new(width, height),
196//! # PathCacheConfig::with_chunk_size(3),
197//! # );
198//! #
199//! let start = (0, 0);
200//! let goals = [(4, 4), (2, 0)];
201//!
202//! // find_paths returns a HashMap<goal, Path> for all successes
203//! let paths = pathfinding.find_paths(
204//! start,
205//! &goals,
206//! cost_fn(&grid),
207//! );
208//!
209//! // (4, 4) is reachable
210//! assert!(paths.contains_key(&goals[0]));
211//!
212//! // (2, 0) is not reachable
213//! assert!(!paths.contains_key(&goals[1]));
214//! ```
215//! For more information, see [`find_paths`](PathCache::find_paths).
216//!
217//! ##### Using a Path
218//! - Path exists: `path.is_some()` | `paths.contains_key()`
219//! - Useful as a Heuristic for other Algorithms
220//! - **100% correct** (`true` if and only if path can be found)
221//! - Total Cost of the Path: [`path.cost()`](internals::AbstractPath::cost)
222//! - Correct for this Path, may be slightly larger than for optimal Path
223//! - The cost is simply returned; `cost()` does no calculations
224//! - Total Length of the Path: [`path.length()`](internals::AbstractPath::length)
225//! - Correct for this Path, may be slightly longer than the optimal Path
226//! - The length is simply returned; `length()` does no calculations
227//! - Next Position: [`path.next()`](internals::AbstractPath::next) | [`path.safe_next(cost_fn)`](internals::AbstractPath::safe_next)
228//! - [`safe_next`](internals::AbstractPath::safe_next) is needed if [`config.cache_paths`](crate::PathCacheConfig::cache_paths) is set to `false`
229//! - can be called several times to iterate Path
230//! - path implements `Iterator<Item = (usize, usize)>`
231//! - Entire Path: `path.collect::<Vec<_>>()` | [`path.resolve(cost_fn)`](internals::AbstractPath::resolve)
232//! - [`resolve`](internals::AbstractPath::resolve) is needed if [`config.cache_paths`](crate::PathCacheConfig::cache_paths) is set to `false`
233//! - Returns a `Vec<(usize, usize)>`
234//!
235//! Note that [`resolve`](internals::AbstractPath::resolve) calculates any missing segments (if [`config.cache_paths`](crate::PathCacheConfig::cache_paths) ` == false`)
236//! and allocates a [`Vec`](std::vec::Vec) with the resulting Points. Not recommended if only the
237//! beginning of the Path is needed.
238//! ```
239//! # use hierarchical_pathfinding::prelude::*;
240//! # let mut grid = [
241//! # [0, 2, 0, 0, 0],
242//! # [0, 2, 2, 2, 2],
243//! # [0, 1, 0, 0, 0],
244//! # [0, 1, 0, 2, 0],
245//! # [0, 0, 0, 2, 0],
246//! # ];
247//! # let (width, height) = (grid.len(), grid[0].len());
248//! # fn cost_fn(grid: &[[usize; 5]; 5]) -> impl '_ + Sync + Fn((usize, usize)) -> isize {
249//! # move |(x, y)| [1, 10, -1][grid[y][x]]
250//! # }
251//! # let mut pathfinding = PathCache::new(
252//! # (width, height),
253//! # cost_fn(&grid),
254//! # ManhattanNeighborhood::new(width, height),
255//! # PathCacheConfig::with_chunk_size(3),
256//! # );
257//! # struct Player{ pos: (usize, usize) }
258//! # impl Player {
259//! # pub fn move_to(&mut self, pos: (usize, usize)) {
260//! # self.pos = pos;
261//! # }
262//! # }
263//! #
264//! let mut player = Player {
265//! pos: (0, 0),
266//! //...
267//! };
268//! let goal = (4, 4);
269//!
270//! let mut path = pathfinding.find_path(
271//! player.pos,
272//! goal,
273//! cost_fn(&grid),
274//! ).unwrap();
275//!
276//! player.move_to(path.next().unwrap());
277//! assert_eq!(player.pos, (0, 1));
278//!
279//! // wait for next turn or whatever
280//!
281//! player.move_to(path.next().unwrap());
282//! assert_eq!(player.pos, (0, 2));
283//!
284//! // iterating is possible
285//! for new_pos in path {
286//! player.move_to(new_pos);
287//! }
288//! assert_eq!(player.pos, goal);
289//! ```
290//!
291//! ##### Updating the PathCache
292//! The PathCache does not contain a copy or reference of the Grid for mutability and Ownership reasons.
293//! This means however, that the user is responsible for storing and maintaining both the Grid and the PathCache.
294//! It is also necessary to update the PathCache when the Grid has changed to keep it consistent:
295//! ```should_panic
296//! # use hierarchical_pathfinding::prelude::*;
297//! # let mut grid = [
298//! # [0, 2, 0, 0, 0],
299//! # [0, 2, 2, 2, 2],
300//! # [0, 1, 0, 0, 0],
301//! # [0, 1, 0, 2, 0],
302//! # [0, 0, 0, 2, 0],
303//! # ];
304//! # let (width, height) = (grid.len(), grid[0].len());
305//! # fn cost_fn(grid: &[[usize; 5]; 5]) -> impl '_ + Sync + Fn((usize, usize)) -> isize {
306//! # move |(x, y)| [1, 10, -1][grid[y][x]]
307//! # }
308//! # let mut pathfinding = PathCache::new(
309//! # (width, height),
310//! # cost_fn(&grid),
311//! # ManhattanNeighborhood::new(width, height),
312//! # PathCacheConfig::with_chunk_size(3),
313//! # );
314//! #
315//! let (start, goal) = ((0, 0), (2, 0));
316//!
317//! let path = pathfinding.find_path(start, goal, cost_fn(&grid));
318//! assert!(path.is_none()); // from previous example
319//!
320//! // Clear a way to the goal
321//! grid[1][2] = 0; // at (2, 1): the wall below the goal
322//!
323//! let path = pathfinding.find_path(start, goal, cost_fn(&grid));
324//! assert!(path.is_some()); // there should be a Path now!
325//! ```
326//! [`tiles_changed`](PathCache::tiles_changed) must be called with all changed Tiles:
327//! ```
328//! # use hierarchical_pathfinding::prelude::*;
329//! # let mut grid = [
330//! # [0, 2, 0, 0, 0],
331//! # [0, 2, 2, 2, 2],
332//! # [0, 1, 0, 0, 0],
333//! # [0, 1, 0, 2, 0],
334//! # [0, 0, 0, 2, 0],
335//! # ];
336//! # let (width, height) = (grid.len(), grid[0].len());
337//! # fn cost_fn(grid: &[[usize; 5]; 5]) -> impl '_ + Sync + Fn((usize, usize)) -> isize {
338//! # move |(x, y)| [1, 10, -1][grid[y][x]]
339//! # }
340//! # let mut pathfinding = PathCache::new(
341//! # (width, height),
342//! # cost_fn(&grid),
343//! # ManhattanNeighborhood::new(width, height),
344//! # PathCacheConfig::with_chunk_size(3),
345//! # );
346//! #
347//! let (start, goal) = ((0, 0), (2, 0));
348//!
349//! let path = pathfinding.find_path(start, goal, cost_fn(&grid));
350//! assert!(path.is_none());
351//!
352//! // Clear a way to the goal
353//! grid[1][2] = 0; // at (2, 1): the wall below the goal
354//!
355//! pathfinding.tiles_changed(
356//! &[(2, 1)],
357//! cost_fn(&grid),
358//! );
359//!
360//! let path = pathfinding.find_path(start, goal, cost_fn(&grid));
361//! assert!(path.is_some());
362//! ```
363//! `tiles_changed` takes a slice of Points, and it is recommended to bundle changes together for
364//! performance reasons.
365//!
366//! ##### Configuration
367//! The last parameter for PathCache::new is a [`PathCacheConfig`] object with different options to have more control over the generated PathCache.
368//! These options are mostly used to adjust the balance between Performance and Memory Usage, with the default values aiming more at Performance.
369//! ```
370//! # use hierarchical_pathfinding::prelude::*;
371//! # let mut grid = [
372//! # [0, 2, 0, 0, 0],
373//! # [0, 2, 2, 2, 2],
374//! # [0, 1, 0, 0, 0],
375//! # [0, 1, 0, 2, 0],
376//! # [0, 0, 0, 2, 0],
377//! # ];
378//! # let (width, height) = (grid.len(), grid[0].len());
379//! # fn cost_fn(grid: &[[usize; 5]; 5]) -> impl '_ + Sync + Fn((usize, usize)) -> isize {
380//! # move |(x, y)| [1, 10, -1][grid[y][x]]
381//! # }
382//!
383//! let mut pathfinding = PathCache::new(
384//! (width, height), // the size of the Grid
385//! cost_fn(&grid), // get the cost for walking over a Tile
386//! ManhattanNeighborhood::new(width, height), // the Neighborhood
387//! PathCacheConfig {
388//! chunk_size: 3,
389//! ..PathCacheConfig::LOW_MEM
390//! }
391//! );
392//!
393//! assert_eq!(pathfinding.config().chunk_size, 3);
394//! ```
395//! # Cargo Features
396//! ##### parallel
397//! Enabled by default.
398//!
399//! The parallel feature causes [`PathCache`] creation and updates to be multithreaded using [Rayon](https://crates.io/crates/rayon), making them significantly faster.
400//! This feature has no effect on the speed of finding paths (_yet_).
401//!
402//! ##### log
403//! Disabled by default.
404//!
405//! The log feature is used to enable internal timings on some functions.
406//!
407//! You probably shouldn't enable this feature unless you are working on improvments to hierarchical_pathfinding.
408//! In order to comsume the logs, you need a logger setup to show trace! level logs.
409//! See the [log](https://crates.io/crates/log) crate for more details.
410//!
411
412/// Shorthand for a 2D Point
413type Point = (usize, usize);
414
415/// A convenience type for a [`HashMap`](hashbrown::HashMap) using Points as the key
416type PointMap<V> = hashbrown::HashMap<Point, V>;
417/// A convenience type for a [`HashSet`](hashbrown::HashSet) with Points
418type PointSet = hashbrown::HashSet<Point>;
419
420/// The Type used to reference a Node in the abstracted Graph
421type NodeID = u32;
422
423/// A convenience type for a [`HashMap`](hashbrown::HashMap) using NodeIDs as the key
424type NodeIDMap<V> = hashbrown::HashMap<NodeID, V>;
425/// A convenience type for a [`HashSet`](hashbrown::HashSet) with NodeIDs
426type NodeIDSet = hashbrown::HashSet<NodeID>;
427
428mod path_cache;
429pub use self::path_cache::{PathCache, PathCacheConfig};
430
431mod path;
432
433mod utils;
434pub(crate) use utils::*;
435
436pub mod neighbors;
437
438mod graph;
439mod grid;
440
441/// Internal stuff that is returned by other function
442pub mod internals {
443 pub use crate::path::AbstractPath;
444 pub use crate::path_cache::{CacheInspector, NodeInspector};
445}
446
447/// The prelude for this crate.
448pub mod prelude {
449 pub use crate::{
450 neighbors::{ManhattanNeighborhood, MooreNeighborhood, Neighborhood},
451 PathCache, PathCacheConfig,
452 };
453}