Move path assignment to main thread and refactor pathfinder to avoid memory allocations

This commit is contained in:
Stone_Red
2026-07-07 19:32:13 +02:00
parent ced5509983
commit 68253c38ce
2 changed files with 72 additions and 45 deletions
@@ -73,6 +73,7 @@ public class LargeGridPathfindingGame : Game
private readonly object pendingOperationsLock = new(); private readonly object pendingOperationsLock = new();
private readonly ProgressTracker progressTracker = new ProgressTracker(); private readonly ProgressTracker progressTracker = new ProgressTracker();
private readonly List<Agent> agents = []; private readonly List<Agent> agents = [];
private readonly ConcurrentQueue<(Agent agent, List<Vector2>? path)> pendingPathResults = new();
private readonly HashSet<int> affectedZones = []; private readonly HashSet<int> affectedZones = [];
private readonly object affectedZonesLock = new(); private readonly object affectedZonesLock = new();
private SpriteBatch spriteBatch = null!; private SpriteBatch spriteBatch = null!;
@@ -147,6 +148,22 @@ public class LargeGridPathfindingGame : Game
if (pathfinder is not null) if (pathfinder is not null)
{ {
Trace.WriteLine($"Pending path results: {pendingPathResults.Count}, Affected zones: {affectedZones.Count}, Pending operations: {pendingOperations.Count}");
// Drain pending path results — background thread enqueues, main thread applies
while (pendingPathResults.TryDequeue(out (Agent agent, List<Vector2>? path) result))
{
Agent agent = result.agent;
List<Vector2>? path = result.path;
agent.Path = path;
if (path?.Count > 0)
{
agent.Position = path[0];
agent.NextPosition = path.Count > 1 ? path[1] : path[0];
agent.Destination = new Point((int)path[^1].X, (int)path[^1].Y);
}
}
// Update agent positions // Update agent positions
foreach (Agent agent in agents) foreach (Agent agent in agents)
{ {
@@ -238,7 +255,7 @@ public class LargeGridPathfindingGame : Game
break; break;
case StartupMenuItem.AgentCount: case StartupMenuItem.AgentCount:
startupAgentCount = Math.Clamp(startupAgentCount + (direction * 100), 100, 200000); startupAgentCount = Math.Clamp(startupAgentCount + (direction * 100), 100, 1000000);
break; break;
case StartupMenuItem.MapPreset: case StartupMenuItem.MapPreset:
@@ -1008,7 +1025,7 @@ public class LargeGridPathfindingGame : Game
ParallelOptions parallelOptions = new ParallelOptions ParallelOptions parallelOptions = new ParallelOptions
{ {
MaxDegreeOfParallelism = Environment.ProcessorCount MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 3 * 2)
}; };
long lastProgressReport = Environment.TickCount64; long lastProgressReport = Environment.TickCount64;
@@ -1023,14 +1040,7 @@ public class LargeGridPathfindingGame : Game
List<Vector2>? path = CalculatePath(agent.GridPosition, agent.Destination) List<Vector2>? path = CalculatePath(agent.GridPosition, agent.Destination)
?? CalculatePath(); ?? CalculatePath();
agent.Path = path; pendingPathResults.Enqueue((agent, path));
if (path?.Count > 0)
{
agent.Position = path[0];
agent.NextPosition = path.Count > 1 ? path[1] : path[0];
agent.Destination = new Point((int)path[^1].X, (int)path[^1].Y);
}
} }
int local = Interlocked.Add(ref pathsCalculated, range.Item2 - range.Item1); int local = Interlocked.Add(ref pathsCalculated, range.Item2 - range.Item1);
@@ -1050,7 +1060,7 @@ public class LargeGridPathfindingGame : Game
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine(ex); Trace.WriteLine(ex);
} }
} }
}, TaskCreationOptions.LongRunning); }, TaskCreationOptions.LongRunning);
+51 -34
View File
@@ -75,55 +75,72 @@ public class Pathfinder
return null; return null;
} }
PriorityQueue<(int node, List<int> path, int cost), int> queue = new();
HashSet<int> visited = [];
queue.Enqueue((start, new List<int> { start }, 0), 0);
if (!rectangleMap.ContainsKey(start) || !rectangleMap.ContainsKey(goal)) if (!rectangleMap.ContainsKey(start) || !rectangleMap.ContainsKey(goal))
{ {
Debug.WriteLine($"{(rectangleMap.ContainsKey(start) ? "Goal" : "Start")} rectangle not found."); Debug.WriteLine($"{(rectangleMap.ContainsKey(start) ? "Goal" : "Start")} rectangle not found.");
return null; return null;
} }
while (queue.Count > 0) PriorityQueue<(int node, int cost), int> openSet = new();
Dictionary<int, int> gScore = [];
Dictionary<int, int> cameFrom = [];
gScore[start] = 0;
openSet.Enqueue((start, 0), 0);
while (openSet.Count > 0)
{ {
(int node, List<int> path, int cost) = queue.Dequeue(); (int current, int currentCost) = openSet.Dequeue();
if (node == goal) if (gScore.TryGetValue(current, out int bestG) && currentCost != bestG)
{
return BuildCoordinatePath(path, startPoint, goalPoint);
}
if (visited.Contains(node))
{ {
continue; continue;
} }
_ = visited.Add(node); if (current == goal)
foreach (int neighbor in adjacencyList.TryGetValue(node, out List<int>? neighbors) ? neighbors : [])
{ {
if (!visited.Contains(neighbor) && rectangleMap.ContainsKey(neighbor)) List<int> path = [];
int node = goal;
while (node != start)
{ {
List<int> newPath = [.. path, neighbor]; path.Add(node);
node = cameFrom[node];
int newCost = cost + GetRectangleWeight(neighbor);
if (penalizeStretchedRectangles)
{
Rectangle rectangle = rectangleMap[neighbor];
newCost += Math.Max(rectangle.Width, rectangle.Height) / Math.Min(rectangle.Width, rectangle.Height);
}
int priority = newCost + GetHeuristicCost(neighbor, goal);
if (pathRandomization)
{
priority += Random.Shared.Next(-1, 2);
}
queue.Enqueue((neighbor, newPath, newCost), priority);
} }
path.Add(start);
path.Reverse();
return BuildCoordinatePath(path, startPoint, goalPoint);
}
foreach (int neighbor in adjacencyList.TryGetValue(current, out List<int>? neighbors) ? neighbors : [])
{
if (!rectangleMap.ContainsKey(neighbor))
{
continue;
}
int tentativeG = currentCost + GetRectangleWeight(neighbor);
if (penalizeStretchedRectangles)
{
Rectangle rectangle = rectangleMap[neighbor];
tentativeG += Math.Max(rectangle.Width, rectangle.Height) / Math.Min(rectangle.Width, rectangle.Height);
}
if (gScore.TryGetValue(neighbor, out int existingG) && tentativeG >= existingG)
{
continue;
}
cameFrom[neighbor] = current;
gScore[neighbor] = tentativeG;
int priority = tentativeG + GetHeuristicCost(neighbor, goal);
if (pathRandomization)
{
priority += Random.Shared.Next(-1, 2);
}
openSet.Enqueue((neighbor, tentativeG), priority);
} }
} }