diff --git a/src/LargeGridPathfinding/LargeGridPathfindingGame.cs b/src/LargeGridPathfinding/LargeGridPathfindingGame.cs index 120b263..b172b73 100644 --- a/src/LargeGridPathfinding/LargeGridPathfindingGame.cs +++ b/src/LargeGridPathfinding/LargeGridPathfindingGame.cs @@ -73,6 +73,7 @@ public class LargeGridPathfindingGame : Game private readonly object pendingOperationsLock = new(); private readonly ProgressTracker progressTracker = new ProgressTracker(); private readonly List agents = []; + private readonly ConcurrentQueue<(Agent agent, List? path)> pendingPathResults = new(); private readonly HashSet affectedZones = []; private readonly object affectedZonesLock = new(); private SpriteBatch spriteBatch = null!; @@ -147,6 +148,22 @@ public class LargeGridPathfindingGame : Game 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? path) result)) + { + Agent agent = result.agent; + List? 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 foreach (Agent agent in agents) { @@ -238,7 +255,7 @@ public class LargeGridPathfindingGame : Game break; case StartupMenuItem.AgentCount: - startupAgentCount = Math.Clamp(startupAgentCount + (direction * 100), 100, 200000); + startupAgentCount = Math.Clamp(startupAgentCount + (direction * 100), 100, 1000000); break; case StartupMenuItem.MapPreset: @@ -1008,7 +1025,7 @@ public class LargeGridPathfindingGame : Game ParallelOptions parallelOptions = new ParallelOptions { - MaxDegreeOfParallelism = Environment.ProcessorCount + MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 3 * 2) }; long lastProgressReport = Environment.TickCount64; @@ -1023,14 +1040,7 @@ public class LargeGridPathfindingGame : Game List? path = CalculatePath(agent.GridPosition, agent.Destination) ?? CalculatePath(); - 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); - } + pendingPathResults.Enqueue((agent, path)); } int local = Interlocked.Add(ref pathsCalculated, range.Item2 - range.Item1); @@ -1050,7 +1060,7 @@ public class LargeGridPathfindingGame : Game } catch (Exception ex) { - Debug.WriteLine(ex); + Trace.WriteLine(ex); } } }, TaskCreationOptions.LongRunning); diff --git a/src/LargeGridPathfinding/Pathfinder.cs b/src/LargeGridPathfinding/Pathfinder.cs index cb6e1e9..fccabf9 100644 --- a/src/LargeGridPathfinding/Pathfinder.cs +++ b/src/LargeGridPathfinding/Pathfinder.cs @@ -75,55 +75,72 @@ public class Pathfinder return null; } - PriorityQueue<(int node, List path, int cost), int> queue = new(); - HashSet visited = []; - queue.Enqueue((start, new List { start }, 0), 0); - if (!rectangleMap.ContainsKey(start) || !rectangleMap.ContainsKey(goal)) { Debug.WriteLine($"{(rectangleMap.ContainsKey(start) ? "Goal" : "Start")} rectangle not found."); return null; } - while (queue.Count > 0) + PriorityQueue<(int node, int cost), int> openSet = new(); + Dictionary gScore = []; + Dictionary cameFrom = []; + + gScore[start] = 0; + openSet.Enqueue((start, 0), 0); + + while (openSet.Count > 0) { - (int node, List path, int cost) = queue.Dequeue(); + (int current, int currentCost) = openSet.Dequeue(); - if (node == goal) - { - return BuildCoordinatePath(path, startPoint, goalPoint); - } - - if (visited.Contains(node)) + if (gScore.TryGetValue(current, out int bestG) && currentCost != bestG) { continue; } - _ = visited.Add(node); - - foreach (int neighbor in adjacencyList.TryGetValue(node, out List? neighbors) ? neighbors : []) + if (current == goal) { - if (!visited.Contains(neighbor) && rectangleMap.ContainsKey(neighbor)) + List path = []; + int node = goal; + while (node != start) { - List newPath = [.. path, neighbor]; - - 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(node); + node = cameFrom[node]; } + path.Add(start); + path.Reverse(); + return BuildCoordinatePath(path, startPoint, goalPoint); + } + + foreach (int neighbor in adjacencyList.TryGetValue(current, out List? 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); } }