From 8181cde647bcf5cc21db727d7853820edab80f2d Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue, 5 May 2026 18:02:19 +0200 Subject: [PATCH] Batch agent path calculation for improved performance --- .../LargeGridPathfindingGame.cs | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/LargeGridPathfinding/LargeGridPathfindingGame.cs b/src/LargeGridPathfinding/LargeGridPathfindingGame.cs index 9c50e9d..743f962 100644 --- a/src/LargeGridPathfinding/LargeGridPathfindingGame.cs +++ b/src/LargeGridPathfinding/LargeGridPathfindingGame.cs @@ -784,7 +784,8 @@ public class LargeGridPathfindingGame : Game } // Efficiently collect agents without paths (avoid LINQ allocation on hot path) - List agentsRequirePathList = new List(Math.Min(agents.Count, 16)); // pre-allocate reasonable capacity + List agentsRequirePathList = new List(agents.Count); + foreach (Agent agent in agents) { if (agent.Path is null) @@ -796,43 +797,46 @@ public class LargeGridPathfindingGame : Game if (agentsRequirePathList.Count != 0) { int agentCount = agentsRequirePathList.Count; + int batchSize = Math.Max(64, agentCount / (Environment.ProcessorCount * 4)); + ProgressTracker.ProgressData progressDataPaths = progressTracker.AddProgress($"Calculating {agentCount} paths", out IProgress progress); - // Use ParallelOptions to control concurrency ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; - // Batch progress reporting every 100ms instead of modulo checks long lastProgressReport = Environment.TickCount64; int pathsCalculated = 0; - _ = Parallel.ForEach(agentsRequirePathList, parallelOptions, agent => - { - // Calculate path once and reuse - List? path = CalculatePath(agent.GridPosition, agent.Destination) ?? CalculatePath(); - agent.Path = path; - - // Avoid repeated property access and null checks - if (path?.Count > 0) + _ = Parallel.ForEach(Partitioner.Create(0, agentCount, batchSize), parallelOptions, range => { - 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); - } + for (int i = range.Item1; i < range.Item2; i++) + { + Agent agent = agentsRequirePathList[i]; - // Batch progress updates to reduce lock contention - int local = Interlocked.Increment(ref pathsCalculated); - long now = Environment.TickCount64; - if (now - lastProgressReport > 100) - { - progress.Report((float)local / agentCount); - lastProgressReport = now; - } - }); + 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); + } + } + + int local = Interlocked.Add(ref pathsCalculated, range.Item2 - range.Item1); + long now = Environment.TickCount64; + if (now - lastProgressReport > 100) + { + progress.Report((float)local / agentCount); + _ = Interlocked.Exchange(ref lastProgressReport, now); + } + }); - // Report final progress progress.Report(1.0f); progressTracker.RemoveProgress(progressDataPaths); }