mirror of
https://github.com/Stone-Red-Code/LargeGridPathfinding.git
synced 2026-09-04 09:06:12 +02:00
Add batch processing for tile and obstacle operations
This commit is contained in:
@@ -233,37 +233,57 @@ internal class GridFiller
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveObstacle(Rectangle rectangle)
|
public void RemoveObstacle(Rectangle rectangle)
|
||||||
|
{
|
||||||
|
RemoveObstacles([rectangle]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveObstacles(IEnumerable<Rectangle> rectangles)
|
||||||
{
|
{
|
||||||
lock (mutationLock)
|
lock (mutationLock)
|
||||||
{
|
{
|
||||||
Rectangle clampedRectangle = ClampToGrid(rectangle);
|
List<Rectangle> clampedRectangles = [.. rectangles
|
||||||
if (clampedRectangle == Rectangle.Empty)
|
.Select(ClampToGrid)
|
||||||
|
.Where(r => r != Rectangle.Empty)];
|
||||||
|
|
||||||
|
if (clampedRectangles.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int minX = Width;
|
||||||
|
int minY = Height;
|
||||||
|
int maxX = -1;
|
||||||
|
int maxY = -1;
|
||||||
bool changed = false;
|
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++)
|
||||||
|
{
|
||||||
|
for (int dx = rectangle.Left; dx < rectangle.Right; dx++)
|
||||||
{
|
{
|
||||||
if (Grid[dy, dx] < 0)
|
if (Grid[dy, dx] < 0)
|
||||||
{
|
{
|
||||||
Grid[dy, dx] = 0;
|
|
||||||
WeightGrid[dy, dx] = 1;
|
|
||||||
changed = true;
|
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)
|
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++)
|
||||||
|
{
|
||||||
|
for (int dx = rectangle.Left; dx < rectangle.Right; dx++)
|
||||||
{
|
{
|
||||||
if (Grid[dy, dx] < 0)
|
if (Grid[dy, dx] < 0)
|
||||||
{
|
{
|
||||||
@@ -272,6 +292,7 @@ internal class GridFiller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,19 @@ public class LargeGridPathfindingGame : Game
|
|||||||
Obstacle
|
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 ConcurrentDictionary<Vector2, Color> temporaryIndicators = [];
|
||||||
|
private readonly Dictionary<Point, PendingOperation> pendingOperations = [];
|
||||||
|
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 SpriteBatch spriteBatch = null!;
|
private SpriteBatch spriteBatch = null!;
|
||||||
@@ -40,6 +52,7 @@ public class LargeGridPathfindingGame : Game
|
|||||||
private BrushMode brushMode = BrushMode.Weight;
|
private BrushMode brushMode = BrushMode.Weight;
|
||||||
private int paintWeight = 5;
|
private int paintWeight = 5;
|
||||||
private Vector2? previousMousePosition;
|
private Vector2? previousMousePosition;
|
||||||
|
private bool batchProcessingScheduled;
|
||||||
|
|
||||||
public LargeGridPathfindingGame()
|
public LargeGridPathfindingGame()
|
||||||
{
|
{
|
||||||
@@ -268,36 +281,19 @@ public class LargeGridPathfindingGame : Game
|
|||||||
Color indicatorColor = brushMode == BrushMode.Weight ? Color.Orange : Color.Red;
|
Color indicatorColor = brushMode == BrushMode.Weight ? Color.Orange : Color.Red;
|
||||||
BrushMode currentBrushMode = brushMode;
|
BrushMode currentBrushMode = brushMode;
|
||||||
int currentPaintWeight = paintWeight;
|
int currentPaintWeight = paintWeight;
|
||||||
temporaryIndicators[gridPosition] = indicatorColor;
|
|
||||||
|
|
||||||
previousMousePosition = mousePosition;
|
previousMousePosition = mousePosition;
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
|
||||||
{
|
|
||||||
List<Point> brushPoints = GetBrushPoints(gridPosition, previousGridPosition);
|
List<Point> brushPoints = GetBrushPoints(gridPosition, previousGridPosition);
|
||||||
foreach (Point point in brushPoints)
|
foreach (Point point in brushPoints)
|
||||||
{
|
{
|
||||||
temporaryIndicators[point.ToVector2()] = indicatorColor;
|
temporaryIndicators[point.ToVector2()] = indicatorColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentBrushMode == BrushMode.Weight)
|
PendingOperation pendingOperation = currentBrushMode == BrushMode.Weight
|
||||||
{
|
? new PendingOperation(PendingOperationKind.SetWeight, currentPaintWeight)
|
||||||
gridFiller.SetTileWeights(brushPoints, currentPaintWeight);
|
: new PendingOperation(PendingOperationKind.PlaceObstacle, 0);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
List<Rectangle> obstacleRectangles = [.. brushPoints.Select(point => new Rectangle(point.X, point.Y, 1, 1))];
|
|
||||||
gridFiller.PlaceObstacles(obstacleRectangles);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Point point in brushPoints)
|
EnqueuePendingOperations(brushPoints, pendingOperation);
|
||||||
{
|
|
||||||
_ = temporaryIndicators.TryRemove(point.ToVector2(), out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
gridChanged = true;
|
|
||||||
_ = temporaryIndicators.TryRemove(gridPosition, out _);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else if (mouseState.IsButtonDown(MouseButton.Right))
|
else if (mouseState.IsButtonDown(MouseButton.Right))
|
||||||
{
|
{
|
||||||
@@ -323,41 +319,19 @@ public class LargeGridPathfindingGame : Game
|
|||||||
|
|
||||||
Color indicatorColor = brushMode == BrushMode.Weight ? Color.LightGray : Color.Yellow;
|
Color indicatorColor = brushMode == BrushMode.Weight ? Color.LightGray : Color.Yellow;
|
||||||
BrushMode currentBrushMode = brushMode;
|
BrushMode currentBrushMode = brushMode;
|
||||||
temporaryIndicators[gridPosition] = indicatorColor;
|
|
||||||
|
|
||||||
previousMousePosition = mousePosition;
|
previousMousePosition = mousePosition;
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
|
||||||
{
|
|
||||||
List<Point> brushPoints = GetBrushPoints(gridPosition, previousGridPosition);
|
List<Point> brushPoints = GetBrushPoints(gridPosition, previousGridPosition);
|
||||||
foreach (Point point in brushPoints)
|
foreach (Point point in brushPoints)
|
||||||
{
|
{
|
||||||
temporaryIndicators[point.ToVector2()] = indicatorColor;
|
temporaryIndicators[point.ToVector2()] = indicatorColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentBrushMode == BrushMode.Weight)
|
PendingOperation pendingOperation = currentBrushMode == BrushMode.Weight
|
||||||
{
|
? new PendingOperation(PendingOperationKind.ResetWeight, 0)
|
||||||
gridFiller.ResetTileWeights(brushPoints);
|
: new PendingOperation(PendingOperationKind.RemoveObstacle, 0);
|
||||||
}
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Point point in brushPoints)
|
EnqueuePendingOperations(brushPoints, pendingOperation);
|
||||||
{
|
|
||||||
_ = temporaryIndicators.TryRemove(point.ToVector2(), out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
gridChanged = true;
|
|
||||||
_ = temporaryIndicators.TryRemove(gridPosition, out _);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -712,6 +686,89 @@ public class LargeGridPathfindingGame : Game
|
|||||||
return pathfinder.FindPath(start.Value, goal.Value);
|
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)
|
private static List<Point> GetBrushPoints(Vector2 currentGridPosition, Vector2? previousGridPosition)
|
||||||
{
|
{
|
||||||
HashSet<Point> points = [];
|
HashSet<Point> points = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user