From 7ce2b5436f7ea06336848fdada9cc624aa245782 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue, 5 May 2026 01:28:28 +0200 Subject: [PATCH] Add batch processing for tile and obstacle operations --- src/LargeGridPathfinding/GridFiller.cs | 49 +++-- .../LargeGridPathfindingGame.cs | 167 ++++++++++++------ 2 files changed, 147 insertions(+), 69 deletions(-) diff --git a/src/LargeGridPathfinding/GridFiller.cs b/src/LargeGridPathfinding/GridFiller.cs index ecd5718..2ebd955 100644 --- a/src/LargeGridPathfinding/GridFiller.cs +++ b/src/LargeGridPathfinding/GridFiller.cs @@ -233,42 +233,63 @@ internal class GridFiller } public void RemoveObstacle(Rectangle rectangle) + { + RemoveObstacles([rectangle]); + } + + public void RemoveObstacles(IEnumerable rectangles) { lock (mutationLock) { - Rectangle clampedRectangle = ClampToGrid(rectangle); - if (clampedRectangle == Rectangle.Empty) + List clampedRectangles = [.. rectangles + .Select(ClampToGrid) + .Where(r => r != Rectangle.Empty)]; + + if (clampedRectangles.Count == 0) { return; } + int minX = Width; + int minY = Height; + int maxX = -1; + int maxY = -1; bool changed = false; - for (int dy = clampedRectangle.Top; dy < clampedRectangle.Bottom; dy++) + foreach (Rectangle rectangle in clampedRectangles) { - for (int dx = clampedRectangle.Left; dx < clampedRectangle.Right; dx++) + for (int dy = rectangle.Top; dy < rectangle.Bottom; dy++) { - if (Grid[dy, dx] < 0) + for (int dx = rectangle.Left; dx < rectangle.Right; dx++) { - Grid[dy, dx] = 0; - WeightGrid[dy, dx] = 1; - changed = true; + if (Grid[dy, dx] < 0) + { + changed = true; + } } } + + minX = Math.Min(minX, rectangle.Left); + minY = Math.Min(minY, rectangle.Top); + maxX = Math.Max(maxX, rectangle.Right); + maxY = Math.Max(maxY, rectangle.Bottom); } if (changed) { - RecalculateAroundArea(clampedRectangle.Left, clampedRectangle.Top, clampedRectangle.Right, clampedRectangle.Bottom, () => + RecalculateAroundArea(minX, minY, maxX, maxY, () => { - for (int dy = clampedRectangle.Top; dy < clampedRectangle.Bottom; dy++) + foreach (Rectangle rectangle in clampedRectangles) { - for (int dx = clampedRectangle.Left; dx < clampedRectangle.Right; dx++) + for (int dy = rectangle.Top; dy < rectangle.Bottom; dy++) { - if (Grid[dy, dx] < 0) + for (int dx = rectangle.Left; dx < rectangle.Right; dx++) { - Grid[dy, dx] = 0; - WeightGrid[dy, dx] = 1; + if (Grid[dy, dx] < 0) + { + Grid[dy, dx] = 0; + WeightGrid[dy, dx] = 1; + } } } } diff --git a/src/LargeGridPathfinding/LargeGridPathfindingGame.cs b/src/LargeGridPathfinding/LargeGridPathfindingGame.cs index 5ac14f7..6df2d6e 100644 --- a/src/LargeGridPathfinding/LargeGridPathfindingGame.cs +++ b/src/LargeGridPathfinding/LargeGridPathfindingGame.cs @@ -23,7 +23,19 @@ public class LargeGridPathfindingGame : Game Obstacle } + private enum PendingOperationKind + { + SetWeight, + ResetWeight, + PlaceObstacle, + RemoveObstacle + } + + private readonly record struct PendingOperation(PendingOperationKind Kind, int Weight); + private readonly ConcurrentDictionary temporaryIndicators = []; + private readonly Dictionary pendingOperations = []; + private readonly object pendingOperationsLock = new(); private readonly ProgressTracker progressTracker = new ProgressTracker(); private readonly List agents = []; private SpriteBatch spriteBatch = null!; @@ -40,6 +52,7 @@ public class LargeGridPathfindingGame : Game private BrushMode brushMode = BrushMode.Weight; private int paintWeight = 5; private Vector2? previousMousePosition; + private bool batchProcessingScheduled; public LargeGridPathfindingGame() { @@ -268,36 +281,19 @@ public class LargeGridPathfindingGame : Game Color indicatorColor = brushMode == BrushMode.Weight ? Color.Orange : Color.Red; BrushMode currentBrushMode = brushMode; int currentPaintWeight = paintWeight; - temporaryIndicators[gridPosition] = indicatorColor; - previousMousePosition = mousePosition; - _ = Task.Run(() => + List brushPoints = GetBrushPoints(gridPosition, previousGridPosition); + foreach (Point point in brushPoints) { - List brushPoints = GetBrushPoints(gridPosition, previousGridPosition); - foreach (Point point in brushPoints) - { - temporaryIndicators[point.ToVector2()] = indicatorColor; - } + temporaryIndicators[point.ToVector2()] = indicatorColor; + } - if (currentBrushMode == BrushMode.Weight) - { - gridFiller.SetTileWeights(brushPoints, currentPaintWeight); - } - else - { - List obstacleRectangles = [.. brushPoints.Select(point => new Rectangle(point.X, point.Y, 1, 1))]; - gridFiller.PlaceObstacles(obstacleRectangles); - } + PendingOperation pendingOperation = currentBrushMode == BrushMode.Weight + ? new PendingOperation(PendingOperationKind.SetWeight, currentPaintWeight) + : new PendingOperation(PendingOperationKind.PlaceObstacle, 0); - foreach (Point point in brushPoints) - { - _ = temporaryIndicators.TryRemove(point.ToVector2(), out _); - } - - gridChanged = true; - _ = temporaryIndicators.TryRemove(gridPosition, out _); - }); + EnqueuePendingOperations(brushPoints, pendingOperation); } else if (mouseState.IsButtonDown(MouseButton.Right)) { @@ -323,41 +319,19 @@ public class LargeGridPathfindingGame : Game Color indicatorColor = brushMode == BrushMode.Weight ? Color.LightGray : Color.Yellow; BrushMode currentBrushMode = brushMode; - temporaryIndicators[gridPosition] = indicatorColor; - previousMousePosition = mousePosition; - _ = Task.Run(() => + List brushPoints = GetBrushPoints(gridPosition, previousGridPosition); + foreach (Point point in brushPoints) { - List brushPoints = GetBrushPoints(gridPosition, previousGridPosition); - foreach (Point point in brushPoints) - { - temporaryIndicators[point.ToVector2()] = indicatorColor; - } + temporaryIndicators[point.ToVector2()] = indicatorColor; + } - if (currentBrushMode == BrushMode.Weight) - { - gridFiller.ResetTileWeights(brushPoints); - } - else - { - foreach (Point point in brushPoints) - { - if (point.X >= 0 && point.Y >= 0 && point.X < gridFiller.Width && point.Y < gridFiller.Height && gridFiller.Grid[point.Y, point.X] < 0) - { - gridFiller.RemoveObstacle(new Rectangle(point.X, point.Y, 1, 1)); - } - } - } + PendingOperation pendingOperation = currentBrushMode == BrushMode.Weight + ? new PendingOperation(PendingOperationKind.ResetWeight, 0) + : new PendingOperation(PendingOperationKind.RemoveObstacle, 0); - foreach (Point point in brushPoints) - { - _ = temporaryIndicators.TryRemove(point.ToVector2(), out _); - } - - gridChanged = true; - _ = temporaryIndicators.TryRemove(gridPosition, out _); - }); + EnqueuePendingOperations(brushPoints, pendingOperation); } else { @@ -712,6 +686,89 @@ public class LargeGridPathfindingGame : Game return pathfinder.FindPath(start.Value, goal.Value); } + private void EnqueuePendingOperations(IEnumerable points, PendingOperation operation) + { + lock (pendingOperationsLock) + { + foreach (Point point in points) + { + pendingOperations[point] = operation; + } + + if (batchProcessingScheduled) + { + return; + } + + batchProcessingScheduled = true; + } + + _ = Task.Run(ProcessPendingOperations); + } + + private void ProcessPendingOperations() + { + while (true) + { + KeyValuePair[] operationsBatch; + + lock (pendingOperationsLock) + { + if (pendingOperations.Count == 0) + { + batchProcessingScheduled = false; + return; + } + + operationsBatch = [.. pendingOperations]; + pendingOperations.Clear(); + } + + IGrouping[] weightGroups = [.. operationsBatch + .Where(op => op.Value.Kind == PendingOperationKind.SetWeight) + .GroupBy(op => op.Value.Weight, op => op.Key)]; + + Point[] resetWeightPoints = [.. operationsBatch + .Where(op => op.Value.Kind == PendingOperationKind.ResetWeight) + .Select(op => op.Key)]; + + Rectangle[] placeObstacleRectangles = [.. operationsBatch + .Where(op => op.Value.Kind == PendingOperationKind.PlaceObstacle) + .Select(op => new Rectangle(op.Key.X, op.Key.Y, 1, 1))]; + + Rectangle[] removeObstacleRectangles = [.. operationsBatch + .Where(op => op.Value.Kind == PendingOperationKind.RemoveObstacle) + .Select(op => new Rectangle(op.Key.X, op.Key.Y, 1, 1))]; + + foreach (IGrouping weightGroup in weightGroups) + { + gridFiller.SetTileWeights(weightGroup, weightGroup.Key); + } + + if (resetWeightPoints.Length > 0) + { + gridFiller.ResetTileWeights(resetWeightPoints); + } + + if (placeObstacleRectangles.Length > 0) + { + gridFiller.PlaceObstacles(placeObstacleRectangles); + } + + if (removeObstacleRectangles.Length > 0) + { + gridFiller.RemoveObstacles(removeObstacleRectangles); + } + + foreach (KeyValuePair operation in operationsBatch) + { + _ = temporaryIndicators.TryRemove(operation.Key.ToVector2(), out _); + } + + gridChanged = true; + } + } + private static List GetBrushPoints(Vector2 currentGridPosition, Vector2? previousGridPosition) { HashSet points = [];