mirror of
https://github.com/Stone-Red-Code/LargeGridPathfinding.git
synced 2026-09-04 00:56:10 +02:00
Add thread safety and optimize grid obstacle, weight, and recalculation logic
This commit is contained in:
@@ -11,6 +11,8 @@ namespace LargeGridPathfinding;
|
||||
|
||||
internal class GridFiller
|
||||
{
|
||||
private const int RecalculationRadius = 24;
|
||||
private readonly object mutationLock = new();
|
||||
private int currentLabel = 1;
|
||||
private int currentObstacleLabel = -1;
|
||||
public int[,] Grid { get; }
|
||||
@@ -35,13 +37,12 @@ internal class GridFiller
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Rectangle rectangle in obstacles)
|
||||
{
|
||||
PlaceObstacle(rectangle);
|
||||
}
|
||||
PlaceObstacles(obstacles);
|
||||
}
|
||||
|
||||
public void FillGrid(int? x1 = null, int? y1 = null, int? x2 = null, int? y2 = null, bool fillAll = false, IProgress<float>? totalProgress = null, IProgress<float>? calculatingCandidatesProgress = null, IProgress<float>? placingCandidatesProgress = null)
|
||||
{
|
||||
lock (mutationLock)
|
||||
{
|
||||
x1 ??= 0;
|
||||
y1 ??= 0;
|
||||
@@ -63,13 +64,12 @@ internal class GridFiller
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all valid rectangle placements
|
||||
ConcurrentBag<(int x, int y, int w, int h, int weight)> candidates = [];
|
||||
|
||||
int cells = Grid.GetLength(0) * Grid.GetLength(1);
|
||||
int areaPlaced = 0;
|
||||
int maxRectangleSize = Math.Max((Grid.GetLength(0) + Grid.GetLength(1)) / 20, 100);
|
||||
int reportInterval = cells / 100;
|
||||
int reportInterval = Math.Max(cells / 100, 1);
|
||||
|
||||
do
|
||||
{
|
||||
@@ -77,7 +77,6 @@ internal class GridFiller
|
||||
|
||||
Debug.WriteLine("Calculating candidates...");
|
||||
|
||||
// Calculate all possible rectangle placements
|
||||
_ = Parallel.For(y1.Value, y2.Value, (y, loopState) =>
|
||||
{
|
||||
for (int x = x1.Value; x < x2; x++)
|
||||
@@ -110,7 +109,6 @@ internal class GridFiller
|
||||
Debug.WriteLine($"Candidates: {candidates.Count}");
|
||||
Debug.WriteLine("Sorting candidates...");
|
||||
|
||||
// Sort by area (largest first) and prefer less stretched rectangles on ties
|
||||
List<(int x, int y, int w, int h, int weight)> sortedCandidates = [.. candidates];
|
||||
sortedCandidates.Sort((a, b) =>
|
||||
{
|
||||
@@ -136,10 +134,8 @@ internal class GridFiller
|
||||
int placed = 0;
|
||||
int reportIntervalPlacing = Math.Max(sortedCandidates.Count / 10, 1);
|
||||
|
||||
// Place rectangles, ensuring no overlap
|
||||
foreach ((int x, int y, int w, int h, int weight) in sortedCandidates)
|
||||
{
|
||||
// Check if area is still free
|
||||
if (IsAreaFree(x, y, w, h, weight))
|
||||
{
|
||||
Rectangle rectangle = new Rectangle(x, y, w, h);
|
||||
@@ -164,92 +160,123 @@ internal class GridFiller
|
||||
calculatingCandidatesProgress?.Report(1);
|
||||
placingCandidatesProgress?.Report(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void PlaceObstacle(Rectangle rectangle)
|
||||
{
|
||||
// Check area around the rectangle to update all adjacent rectangles
|
||||
|
||||
int y = int.Clamp(rectangle.Y - 1, 0, Height);
|
||||
int x = int.Clamp(rectangle.X - 1, 0, Width);
|
||||
int h = int.Clamp(rectangle.Y + rectangle.Height + 2, 0, Height);
|
||||
int w = int.Clamp(rectangle.X + rectangle.Width + 2, 0, Width);
|
||||
|
||||
List<Rectangle> removedRectangles = [new Rectangle(x, y, w - x, h - y)];
|
||||
|
||||
int obstacle = currentObstacleLabel--;
|
||||
|
||||
for (int dy = y; dy < h; dy++)
|
||||
{
|
||||
for (int dx = x; dx < w; dx++)
|
||||
{
|
||||
if (Grid[dy, dx] > 0 && PlacedRectangles.ContainsKey(Grid[dy, dx]))
|
||||
{
|
||||
removedRectangles.Add(RemoveRectangle(Grid[dy, dx]));
|
||||
PlaceObstacles([rectangle]);
|
||||
}
|
||||
|
||||
if (Grid[dy, dx] >= 0 && dy < rectangle.Y + rectangle.Height && dx < rectangle.X + rectangle.Width && dy >= rectangle.Y && dx >= rectangle.X)
|
||||
public void PlaceObstacles(IEnumerable<Rectangle> rectangles)
|
||||
{
|
||||
lock (mutationLock)
|
||||
{
|
||||
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;
|
||||
|
||||
foreach (Rectangle rectangle in clampedRectangles)
|
||||
{
|
||||
for (int dy = rectangle.Top; dy < rectangle.Bottom; dy++)
|
||||
{
|
||||
for (int dx = rectangle.Left; dx < rectangle.Right; dx++)
|
||||
{
|
||||
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(minX, minY, maxX, maxY, () =>
|
||||
{
|
||||
foreach (Rectangle rectangle in clampedRectangles)
|
||||
{
|
||||
int obstacle = currentObstacleLabel--;
|
||||
|
||||
for (int dy = rectangle.Top; dy < rectangle.Bottom; dy++)
|
||||
{
|
||||
for (int dx = rectangle.Left; dx < rectangle.Right; dx++)
|
||||
{
|
||||
if (Grid[dy, dx] >= 0)
|
||||
{
|
||||
Grid[dy, dx] = obstacle;
|
||||
WeightGrid[dy, dx] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (removedRectangles.Count == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int minStartX = removedRectangles.Min(r => r.X);
|
||||
int minStartY = removedRectangles.Min(r => r.Y);
|
||||
int maxEndX = removedRectangles.Max(r => r.X + r.Width);
|
||||
int maxEndY = removedRectangles.Max(r => r.Y + r.Height);
|
||||
|
||||
FillGrid(minStartX, minStartY, maxEndX, maxEndY);
|
||||
|
||||
Debug.WriteLine("Placed obstacle");
|
||||
Debug.WriteLine($"Placed obstacle {rectangle.X}, {rectangle.Y}, {rectangle.Width}, {rectangle.Height}");
|
||||
Debug.WriteLine($"minX: {minStartX}, minY: {minStartY}, maxX: {maxEndX}, maxY: {maxEndY}");
|
||||
}
|
||||
|
||||
public void RemoveObstacle(Rectangle rectangle)
|
||||
{
|
||||
// Check area around the rectangle to update all adjacent rectangles
|
||||
|
||||
int y = int.Clamp(rectangle.Y - 1, 0, Height);
|
||||
int x = int.Clamp(rectangle.X - 1, 0, Width);
|
||||
int h = int.Clamp(rectangle.Y + rectangle.Height + 2, 0, Height);
|
||||
int w = int.Clamp(rectangle.X + rectangle.Width + 2, 0, Width);
|
||||
|
||||
List<Rectangle> removedRectangles = [new Rectangle(x, y, w - x, h - y)];
|
||||
|
||||
for (int dy = y; dy < h; dy++)
|
||||
lock (mutationLock)
|
||||
{
|
||||
for (int dx = x; dx < w; dx++)
|
||||
Rectangle clampedRectangle = ClampToGrid(rectangle);
|
||||
if (clampedRectangle == Rectangle.Empty)
|
||||
{
|
||||
if (Grid[dy, dx] > 0)
|
||||
{
|
||||
removedRectangles.Add(RemoveRectangle(Grid[dy, dx]));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Grid[dy, dx] < 0 && dy < rectangle.Y + rectangle.Height && dx < rectangle.X + rectangle.Width && dy >= rectangle.Y && dx >= rectangle.X)
|
||||
bool changed = false;
|
||||
|
||||
for (int dy = clampedRectangle.Top; dy < clampedRectangle.Bottom; dy++)
|
||||
{
|
||||
for (int dx = clampedRectangle.Left; dx < clampedRectangle.Right; dx++)
|
||||
{
|
||||
if (Grid[dy, dx] < 0)
|
||||
{
|
||||
Grid[dy, dx] = 0;
|
||||
WeightGrid[dy, dx] = 1;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
RecalculateAroundArea(clampedRectangle.Left, clampedRectangle.Top, clampedRectangle.Right, clampedRectangle.Bottom, () =>
|
||||
{
|
||||
for (int dy = clampedRectangle.Top; dy < clampedRectangle.Bottom; dy++)
|
||||
{
|
||||
for (int dx = clampedRectangle.Left; dx < clampedRectangle.Right; dx++)
|
||||
{
|
||||
if (Grid[dy, dx] < 0)
|
||||
{
|
||||
Grid[dy, dx] = 0;
|
||||
WeightGrid[dy, dx] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int minStartX = removedRectangles.Min(r => r.X);
|
||||
int minStartY = removedRectangles.Min(r => r.Y);
|
||||
int maxEndX = removedRectangles.Max(r => r.X + r.Width);
|
||||
int maxEndY = removedRectangles.Max(r => r.Y + r.Height);
|
||||
|
||||
FillGrid(minStartX, minStartY, maxEndX, maxEndY);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLine("Removed obstacle");
|
||||
Debug.WriteLine($"Placed obstacle {rectangle.X}, {rectangle.Y}, {rectangle.Width}, {rectangle.Height}");
|
||||
Debug.WriteLine($"minX: {minStartX}, minY: {minStartY}, maxX: {maxEndX}, maxY: {maxEndY}");
|
||||
}
|
||||
|
||||
public void SetTileWeight(int x, int y, int weight)
|
||||
@@ -263,14 +290,15 @@ internal class GridFiller
|
||||
}
|
||||
|
||||
public void SetTileWeights(IEnumerable<Point> points, int weight)
|
||||
{
|
||||
lock (mutationLock)
|
||||
{
|
||||
int clampedWeight = Math.Max(1, weight);
|
||||
HashSet<Point> changedPoints = [];
|
||||
|
||||
int minX = Width;
|
||||
int minY = Height;
|
||||
int maxX = -1;
|
||||
int maxY = -1;
|
||||
bool changed = false;
|
||||
|
||||
foreach (Point point in points)
|
||||
{
|
||||
@@ -279,49 +307,29 @@ internal class GridFiller
|
||||
continue;
|
||||
}
|
||||
|
||||
_ = changedPoints.Add(point);
|
||||
changed = true;
|
||||
minX = Math.Min(minX, point.X);
|
||||
minY = Math.Min(minY, point.Y);
|
||||
maxX = Math.Max(maxX, point.X);
|
||||
maxY = Math.Max(maxY, point.Y);
|
||||
maxX = Math.Max(maxX, point.X + 1);
|
||||
maxY = Math.Max(maxY, point.Y + 1);
|
||||
}
|
||||
|
||||
if (changedPoints.Count == 0)
|
||||
if (changed)
|
||||
{
|
||||
return;
|
||||
RecalculateAroundArea(minX, minY, maxX, maxY, () =>
|
||||
{
|
||||
foreach (Point point in points)
|
||||
{
|
||||
if (point.X < 0 || point.Y < 0 || point.X >= Width || point.Y >= Height || Grid[point.Y, point.X] < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int x = int.Clamp(minX - 1, 0, Width);
|
||||
int y = int.Clamp(minY - 1, 0, Height);
|
||||
int w = int.Clamp(maxX + 2, 0, Width);
|
||||
int h = int.Clamp(maxY + 2, 0, Height);
|
||||
|
||||
List<Rectangle> removedRectangles = [new Rectangle(x, y, w - x, h - y)];
|
||||
HashSet<int> removedLabels = [];
|
||||
|
||||
for (int dy = y; dy < h; dy++)
|
||||
{
|
||||
for (int dx = x; dx < w; dx++)
|
||||
{
|
||||
int label = Grid[dy, dx];
|
||||
if (label > 0 && removedLabels.Add(label))
|
||||
{
|
||||
removedRectangles.Add(RemoveRectangle(label));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Point point in changedPoints)
|
||||
{
|
||||
WeightGrid[point.Y, point.X] = clampedWeight;
|
||||
}
|
||||
|
||||
int minStartX = removedRectangles.Min(r => r.X);
|
||||
int minStartY = removedRectangles.Min(r => r.Y);
|
||||
int maxEndX = removedRectangles.Max(r => r.X + r.Width);
|
||||
int maxEndY = removedRectangles.Max(r => r.Y + r.Height);
|
||||
|
||||
FillGrid(minStartX, minStartY, maxEndX, maxEndY);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetTileWeights(IEnumerable<Point> points)
|
||||
@@ -329,14 +337,13 @@ internal class GridFiller
|
||||
SetTileWeights(points, 1);
|
||||
}
|
||||
|
||||
// Helper method to check if a rectangle can still be placed
|
||||
private bool IsAreaFree(int x, int y, int w, int h, int requiredWeight)
|
||||
{
|
||||
for (int dy = 0; dy < h; dy++)
|
||||
{
|
||||
for (int dx = 0; dx < w; dx++)
|
||||
{
|
||||
if (Grid[y + dy, x + dx] != 0 || WeightGrid[y + dy, x + dx] != requiredWeight) // Not empty or mismatched weight
|
||||
if (Grid[y + dy, x + dx] != 0 || WeightGrid[y + dy, x + dx] != requiredWeight)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -428,6 +435,73 @@ internal class GridFiller
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
private void RecalculateAroundArea(int minX, int minY, int maxX, int maxY, Action applyChanges)
|
||||
{
|
||||
int left = int.Clamp(minX - RecalculationRadius, 0, Width);
|
||||
int top = int.Clamp(minY - RecalculationRadius, 0, Height);
|
||||
int right = int.Clamp(maxX + RecalculationRadius, 0, Width);
|
||||
int bottom = int.Clamp(maxY + RecalculationRadius, 0, Height);
|
||||
|
||||
if (left >= right || top >= bottom)
|
||||
{
|
||||
applyChanges();
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<int> labelsToRemove = [];
|
||||
for (int y = top; y < bottom; y++)
|
||||
{
|
||||
for (int x = left; x < right; x++)
|
||||
{
|
||||
int label = Grid[y, x];
|
||||
if (label > 0)
|
||||
{
|
||||
_ = labelsToRemove.Add(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Rectangle> removedRectangles = [];
|
||||
foreach (int label in labelsToRemove)
|
||||
{
|
||||
Rectangle removed = RemoveRectangle(label);
|
||||
if (removed != Rectangle.Empty)
|
||||
{
|
||||
removedRectangles.Add(removed);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedRectangles.Count == 0)
|
||||
{
|
||||
applyChanges();
|
||||
FillGrid(left, top, right, bottom);
|
||||
return;
|
||||
}
|
||||
|
||||
int fillLeft = Math.Min(left, removedRectangles.Min(r => r.Left));
|
||||
int fillTop = Math.Min(top, removedRectangles.Min(r => r.Top));
|
||||
int fillRight = Math.Max(right, removedRectangles.Max(r => r.Right));
|
||||
int fillBottom = Math.Max(bottom, removedRectangles.Max(r => r.Bottom));
|
||||
|
||||
applyChanges();
|
||||
FillGrid(fillLeft, fillTop, fillRight, fillBottom);
|
||||
}
|
||||
|
||||
private Rectangle ClampToGrid(Rectangle rectangle)
|
||||
{
|
||||
int left = int.Clamp(rectangle.Left, 0, Width);
|
||||
int top = int.Clamp(rectangle.Top, 0, Height);
|
||||
int right = int.Clamp(rectangle.Right, 0, Width);
|
||||
int bottom = int.Clamp(rectangle.Bottom, 0, Height);
|
||||
|
||||
if (right <= left || bottom <= top)
|
||||
{
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
|
||||
return new Rectangle(left, top, right - left, bottom - top);
|
||||
}
|
||||
|
||||
private int GetRowContinuousWidth(int startX, int y, int maxWidth, int requiredWeight)
|
||||
{
|
||||
int width = 0;
|
||||
@@ -457,5 +531,4 @@ internal class GridFiller
|
||||
int maxSide = Math.Max(width, height);
|
||||
return maxSide / minSide;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -286,13 +286,8 @@ public class LargeGridPathfindingGame : Game
|
||||
}
|
||||
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.PlaceObstacle(new Rectangle(point.X, point.Y, 1, 1));
|
||||
}
|
||||
}
|
||||
List<Rectangle> obstacleRectangles = [.. brushPoints.Select(point => new Rectangle(point.X, point.Y, 1, 1))];
|
||||
gridFiller.PlaceObstacles(obstacleRectangles);
|
||||
}
|
||||
|
||||
foreach (Point point in brushPoints)
|
||||
|
||||
Reference in New Issue
Block a user