From c755df7a169a90e424b285bf95d08a1d673caab9 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue, 5 May 2026 19:41:47 +0200 Subject: [PATCH] Fix graph update logic --- .../LargeGridPathfindingGame.cs | 8 +- src/LargeGridPathfinding/Pathfinder.cs | 137 ++++++++++-------- 2 files changed, 80 insertions(+), 65 deletions(-) diff --git a/src/LargeGridPathfinding/LargeGridPathfindingGame.cs b/src/LargeGridPathfinding/LargeGridPathfindingGame.cs index 743f962..995ad67 100644 --- a/src/LargeGridPathfinding/LargeGridPathfindingGame.cs +++ b/src/LargeGridPathfinding/LargeGridPathfindingGame.cs @@ -628,8 +628,8 @@ public class LargeGridPathfindingGame : Game _ = Task.Run(() => { // Configuration options - int width = 1000; - int height = 1000; + int width = 10000; + int height = 10000; int agentCount = 10000; bool pathRandomization = false; // Randomize path costs to prevent agents from following the same path @@ -778,8 +778,8 @@ public class LargeGridPathfindingGame : Game { // Full rebuild only if grid changed but no zones tracked (e.g., initial grid fill) gridChanged = false; - ProgressTracker.ProgressData progressDataBuildGraph = progressTracker.AddProgress("Rebuilding graph", true, out _); - pathfinder.RebuildGraph(); + ProgressTracker.ProgressData progressDataBuildGraph = progressTracker.AddProgress("Building graph", true, out _); + pathfinder.BuildGraph(); progressTracker.RemoveProgress(progressDataBuildGraph); } diff --git a/src/LargeGridPathfinding/Pathfinder.cs b/src/LargeGridPathfinding/Pathfinder.cs index 50d1ece..e2c8fb9 100644 --- a/src/LargeGridPathfinding/Pathfinder.cs +++ b/src/LargeGridPathfinding/Pathfinder.cs @@ -19,12 +19,18 @@ public class Pathfinder private Dictionary rectangleMap; private Dictionary> adjacencyList; - public Dictionary> GetAdjacencyList() => adjacencyList; + public Dictionary> GetAdjacencyList() + { + return adjacencyList; + } public int GetGridValue(Point gridPoint) { if (gridPoint.X < 0 || gridPoint.Y < 0 || gridPoint.X >= grid.GetLength(1) || gridPoint.Y >= grid.GetLength(0)) + { return 0; + } + return grid[gridPoint.Y, gridPoint.X]; } @@ -36,7 +42,7 @@ public class Pathfinder this.pathRandomization = pathRandomization; this.penalizeStretchedRectangles = penalizeStretchedRectangles; rectangleMap = new Dictionary(rectangles); - adjacencyList = BuildGraph(); + adjacencyList = []; } public List? FindPath(Point startPoint, Point goalPoint) @@ -82,9 +88,9 @@ public class Pathfinder foreach (int neighbor in adjacencyList.TryGetValue(node, out List? neighbors) ? neighbors : []) { - if (!visited.Contains(neighbor)) + if (!visited.Contains(neighbor) && rectangleMap.ContainsKey(neighbor)) { - List newPath = new List(path) { neighbor }; + List newPath = [.. path, neighbor]; int newCost = cost + GetRectangleWeight(neighbor); @@ -109,18 +115,25 @@ public class Pathfinder return null; } - public void RebuildGraph() + public void BuildGraph() { rectangleMap = new Dictionary(rectanglesSource); adjacencyList.Clear(); - adjacencyList = BuildGraph(); + adjacencyList = BuildGraphInternal(); } - private bool IsInBounds(int x, int y) => x >= 0 && y >= 0 && y < grid.GetLength(0) && x < grid.GetLength(1); + private bool IsInBounds(int x, int y) + { + return x >= 0 && y >= 0 && y < grid.GetLength(0) && x < grid.GetLength(1); + } private int GetRectangleWeight(int rectangleLabel) { - Rectangle rectangle = rectangleMap[rectangleLabel]; + if (!rectangleMap.TryGetValue(rectangleLabel, out Rectangle rectangle)) + { + return int.MaxValue / 4; + } + return Math.Max(1, weightGrid[rectangle.Y, rectangle.X]); } @@ -222,37 +235,14 @@ public class Pathfinder return new Vector2(closestX, closestY); } - private Dictionary> BuildGraphBaseline() + private Dictionary> BuildGraphInternal() { - Dictionary> graph = []; - - foreach (KeyValuePair rect1 in rectangleMap) - { - if (!graph.ContainsKey(rect1.Key)) - { - graph[rect1.Key] = []; - } - - foreach (KeyValuePair rect2 in rectangleMap) - { - if (rect1.Key != rect2.Key && AreRectanglesAdjacent(rect1.Value, rect2.Value)) - { - graph[rect1.Key].Add(rect2.Key); - } - } - } - - return graph; - } - - private Dictionary> BuildGraph() - { - var rectangleList = rectangleMap.ToList(); + List> rectangleList = rectangleMap.ToList(); int count = rectangleList.Count; // Pre-allocate dictionary with empty lists Dictionary> graph = []; - foreach (var (id, _) in rectangleList) + foreach ((int id, Rectangle _) in rectangleList) { graph[id] = []; } @@ -262,11 +252,11 @@ public class Pathfinder { for (int i = 0; i < count; i++) { - var (id1, rect1) = rectangleList[i]; + (int id1, Rectangle rect1) = rectangleList[i]; for (int j = i + 1; j < count; j++) { - var (id2, rect2) = rectangleList[j]; + (int id2, Rectangle rect2) = rectangleList[j]; // Quick bounding box check if (rect1.Right >= rect2.Left && rect2.Right >= rect1.Left && @@ -290,15 +280,15 @@ public class Pathfinder locks[i] = new object(); } - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; + ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; - Parallel.For(0, count, parallelOptions, i => + _ = Parallel.For(0, count, parallelOptions, i => { - var (id1, rect1) = rectangleList[i]; + (int id1, Rectangle rect1) = rectangleList[i]; for (int j = i + 1; j < count; j++) { - var (id2, rect2) = rectangleList[j]; + (int id2, Rectangle rect2) = rectangleList[j]; // Quick bounding box check if (rect1.Right >= rect2.Left && rect2.Right >= rect1.Left && @@ -331,15 +321,33 @@ public class Pathfinder return; } + // Capture incoming connections to affected zones before mutating adjacency lists. + Dictionary> incomingByAffected = []; + foreach (int zoneId in affectedZones) + { + incomingByAffected[zoneId] = []; + } + + foreach ((int zoneId, List neighbors) in adjacencyList) + { + foreach (int neighbor in neighbors) + { + if (incomingByAffected.TryGetValue(neighbor, out List? incoming)) + { + incoming.Add(zoneId); + } + } + } + // Remove zones that no longer exist in rectangleMap - var orphanedZones = adjacencyList.Keys.Where(z => !rectangleMap.ContainsKey(z) && !rectanglesSource.ContainsKey(z)).ToList(); + List orphanedZones = adjacencyList.Keys.Where(z => !rectangleMap.ContainsKey(z) && !rectanglesSource.ContainsKey(z)).ToList(); foreach (int zone in orphanedZones) { - adjacencyList.Remove(zone); + _ = adjacencyList.Remove(zone); } // Update rectangleMap with current state and collect all affected zones - HashSet allAffected = [..affectedZones]; + HashSet allAffected = [.. affectedZones]; foreach (int zoneId in affectedZones) { if (rectanglesSource.TryGetValue(zoneId, out Rectangle rect)) @@ -348,20 +356,17 @@ public class Pathfinder } else { - rectangleMap.Remove(zoneId); - adjacencyList.Remove(zoneId); + _ = rectangleMap.Remove(zoneId); + _ = adjacencyList.Remove(zoneId); } } - // Also collect zones that had connections to affected zones (they might need updates too) - foreach (int zoneId in affectedZones) + // Also collect zones that had connections to affected zones (they need adjacency refresh as well). + foreach ((int _, List incoming) in incomingByAffected) { - if (adjacencyList.TryGetValue(zoneId, out var neighbors)) + foreach (int neighbor in incoming) { - foreach (int neighbor in neighbors.ToList()) - { - allAffected.Add(neighbor); - } + _ = allAffected.Add(neighbor); } } @@ -374,34 +379,39 @@ public class Pathfinder } } - // Clear adjacencies for all affected zones AND collect zones that referenced affected zones + // Clear adjacencies for all affected zones foreach (int zoneId in allAffected) { adjacencyList[zoneId].Clear(); } - - // Also remove references FROM other zones TO affected zones - // (since those adjacencies will be recalculated if needed) - foreach (int otherZoneId in adjacencyList.Keys.ToList()) + + // Remove stale references to missing zones from all unaffected adjacency lists. + foreach ((int zoneId, List neighbors) in adjacencyList) { - if (!allAffected.Contains(otherZoneId)) + if (allAffected.Contains(zoneId)) { - adjacencyList[otherZoneId].RemoveAll(z => affectedZones.Contains(z)); + continue; } + + _ = neighbors.RemoveAll(n => !rectangleMap.ContainsKey(n)); } // Recalculate adjacencies between affected zones and all zones - var allZonesList = rectangleMap.ToList(); + List> allZonesList = rectangleMap.ToList(); foreach (int zoneId in allAffected) { if (!rectangleMap.TryGetValue(zoneId, out Rectangle rect1)) + { continue; + } - foreach (var (otherId, rect2) in allZonesList) + foreach ((int otherId, Rectangle rect2) in allZonesList) { if (otherId == zoneId) + { continue; + } if (rect1.Right >= rect2.Left && rect2.Right >= rect1.Left && rect1.Bottom >= rect2.Top && rect2.Bottom >= rect1.Top) @@ -409,9 +419,14 @@ public class Pathfinder if (AreRectanglesAdjacent(rect1, rect2)) { if (!adjacencyList[zoneId].Contains(otherId)) + { adjacencyList[zoneId].Add(otherId); + } + if (!adjacencyList[otherId].Contains(zoneId)) + { adjacencyList[otherId].Add(zoneId); + } } } }