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//! ![The Setup](https://github.com/mich101mich/hierarchical_pathfinding/blob/master/img/problem.png?raw=true)
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//! ![A*](https://github.com/mich101mich/hierarchical_pathfinding/blob/master/img/a_star.png?raw=true)
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//! ![The Graph](https://github.com/mich101mich/hierarchical_pathfinding/blob/master/img/hpa.png?raw=true)
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//! ![The Solution](https://github.com/mich101mich/hierarchical_pathfinding/blob/master/img/hpa_solution.png?raw=true)
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}