Add batch processing for tile and obstacle operations

This commit is contained in:
Stone_Red
2026-05-05 01:28:33 +02:00
parent 1b79459b36
commit 7ce2b5436f
2 changed files with 147 additions and 69 deletions
+35 -14
View File
@@ -233,42 +233,63 @@ internal class GridFiller
}
public void RemoveObstacle(Rectangle rectangle)
{
RemoveObstacles([rectangle]);
}
public void RemoveObstacles(IEnumerable<Rectangle> rectangles)
{
lock (mutationLock)
{
Rectangle clampedRectangle = ClampToGrid(rectangle);
if (clampedRectangle == Rectangle.Empty)
List<Rectangle> 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;
}
}
}
}
@@ -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<Vector2, Color> temporaryIndicators = [];
private readonly Dictionary<Point, PendingOperation> pendingOperations = [];
private readonly object pendingOperationsLock = new();
private readonly ProgressTracker progressTracker = new ProgressTracker();
private readonly List<Agent> 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<Point> brushPoints = GetBrushPoints(gridPosition, previousGridPosition);
foreach (Point point in brushPoints)
{
List<Point> 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<Rectangle> 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<Point> brushPoints = GetBrushPoints(gridPosition, previousGridPosition);
foreach (Point point in brushPoints)
{
List<Point> 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<Point> 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<Point, PendingOperation>[] operationsBatch;
lock (pendingOperationsLock)
{
if (pendingOperations.Count == 0)
{
batchProcessingScheduled = false;
return;
}
operationsBatch = [.. pendingOperations];
pendingOperations.Clear();
}
IGrouping<int, Point>[] 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<int, Point> 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<Point, PendingOperation> operation in operationsBatch)
{
_ = temporaryIndicators.TryRemove(operation.Key.ToVector2(), out _);
}
gridChanged = true;
}
}
private static List<Point> GetBrushPoints(Vector2 currentGridPosition, Vector2? previousGridPosition)
{
HashSet<Point> points = [];