Fix graph update logic

This commit is contained in:
Stone_Red
2026-05-05 19:41:47 +02:00
parent 8181cde647
commit c755df7a16
2 changed files with 80 additions and 65 deletions
@@ -628,8 +628,8 @@ public class LargeGridPathfindingGame : Game
_ = Task.Run(() => _ = Task.Run(() =>
{ {
// Configuration options // Configuration options
int width = 1000; int width = 10000;
int height = 1000; int height = 10000;
int agentCount = 10000; int agentCount = 10000;
bool pathRandomization = false; // Randomize path costs to prevent agents from following the same path 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) // Full rebuild only if grid changed but no zones tracked (e.g., initial grid fill)
gridChanged = false; gridChanged = false;
ProgressTracker.ProgressData progressDataBuildGraph = progressTracker.AddProgress("Rebuilding graph", true, out _); ProgressTracker.ProgressData progressDataBuildGraph = progressTracker.AddProgress("Building graph", true, out _);
pathfinder.RebuildGraph(); pathfinder.BuildGraph();
progressTracker.RemoveProgress(progressDataBuildGraph); progressTracker.RemoveProgress(progressDataBuildGraph);
} }
+75 -60
View File
@@ -19,12 +19,18 @@ public class Pathfinder
private Dictionary<int, Rectangle> rectangleMap; private Dictionary<int, Rectangle> rectangleMap;
private Dictionary<int, List<int>> adjacencyList; private Dictionary<int, List<int>> adjacencyList;
public Dictionary<int, List<int>> GetAdjacencyList() => adjacencyList; public Dictionary<int, List<int>> GetAdjacencyList()
{
return adjacencyList;
}
public int GetGridValue(Point gridPoint) public int GetGridValue(Point gridPoint)
{ {
if (gridPoint.X < 0 || gridPoint.Y < 0 || gridPoint.X >= grid.GetLength(1) || gridPoint.Y >= grid.GetLength(0)) if (gridPoint.X < 0 || gridPoint.Y < 0 || gridPoint.X >= grid.GetLength(1) || gridPoint.Y >= grid.GetLength(0))
{
return 0; return 0;
}
return grid[gridPoint.Y, gridPoint.X]; return grid[gridPoint.Y, gridPoint.X];
} }
@@ -36,7 +42,7 @@ public class Pathfinder
this.pathRandomization = pathRandomization; this.pathRandomization = pathRandomization;
this.penalizeStretchedRectangles = penalizeStretchedRectangles; this.penalizeStretchedRectangles = penalizeStretchedRectangles;
rectangleMap = new Dictionary<int, Rectangle>(rectangles); rectangleMap = new Dictionary<int, Rectangle>(rectangles);
adjacencyList = BuildGraph(); adjacencyList = [];
} }
public List<Vector2>? FindPath(Point startPoint, Point goalPoint) public List<Vector2>? FindPath(Point startPoint, Point goalPoint)
@@ -82,9 +88,9 @@ public class Pathfinder
foreach (int neighbor in adjacencyList.TryGetValue(node, out List<int>? neighbors) ? neighbors : []) foreach (int neighbor in adjacencyList.TryGetValue(node, out List<int>? neighbors) ? neighbors : [])
{ {
if (!visited.Contains(neighbor)) if (!visited.Contains(neighbor) && rectangleMap.ContainsKey(neighbor))
{ {
List<int> newPath = new List<int>(path) { neighbor }; List<int> newPath = [.. path, neighbor];
int newCost = cost + GetRectangleWeight(neighbor); int newCost = cost + GetRectangleWeight(neighbor);
@@ -109,18 +115,25 @@ public class Pathfinder
return null; return null;
} }
public void RebuildGraph() public void BuildGraph()
{ {
rectangleMap = new Dictionary<int, Rectangle>(rectanglesSource); rectangleMap = new Dictionary<int, Rectangle>(rectanglesSource);
adjacencyList.Clear(); 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) 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]); return Math.Max(1, weightGrid[rectangle.Y, rectangle.X]);
} }
@@ -222,37 +235,14 @@ public class Pathfinder
return new Vector2(closestX, closestY); return new Vector2(closestX, closestY);
} }
private Dictionary<int, List<int>> BuildGraphBaseline() private Dictionary<int, List<int>> BuildGraphInternal()
{ {
Dictionary<int, List<int>> graph = []; List<KeyValuePair<int, Rectangle>> rectangleList = rectangleMap.ToList();
foreach (KeyValuePair<int, Rectangle> rect1 in rectangleMap)
{
if (!graph.ContainsKey(rect1.Key))
{
graph[rect1.Key] = [];
}
foreach (KeyValuePair<int, Rectangle> rect2 in rectangleMap)
{
if (rect1.Key != rect2.Key && AreRectanglesAdjacent(rect1.Value, rect2.Value))
{
graph[rect1.Key].Add(rect2.Key);
}
}
}
return graph;
}
private Dictionary<int, List<int>> BuildGraph()
{
var rectangleList = rectangleMap.ToList();
int count = rectangleList.Count; int count = rectangleList.Count;
// Pre-allocate dictionary with empty lists // Pre-allocate dictionary with empty lists
Dictionary<int, List<int>> graph = []; Dictionary<int, List<int>> graph = [];
foreach (var (id, _) in rectangleList) foreach ((int id, Rectangle _) in rectangleList)
{ {
graph[id] = []; graph[id] = [];
} }
@@ -262,11 +252,11 @@ public class Pathfinder
{ {
for (int i = 0; i < count; i++) 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++) for (int j = i + 1; j < count; j++)
{ {
var (id2, rect2) = rectangleList[j]; (int id2, Rectangle rect2) = rectangleList[j];
// Quick bounding box check // Quick bounding box check
if (rect1.Right >= rect2.Left && rect2.Right >= rect1.Left && if (rect1.Right >= rect2.Left && rect2.Right >= rect1.Left &&
@@ -290,15 +280,15 @@ public class Pathfinder
locks[i] = new object(); 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++) for (int j = i + 1; j < count; j++)
{ {
var (id2, rect2) = rectangleList[j]; (int id2, Rectangle rect2) = rectangleList[j];
// Quick bounding box check // Quick bounding box check
if (rect1.Right >= rect2.Left && rect2.Right >= rect1.Left && if (rect1.Right >= rect2.Left && rect2.Right >= rect1.Left &&
@@ -331,15 +321,33 @@ public class Pathfinder
return; return;
} }
// Capture incoming connections to affected zones before mutating adjacency lists.
Dictionary<int, List<int>> incomingByAffected = [];
foreach (int zoneId in affectedZones)
{
incomingByAffected[zoneId] = [];
}
foreach ((int zoneId, List<int> neighbors) in adjacencyList)
{
foreach (int neighbor in neighbors)
{
if (incomingByAffected.TryGetValue(neighbor, out List<int>? incoming))
{
incoming.Add(zoneId);
}
}
}
// Remove zones that no longer exist in rectangleMap // Remove zones that no longer exist in rectangleMap
var orphanedZones = adjacencyList.Keys.Where(z => !rectangleMap.ContainsKey(z) && !rectanglesSource.ContainsKey(z)).ToList(); List<int> orphanedZones = adjacencyList.Keys.Where(z => !rectangleMap.ContainsKey(z) && !rectanglesSource.ContainsKey(z)).ToList();
foreach (int zone in orphanedZones) foreach (int zone in orphanedZones)
{ {
adjacencyList.Remove(zone); _ = adjacencyList.Remove(zone);
} }
// Update rectangleMap with current state and collect all affected zones // Update rectangleMap with current state and collect all affected zones
HashSet<int> allAffected = [..affectedZones]; HashSet<int> allAffected = [.. affectedZones];
foreach (int zoneId in affectedZones) foreach (int zoneId in affectedZones)
{ {
if (rectanglesSource.TryGetValue(zoneId, out Rectangle rect)) if (rectanglesSource.TryGetValue(zoneId, out Rectangle rect))
@@ -348,20 +356,17 @@ public class Pathfinder
} }
else else
{ {
rectangleMap.Remove(zoneId); _ = rectangleMap.Remove(zoneId);
adjacencyList.Remove(zoneId); _ = adjacencyList.Remove(zoneId);
} }
} }
// Also collect zones that had connections to affected zones (they might need updates too) // Also collect zones that had connections to affected zones (they need adjacency refresh as well).
foreach (int zoneId in affectedZones) foreach ((int _, List<int> 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) foreach (int zoneId in allAffected)
{ {
adjacencyList[zoneId].Clear(); adjacencyList[zoneId].Clear();
} }
// Also remove references FROM other zones TO affected zones // Remove stale references to missing zones from all unaffected adjacency lists.
// (since those adjacencies will be recalculated if needed) foreach ((int zoneId, List<int> neighbors) in adjacencyList)
foreach (int otherZoneId in adjacencyList.Keys.ToList())
{ {
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 // Recalculate adjacencies between affected zones and all zones
var allZonesList = rectangleMap.ToList(); List<KeyValuePair<int, Rectangle>> allZonesList = rectangleMap.ToList();
foreach (int zoneId in allAffected) foreach (int zoneId in allAffected)
{ {
if (!rectangleMap.TryGetValue(zoneId, out Rectangle rect1)) if (!rectangleMap.TryGetValue(zoneId, out Rectangle rect1))
{
continue; continue;
}
foreach (var (otherId, rect2) in allZonesList) foreach ((int otherId, Rectangle rect2) in allZonesList)
{ {
if (otherId == zoneId) if (otherId == zoneId)
{
continue; continue;
}
if (rect1.Right >= rect2.Left && rect2.Right >= rect1.Left && if (rect1.Right >= rect2.Left && rect2.Right >= rect1.Left &&
rect1.Bottom >= rect2.Top && rect2.Bottom >= rect1.Top) rect1.Bottom >= rect2.Top && rect2.Bottom >= rect1.Top)
@@ -409,9 +419,14 @@ public class Pathfinder
if (AreRectanglesAdjacent(rect1, rect2)) if (AreRectanglesAdjacent(rect1, rect2))
{ {
if (!adjacencyList[zoneId].Contains(otherId)) if (!adjacencyList[zoneId].Contains(otherId))
{
adjacencyList[zoneId].Add(otherId); adjacencyList[zoneId].Add(otherId);
}
if (!adjacencyList[otherId].Contains(zoneId)) if (!adjacencyList[otherId].Contains(zoneId))
{
adjacencyList[otherId].Add(zoneId); adjacencyList[otherId].Add(zoneId);
}
} }
} }
} }