Skip to content

Conversation

@louis-e
Copy link
Owner

@louis-e louis-e commented Jan 6, 2026

No description provided.

Copilot AI review requested due to automatic review settings January 6, 2026 17:15
@github-actions
Copy link

github-actions bot commented Jan 6, 2026

⏱️ Benchmark run finished in 0m 27s
🧠 Peak memory usage: 809 MB

📈 Compared against baseline: 30s
🧮 Delta: -3s
🔢 Commit: 7bbee28

🟢 Generation time is unchanged.

📅 Last benchmark: 2026-01-06 18:28:44 UTC

You can retrigger the benchmark by commenting retrigger-benchmark.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds multithreaded precomputation of flood fill operations to improve performance by parallelizing polygon filling across multiple CPU cores. The implementation introduces a new FloodFillCache module that precomputes flood fills for all relevant elements before the main processing loop, then retrieves cached results during sequential element processing.

Key changes:

  • New floodfill_cache.rs module providing parallel precomputation and caching of flood fill results
  • Rayon thread pool configuration with CPU usage cap (90%) to maintain system responsiveness
  • Refactored element processing functions to accept and use the flood fill cache

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
src/floodfill_cache.rs New module implementing FloodFillCache with parallel precomputation, cache retrieval methods, and thread pool configuration
src/main.rs Added thread pool configuration call at CLI entry point
src/gui.rs Added thread pool configuration call at GUI entry point
src/data_processing.rs Added cache precomputation before element processing and passed cache to all element processing functions
src/element_processing/natural.rs Updated to accept and use flood fill cache instead of computing on demand
src/element_processing/leisure.rs Updated to accept and use flood fill cache instead of computing on demand
src/element_processing/landuse.rs Updated to accept and use flood fill cache instead of computing on demand
src/element_processing/highways.rs Updated to accept and use flood fill cache instead of computing on demand
src/element_processing/buildings.rs Updated to accept and use flood fill cache instead of computing on demand
src/element_processing/amenities.rs Updated to accept and use flood fill cache instead of computing on demand

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@louis-e louis-e requested a review from Copilot January 6, 2026 18:25
@louis-e
Copy link
Owner Author

louis-e commented Jan 6, 2026

retrigger-benchmark

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

src/element_processing/buildings.rs:1579

  • The bridge_y_offset calculation is duplicated. It's computed inside the loop (lines 1541-1550) and then again after the flood fill (lines 1570-1579). The first calculation should be moved outside the loop before line 1536, as it's loop-invariant and only depends on element tags. This eliminates redundant computation and improves code maintainability.
        // Calculate bridge level based on the "level" tag
        let bridge_y_offset = if let Some(level_str) = element.tags.get("level") {
            if let Ok(level) = level_str.parse::<i32>() {
                (level * 3) + 1
            } else {
                1 // Default elevation
            }
        } else {
            1 // Default elevation
        };

        // Create bridge path using Bresenham's line
        if let Some(prev) = previous_node {
            let bridge_points: Vec<(i32, i32, i32)> =
                bresenham_line(prev.0, bridge_y_offset, prev.1, x, bridge_y_offset, z);

            for (bx, by, bz) in bridge_points {
                // Place railing blocks
                editor.set_block(railing_block, bx, by + 1, bz, None, None);
                editor.set_block(railing_block, bx, by, bz, None, None);
            }
        }

        previous_node = Some((x, z));
    }

    // Flood fill the area between the bridge path nodes (uses cache)
    let bridge_area: Vec<(i32, i32)> = flood_fill_cache.get_or_compute(element, _floodfill_timeout);

    // Calculate bridge level based on the "level" tag
    let bridge_y_offset = if let Some(level_str) = element.tags.get("level") {
        if let Ok(level) = level_str.parse::<i32>() {
            (level * 3) + 1
        } else {
            1 // Default elevation
        }
    } else {
        1 // Default elevation
    };

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +107 to +121
fn way_needs_flood_fill(way: &ProcessedWay) -> bool {
way.tags.contains_key("building")
|| way.tags.contains_key("building:part")
|| way.tags.contains_key("landuse")
|| way.tags.contains_key("leisure")
|| way.tags.contains_key("amenity")
|| way
.tags
.get("natural")
.map(|v| v != "tree")
.unwrap_or(false)
// Highway areas (like pedestrian plazas) use flood fill when area=yes
|| (way.tags.contains_key("highway")
&& way.tags.get("area").map(|v| v == "yes").unwrap_or(false))
}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The way_needs_flood_fill logic doesn't account for all cases where flood fill might be used. For example, checking the amenities.rs code, there are amenity types like "parking" and "shelter" that use flood fill, but the function only checks if "amenity" tag exists, not specific amenity types. However, this is actually correct because it caches all amenities. Consider adding a comment to clarify that the presence of any tag key is sufficient, even if only certain values of that tag use flood fill, since caching extra results is harmless.

Copilot uses AI. Check for mistakes.
use crate::bresenham::bresenham_line;
use crate::coordinate_system::cartesian::XZPoint;
use crate::floodfill::flood_fill_area;
use crate::floodfill::flood_fill_area; // Needed for inline amenity flood fills
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

This import of flood_fill_area is kept for "inline amenity flood fills" according to the comment, but it doesn't appear to be used anywhere in the visible changes. If all flood fill operations now go through the cache, this import may be unnecessary and should be removed to avoid confusion.

Suggested change
use crate::floodfill::flood_fill_area; // Needed for inline amenity flood fills

Copilot uses AI. Check for mistakes.
fillground: fillground_enabled,
debug: false,
timeout: Some(std::time::Duration::from_secs(floodfill_timeout)),
timeout: Some(std::time::Duration::from_secs(40)),
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The hardcoded timeout value of 40 seconds replaces the previously configurable floodfill_timeout parameter. This removes user flexibility and could cause issues for users who previously needed different timeout values. Consider either keeping the parameter or documenting why 40 seconds is now the fixed choice.

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +78
// Configure thread pool with 90% CPU cap to keep system responsive
crate::floodfill_cache::configure_rayon_thread_pool(0.9);
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The thread pool is configured with a 90% CPU cap in both CLI and GUI modes. However, if both run_cli() and run_gui() could potentially be called in the same process (or if the rayon global pool is already configured), the second call will silently fail according to the error handling at line 166-168. Consider adding a warning message or check to inform users if the thread pool was already configured.

Copilot uses AI. Check for mistakes.
way: &ProcessedWay,
timeout: Option<&Duration>,
) -> Vec<(i32, i32)> {
if let Some(cached) = self.way_cache.get(&way.id) {
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The cached flood fill results are cloned on every access (line 78). For large polygons with many coordinates, this could have a performance impact if the same result is accessed multiple times. Consider using Arc or Rc to share the data without cloning, or document that this trade-off is acceptable since each polygon is typically only accessed once during sequential processing.

Suggested change
if let Some(cached) = self.way_cache.get(&way.id) {
if let Some(cached) = self.way_cache.get(&way.id) {
// NOTE: We intentionally clone here to return an owned Vec.
// In the intended usage, each cached flood fill result is normally
// accessed at most once during the sequential processing phase,
// so this per-access clone cost is acceptable and keeps the API
// simple (no Arc/Rc in the public interface).

Copilot uses AI. Check for mistakes.
// Validate floodfill_timeout and ground_level
floodfill_timeout = isNaN(floodfill_timeout) || floodfill_timeout < 0 ? 20 : floodfill_timeout;
// Validate ground_level
ground_level = isNaN(ground_level) || ground_level < -62 ? 20 : ground_level;
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The validation logic for ground_level is incorrect. When ground_level is -62 (the default), the condition ground_level < -62 is false, but then if it were NaN or less than -62, it would be set to 20, which seems inconsistent. The fallback value of 20 doesn't make sense in the context where the default is -62. This should likely be reviewed to ensure the validation logic matches the intended behavior.

Suggested change
ground_level = isNaN(ground_level) || ground_level < -62 ? 20 : ground_level;
ground_level = isNaN(ground_level) || ground_level < -62 ? -62 : ground_level;

Copilot uses AI. Check for mistakes.
@louis-e louis-e merged commit 99d1f8e into main Jan 6, 2026
8 checks passed
@louis-e louis-e deleted the floodfill-precompute branch January 6, 2026 19:00
@louis-e louis-e restored the floodfill-precompute branch January 6, 2026 19:01
@louis-e louis-e deleted the floodfill-precompute branch January 6, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants