Skip to content

feat: bfs dijkstra flow map#97

Open
Malibu-With-Coke wants to merge 3 commits into
utilForever:mainfrom
Malibu-With-Coke:feat/bfs-dijkstra-flow-map
Open

feat: bfs dijkstra flow map#97
Malibu-With-Coke wants to merge 3 commits into
utilForever:mainfrom
Malibu-With-Coke:feat/bfs-dijkstra-flow-map

Conversation

@Malibu-With-Coke
Copy link
Copy Markdown

@Malibu-With-Coke Malibu-With-Coke commented May 17, 2026

What

Add a dedicated BFS flow map, update Dijkstra maps to use a priority queue for weighted shortest-path costs, and add BFS example/docs coverage.

Why

The previous DijkstraMap implementation used FIFO queue traversal, which behaves more like BFS/flood fill and can produce incorrect results with non-uniform exit costs. Splitting BFS into BfsMap and moving DijkstraMap to BinaryHeap keeps the algorithm roles clear and makes weighted path costs correct.

Closes #32

Checklist

Required

  • cargo check --all passes
  • cargo fmt --all -- --check passes
  • cargo clippy --workspace --all-targets -- -D warnings -A clippy::multiple-crate-versions passes
  • cargo test --all passes
  • I linked the related issue (for example: Closes #123)

Functional Validation

  • Behavior related to this change was verified locally (if applicable)
  • Rendering/backend behavior was verified when runtime code changed (if applicable)
  • Algorithm behavior (pathfinding/FOV/noise/random) was verified when affected (if applicable)
  • I added or updated tests for changed behavior (if applicable)

Configuration & Docs

  • User-facing docs were updated (README.md, ARCHITECTURE.md, or relevant manual pages, if applicable)
  • New dependencies/configuration are documented (if applicable)
  • No sensitive values or credentials were introduced

If Applicable

  • Security impact considered (run cargo audit locally if needed)
  • Breaking behavior changes are clearly described in this PR

Summary by CodeRabbit

  • New Features

    • Added breadth-first search (BFS) algorithm for unweighted flow mapping from multiple starting points.
  • Bug Fixes

    • Fixed Dijkstra algorithm to correctly handle weighted paths using proper priority-queue pathfinding.
  • Documentation

    • Updated documentation and added new examples demonstrating BFS capabilities.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

This PR adds BFS (breadth-first) pathfinding to the bracket-pathfinding crate alongside existing A* and Dijkstra implementations, fixes a correctness issue in Dijkstra's weighted-path selection, implements a runnable BFS visualization example, and updates documentation across multiple files to reflect BFS support.

Changes

BFS Feature Implementation

Layer / File(s) Summary
BFS Data Structure and Build Algorithm
bracket-pathfinding/src/bfs.rs
BfsMap stores per-tile depth values (Vec<f32>) initialized to f32::MAX. Unweighted constructor sets start depth to 0.0; weighted variant accepts explicit start depths. Serial build seeds an open list and performs breadth-first relaxation by incrementing depth by 1.0 per step. When threaded feature is enabled and start count exceeds threshold, parallel build partitions starts across thread chunks, precomputes exits globally, runs independent BFS layers in parallel with per-layer depth buffers, then merges by taking minimum depth per tile.
BFS Path Traversal Helpers
bracket-pathfinding/src/bfs.rs
find_lowest_exit and find_highest_exit functions query BaseMap exits from a position, return None when no exits exist, and select an exit by sorting candidates on bfs.map depth values (using parallel sort when threaded feature is enabled).
BFS Module Integration
bracket-pathfinding/src/lib.rs
Declare bfs module and extend public prelude to re-export crate::bfs::* alongside astar, dijkstra, and field_of_view for downstream convenience.
BFS Visualization Example
bracket-pathfinding/examples/bfs/main.rs
Runnable example constructs a Map, builds a BfsMap from start and end positions, then renders the grid to console: walls shown in yellow, reachable tiles colorized by depth value, unreachable tiles colored as chocolate.

Dijkstra Algorithm Correctness Fix

Layer / File(s) Summary
Priority Queue Implementation
bracket-pathfinding/src/dijkstra.rs
Replace queue-based relaxation (using VecDeque) with priority-queue semantics. Add imports for std::cmp::Ordering and std::collections::BinaryHeap. Define internal DijkstraNode struct with custom Ord/PartialOrd/Eq/PartialEq to order nodes by lowest cost (ties broken by index), enabling BinaryHeap to pop the minimum-cost node.
Serial Dijkstra Build Methods
bracket-pathfinding/src/dijkstra.rs
DijkstraMap::build now delegates to build_weighted by converting unweighted starts to weighted with cost 0.0. build_weighted initializes a BinaryHeap<DijkstraNode> from starts, repeatedly pops the lowest-cost node, skips stale heap entries when popped cost exceeds stored best cost, and pushes improved neighbor costs into the heap.
Parallel Dijkstra Build
bracket-pathfinding/src/dijkstra.rs
build_parallel partitions starts across thread chunks, precomputes all tile exits once, runs independent per-layer BinaryHeap relaxations in parallel with per-layer depth buffers, then merges by taking the minimum cost per tile across all layers.
Dijkstra Test Coverage
bracket-pathfinding/src/dijkstra.rs
Add WeightedShortcutMap test fixture with weighted edges and unreachable nodes, and add dijkstra_follows_lowest_total_cost test validating that the algorithm correctly selects the lowest total-cost path under weighted shortcuts.

Package Documentation Updates

Layer / File(s) Summary
Crate Metadata and Top-level Documentation
bracket-pathfinding/Cargo.toml, src/lib.rs
Update Cargo.toml package description and src/lib.rs pathfinding module doc comment to include BFS in the list of supported algorithms alongside A* and Dijkstra.
README Algorithm and Example Documentation
bracket-pathfinding/README.md
Update intro and algorithm overview sections to list BFS. Add new "BFS Mapping" section explaining BfsMap construction, distance lookup, and helper functions. Align Dijkstra section guidance with BFS. Expand examples list from three to six, including astar_manhattan, bfs, and dijkstra_weighted. Clarify that threaded feature affects both BFS and Dijkstra functions.
Manual Documentation
manual/src/ex_path.md, manual/src/individual_parts.md
Add bfs example subsection to ex_path.md between astar_manhattan and dijkstra; update individual_parts.md to list BFS as a bracket-pathfinding capability alongside A* and Dijkstra.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • utilForever/roguekit#100: Modifies bracket-pathfinding/src/dijkstra.rs build logic and unit tests affecting DijkstraMap weighted-path behavior.

Suggested reviewers

  • utilForever

Poem

A rabbit hops through BFS queues,
where breadth flows wide with depth's new hues,
while Dijkstra's heap now pops with grace—
three pathways shine in roguekit's space! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: bfs dijkstra flow map' clearly and concisely summarizes the main changes: introducing BFS flow mapping functionality alongside improvements to Dijkstra's algorithm implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Malibu-With-Coke Malibu-With-Coke force-pushed the feat/bfs-dijkstra-flow-map branch from 4d8ab73 to bf96536 Compare May 22, 2026 12:04
@Malibu-With-Coke Malibu-With-Coke marked this pull request as ready for review May 24, 2026 01:04
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
bracket-pathfinding/src/bfs.rs (3)

249-249: 💤 Low value

partial_cmp().unwrap() will panic if BFS map contains NaN.

While the map is initialized to f32::MAX (safe for comparison), the public map field allows external modification. If corrupted with NaN, partial_cmp returns None and the unwrap panics.

Consider using unwrap_or(Ordering::Equal) for defensive handling:

🛡️ Suggested fix
-        exits.par_sort_by(|a, b| bfs.map[a.0].partial_cmp(&bfs.map[b.0]).unwrap());
+        exits.par_sort_by(|a, b| {
+            bfs.map[a.0]
+                .partial_cmp(&bfs.map[b.0])
+                .unwrap_or(std::cmp::Ordering::Equal)
+        });

Apply similarly to all four helper variants.

Also applies to: 262-262

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bracket-pathfinding/src/bfs.rs` at line 249, The sort comparator currently
calls bfs.map[a.0].partial_cmp(&bfs.map[b.0]).unwrap(), which will panic if
either value is NaN; change the comparator to handle None defensively by using
.unwrap_or(std::cmp::Ordering::Equal) (or Ordering::Equal via a use) instead of
unwrap so NaNs don't cause a panic; replace the unwrap in exits.par_sort_by and
the three other helper variants that use partial_cmp on bfs.map with this
defensive pattern referencing the exits.par_sort_by call and the bfs.map field.

298-324: ⚡ Quick win

Consider expanding test coverage for BFS module.

The existing test validates that BFS counts edges rather than costs, which is good. However, coverage is minimal for a new module. Consider adding tests for:

  • find_lowest_exit / find_highest_exit helpers
  • max_depth cutoff behavior (verify tiles beyond cutoff remain f32::MAX)
  • new_empty and clear methods
  • Parallel build path (under #[cfg(feature = "threaded")])

As per coding guidelines: "Add tests for new functionality in the relevant module; for split domains, prefer colocated tests.rs".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bracket-pathfinding/src/bfs.rs` around lines 298 - 324, Add unit tests in the
BFS test module to increase coverage: write tests that call find_lowest_exit and
find_highest_exit on small maps with known exits to assert correct indices are
returned; test max_depth cutoff by creating a BfsMap with a tight max_depth and
assert tiles beyond the cutoff remain f32::MAX; add tests for BfsMap::new_empty
(or new_empty) and BfsMap::clear to verify they initialize and reset the
internal map state as expected; and, if the threaded feature exists, add a test
exercising the parallel build path (e.g., BfsMap::new with threaded feature) to
ensure it produces identical results to the single-threaded build. Ensure each
test references the BfsMap methods and helper functions by name so they’re easy
to locate.

204-210: 💤 Low value

Parallel BFS hardcodes start depth to 0.0, unlike serial build_weighted.

The serial build_weighted accepts (usize, f32) tuples allowing variable start depths, but the parallel path hardcodes 0.0 for all starts. The condition 0.0 >= l.map[start] is always false (since l.map[start] is f32::MAX), making the first check dead code.

Currently safe since build_parallel is only invoked from build() which uses depth 0.0 for all starts. Consider a TODO if future support for parallel weighted starts is desired.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bracket-pathfinding/src/bfs.rs` around lines 204 - 210, The parallel BFS
currently hardcodes start depth to 0.0 and leaves a dead branch; update
build_parallel (and any callers that populate l.starts) to accept start depth
tuples like the serial build_weighted by changing l.starts to hold (usize, f32)
pairs, then in the loop use the provided depth (e.g., for (start, depth) in
l.starts.iter().copied()) and replace the checks/assignments to compare and
assign depth (if depth >= l.map[start] || depth >= l.max_depth { continue }
l.map[start] = depth; open_list.push_back((start, depth));). Also update any
call sites that construct l.starts and add a TODO comment if you prefer to
postpone full API changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@manual/src/ex_path.md`:
- Line 39: Replace the empty alt text in the image markdown
![](./ex_path_fov.jpg) with a concise, descriptive alt string that explains the
image content for accessibility (for example: ![Example path field-of-view
visualization showing camera frustum and obstacle layout](./ex_path_fov.jpg));
locate the image token in manual/src/ex_path.md and update only the alt text
between the brackets to a brief descriptive phrase that conveys the image's
meaning to screen readers.

---

Nitpick comments:
In `@bracket-pathfinding/src/bfs.rs`:
- Line 249: The sort comparator currently calls
bfs.map[a.0].partial_cmp(&bfs.map[b.0]).unwrap(), which will panic if either
value is NaN; change the comparator to handle None defensively by using
.unwrap_or(std::cmp::Ordering::Equal) (or Ordering::Equal via a use) instead of
unwrap so NaNs don't cause a panic; replace the unwrap in exits.par_sort_by and
the three other helper variants that use partial_cmp on bfs.map with this
defensive pattern referencing the exits.par_sort_by call and the bfs.map field.
- Around line 298-324: Add unit tests in the BFS test module to increase
coverage: write tests that call find_lowest_exit and find_highest_exit on small
maps with known exits to assert correct indices are returned; test max_depth
cutoff by creating a BfsMap with a tight max_depth and assert tiles beyond the
cutoff remain f32::MAX; add tests for BfsMap::new_empty (or new_empty) and
BfsMap::clear to verify they initialize and reset the internal map state as
expected; and, if the threaded feature exists, add a test exercising the
parallel build path (e.g., BfsMap::new with threaded feature) to ensure it
produces identical results to the single-threaded build. Ensure each test
references the BfsMap methods and helper functions by name so they’re easy to
locate.
- Around line 204-210: The parallel BFS currently hardcodes start depth to 0.0
and leaves a dead branch; update build_parallel (and any callers that populate
l.starts) to accept start depth tuples like the serial build_weighted by
changing l.starts to hold (usize, f32) pairs, then in the loop use the provided
depth (e.g., for (start, depth) in l.starts.iter().copied()) and replace the
checks/assignments to compare and assign depth (if depth >= l.map[start] ||
depth >= l.max_depth { continue } l.map[start] = depth;
open_list.push_back((start, depth));). Also update any call sites that construct
l.starts and add a TODO comment if you prefer to postpone full API changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d3cf71e9-216e-480e-8afa-cd5ffeb3d125

📥 Commits

Reviewing files that changed from the base of the PR and between fe7bf77 and 4201983.

📒 Files selected for processing (9)
  • bracket-pathfinding/Cargo.toml
  • bracket-pathfinding/README.md
  • bracket-pathfinding/examples/bfs/main.rs
  • bracket-pathfinding/src/bfs.rs
  • bracket-pathfinding/src/dijkstra.rs
  • bracket-pathfinding/src/lib.rs
  • manual/src/ex_path.md
  • manual/src/individual_parts.md
  • src/lib.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format all Rust code using cargo fmt --all
Fix all cargo clippy warnings — the CI enforces -D warnings
Add tests for new functionality in the relevant module; for split domains, prefer colocated tests.rs

Files:

  • bracket-pathfinding/examples/bfs/main.rs
  • bracket-pathfinding/src/lib.rs
  • src/lib.rs
  • bracket-pathfinding/src/dijkstra.rs
  • bracket-pathfinding/src/bfs.rs
🪛 LanguageTool
manual/src/individual_parts.md

[style] ~9-~9: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...erating things. * bracket-pathfinding provides A-Star, BFS and Dijkstra mapping soluti...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

bracket-pathfinding/README.md

[style] ~3-~3: ‘in conjunction with’ might be wordy. Consider a shorter alternative.
Context: ... the overall bracket-lib system, and (in conjunction with bracket-algorithm-traits) provides pa...

(EN_WORDINESS_PREMIUM_IN_CONJUNCTION_WITH)

🪛 markdownlint-cli2 (0.22.1)
manual/src/ex_path.md

[warning] 39-39: Images should have alternate text (alt text)

(MD045, no-alt-text)

🔇 Additional comments (18)
bracket-pathfinding/Cargo.toml (1)

7-7: LGTM!

src/lib.rs (1)

40-40: LGTM!

bracket-pathfinding/README.md (1)

3-3: LGTM!

Also applies to: 29-29, 91-107, 121-121, 137-137, 141-141, 144-147

manual/src/ex_path.md (1)

19-24: LGTM!

manual/src/individual_parts.md (1)

9-9: LGTM!

bracket-pathfinding/src/bfs.rs (5)

267-295: Same partial_cmp().unwrap() consideration applies here as noted above.


1-40: LGTM!


42-110: LGTM!


112-135: LGTM!


137-179: LGTM!

bracket-pathfinding/src/lib.rs (1)

4-4: LGTM!

Also applies to: 10-10

bracket-pathfinding/examples/bfs/main.rs (1)

1-47: LGTM!

bracket-pathfinding/src/dijkstra.rs (6)

6-7: LGTM!

The DijkstraNode ordering correctly implements min-heap semantics by reversing the cost comparison (other.cost.partial_cmp(&self.cost)), which is the standard pattern for Dijkstra with Rust's max-heap BinaryHeap. Tie-breaking by index ensures deterministic behavior.

Also applies to: 29-57


178-179: LGTM!


185-219: LGTM!

The priority-queue Dijkstra implementation is correct: seeds starts into the heap, pops minimum-cost nodes, skips stale entries, and relaxes neighbors with proper cost accumulation. This fixes the previous BFS-like behavior that could produce incorrect results with non-uniform exit costs.


223-286: LGTM!

The parallel implementation correctly uses per-layer BinaryHeap with proper stale-entry checks and min-recombination. Pre-computing exits outside the parallel loop is a good optimization to avoid redundant get_available_exits calls.


382-392: LGTM!


457-466: LGTM!

This test validates the core correctness fix: with the priority-queue implementation, Dijkstra correctly discovers that path 0→2→1 (cost 2.0) is cheaper than direct path 0→1 (cost 10.0). This was the exact scenario that would fail with the previous BFS-like traversal.

Comment thread manual/src/ex_path.md
Demonstrates the Field-of-View functionality.

![](./ex_path_fov.jpg) No newline at end of file
![](./ex_path_fov.jpg)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add alt text to the image markdown for accessibility.

Line 39 should use descriptive alt text instead of an empty alt field.

Proposed fix
-![](./ex_path_fov.jpg)
+![Field-of-view example output](./ex_path_fov.jpg)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
![](./ex_path_fov.jpg)
![Field-of-view example output](./ex_path_fov.jpg)
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 39-39: Images should have alternate text (alt text)

(MD045, no-alt-text)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@manual/src/ex_path.md` at line 39, Replace the empty alt text in the image
markdown ![](./ex_path_fov.jpg) with a concise, descriptive alt string that
explains the image content for accessibility (for example: ![Example path
field-of-view visualization showing camera frustum and obstacle
layout](./ex_path_fov.jpg)); locate the image token in manual/src/ex_path.md and
update only the alt text between the brackets to a brief descriptive phrase that
conveys the image's meaning to screen readers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bracket-pathfinding에 BFS 알고리즘 구현하기

1 participant