Remove FunctionalTests and add advanced benchmarks, tests for GridFiller and optimize GridFiller

This commit is contained in:
Stone_Red
2026-05-05 12:06:56 +02:00
parent a27346b81c
commit 2216bac568
3 changed files with 206 additions and 186 deletions
-180
View File
@@ -1,180 +0,0 @@
using LargeGridPathfinding;
using Microsoft.Xna.Framework;
public class GridFillerFunctionalTests
{
public static void Main()
{
Console.WriteLine("Running GridFiller Functional Tests...\n");
TestBasicGridFill();
TestPartialGridFill();
TestObstaclePlacement();
TestObstacleRemoval();
TestWeightAdjustment();
TestRectanglePlacement();
Console.WriteLine("\n✅ All functional tests passed!");
}
private static void TestBasicGridFill()
{
Console.WriteLine("Test 1: Basic Grid Fill");
var filler = new GridFiller(100, 100);
filler.FillGrid(fillAll: true);
// Check that grid has been populated with valid labels
int filledCells = 0;
for (int y = 0; y < 100; y++)
{
for (int x = 0; x < 100; x++)
{
if (filler.Grid[y, x] > 0)
filledCells++;
}
}
Console.WriteLine($" Filled cells: {filledCells}/10000");
Assert(filledCells > 0, "Grid should have filled cells");
Console.WriteLine(" ✓ Passed\n");
}
private static void TestPartialGridFill()
{
Console.WriteLine("Test 2: Partial Grid Fill");
var filler = new GridFiller(100, 100);
filler.FillGrid(0, 0, 50, 50, fillAll: true);
// Check that only the partial region has been filled
int filledInRegion = 0;
int filledOutsideRegion = 0;
for (int y = 0; y < 100; y++)
{
for (int x = 0; x < 100; x++)
{
if (filler.Grid[y, x] > 0)
{
if (x < 50 && y < 50)
filledInRegion++;
else
filledOutsideRegion++;
}
}
}
Console.WriteLine($" Filled in region (0,0,50,50): {filledInRegion}");
Console.WriteLine($" Filled outside region: {filledOutsideRegion}");
Assert(filledInRegion > 0, "Region should have filled cells");
Assert(filledOutsideRegion == 0, "Outside region should be empty");
Console.WriteLine(" ✓ Passed\n");
}
private static void TestObstaclePlacement()
{
Console.WriteLine("Test 3: Obstacle Placement");
var filler = new GridFiller(100, 100);
var obstacleRect = new Rectangle(10, 10, 20, 20);
filler.PlaceObstacle(obstacleRect);
// Check that obstacle has negative labels
int obstaclesPlaced = 0;
for (int y = 10; y < 30; y++)
{
for (int x = 10; x < 30; x++)
{
if (filler.Grid[y, x] < 0)
obstaclesPlaced++;
}
}
Console.WriteLine($" Obstacles placed: {obstaclesPlaced}/400");
Assert(obstaclesPlaced == 400, "Obstacle should cover entire rectangle");
Console.WriteLine(" ✓ Passed\n");
}
private static void TestObstacleRemoval()
{
Console.WriteLine("Test 4: Obstacle Removal");
var filler = new GridFiller(100, 100);
var obstacleRect = new Rectangle(10, 10, 20, 20);
filler.PlaceObstacle(obstacleRect);
filler.RemoveObstacle(obstacleRect);
// Check that obstacle has been removed
int obstaclesRemaining = 0;
for (int y = 10; y < 30; y++)
{
for (int x = 10; x < 30; x++)
{
if (filler.Grid[y, x] < 0)
obstaclesRemaining++;
}
}
Console.WriteLine($" Obstacles remaining: {obstaclesRemaining}");
Assert(obstaclesRemaining == 0, "Obstacle should be completely removed");
Console.WriteLine(" ✓ Passed\n");
}
private static void TestWeightAdjustment()
{
Console.WriteLine("Test 5: Weight Adjustment");
var filler = new GridFiller(100, 100);
var points = new List<Point> { new(10, 10), new(11, 11) };
filler.SetTileWeights(points, 5);
Assert(filler.WeightGrid[10, 10] == 5, "Weight at (10,10) should be 5");
Assert(filler.WeightGrid[11, 11] == 5, "Weight at (11,11) should be 5");
filler.ResetTileWeights(points);
Assert(filler.WeightGrid[10, 10] == 1, "Weight should be reset to 1");
Assert(filler.WeightGrid[11, 11] == 1, "Weight should be reset to 1");
Console.WriteLine(" ✓ Passed\n");
}
private static void TestRectanglePlacement()
{
Console.WriteLine("Test 6: Rectangle Placement Tracking");
var filler = new GridFiller(100, 100);
filler.FillGrid(fillAll: true);
// Check that PlacedRectangles collection is populated
int placedCount = filler.PlacedRectangles.Count;
Console.WriteLine($" Rectangles placed: {placedCount}");
Assert(placedCount > 0, "Should have placed rectangles");
// Verify each rectangle in the collection matches the grid
foreach (var kvp in filler.PlacedRectangles)
{
int label = kvp.Key;
var rect = kvp.Value;
// Check a few cells from this rectangle
for (int y = rect.Top; y < Math.Min(rect.Top + 2, rect.Bottom); y++)
{
for (int x = rect.Left; x < Math.Min(rect.Left + 2, rect.Right); x++)
{
Assert(filler.Grid[y, x] == label, $"Grid cell ({x},{y}) should have label {label}");
}
}
}
Console.WriteLine(" ✓ Passed\n");
}
private static void Assert(bool condition, string message)
{
if (!condition)
{
throw new Exception($"Assertion failed: {message}");
}
}
}
+159
View File
@@ -3,11 +3,60 @@ using BenchmarkDotNet.Running;
using LargeGridPathfinding;
using Microsoft.Xna.Framework;
static int CountZones(GridFiller filler)
{
int width = filler.Width;
int height = filler.Height;
bool[,] visited = new bool[height, width];
int zoneCount = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (!visited[y, x] && filler.Grid[y, x] > 0)
{
// BFS to find all cells in this zone
var queue = new Queue<(int, int)>();
queue.Enqueue((x, y));
visited[y, x] = true;
while (queue.Count > 0)
{
var (cx, cy) = queue.Dequeue();
int zoneId = filler.Grid[cy, cx];
// Check 4 neighbors
foreach (var (dx, dy) in new[] { (0, 1), (0, -1), (1, 0), (-1, 0) })
{
int nx = cx + dx;
int ny = cy + dy;
if (nx >= 0 && nx < width && ny >= 0 && ny < height &&
!visited[ny, nx] && filler.Grid[ny, nx] == zoneId)
{
visited[ny, nx] = true;
queue.Enqueue((nx, ny));
}
}
}
zoneCount++;
}
}
}
return zoneCount;
}
// Check if we should run tests or benchmarks
if (args.Length > 0 && args[0] == "--test")
{
RunFunctionalTests();
}
else if (args.Length > 0 && args[0] == "--advanced")
{
BenchmarkRunner.Run<AdvancedGridFillerBenchmarks>();
}
else
{
BenchmarkRunner.Run<GridFillerBenchmarks>();
@@ -21,6 +70,8 @@ static void RunFunctionalTests()
TestObstaclePlacement();
TestObstacleRemoval();
TestWeightAdjustment();
TestZoneCount();
TestLargerGrids();
Console.WriteLine("\n✅ All functional tests passed!");
}
@@ -112,6 +163,53 @@ static void TestWeightAdjustment()
Console.WriteLine(" ✓ Passed\n");
}
static void TestZoneCount()
{
Console.WriteLine("Test 5: Zone Count (512x512 grid)");
var filler = new GridFiller(512, 512);
filler.FillGrid(fillAll: true);
int zoneCount = CountZones(filler);
Console.WriteLine($" Zones created: {zoneCount}");
Console.WriteLine($" ✓ Passed\n");
}
static void TestLargerGrids()
{
Console.WriteLine("Test 6: Larger Grids Zone Count (3 runs each)\n");
int[] gridSizes = { 512, 1024, 2048 };
int runsPerSize = 3;
foreach (int size in gridSizes)
{
Console.WriteLine($" Testing {size}×{size} grid ({size * size:N0} cells):");
int[] zoneCounts = new int[runsPerSize];
long[] times = new long[runsPerSize];
for (int run = 0; run < runsPerSize; run++)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
var filler = new GridFiller(size, size);
filler.FillGrid(fillAll: true);
sw.Stop();
zoneCounts[run] = CountZones(filler);
times[run] = sw.ElapsedMilliseconds;
}
double avgZones = zoneCounts.Average();
double avgTime = times.Average();
Console.WriteLine($" Zones: {zoneCounts[0]}, {zoneCounts[1]}, {zoneCounts[2]} (avg: {avgZones:F1})");
Console.WriteLine($" Times: {times[0]}ms, {times[1]}ms, {times[2]}ms (avg: {avgTime:F0}ms)\n");
}
Console.WriteLine(" ✓ Passed\n");
}
[MemoryDiagnoser]
public class GridFillerBenchmarks
{
@@ -172,3 +270,64 @@ public class GridFillerBenchmarks
gridFiller.ResetTileWeights(points);
}
}
[MemoryDiagnoser]
public class AdvancedGridFillerBenchmarks
{
private GridFiller gridFiller = null!;
private const int GridSize = 512;
[GlobalSetup]
public void Setup()
{
gridFiller = new GridFiller(GridSize, GridSize);
}
[Benchmark(Description = "GetRowContinuousWidth - 10 calls")]
public int BenchmarkGetRowWidth()
{
int result = 0;
for (int i = 0; i < 10; i++)
{
result += gridFiller.GetRowContinuousWidthPublic(10 + i, 10, 100, 1);
}
return result;
}
[Benchmark(Description = "IsAreaFree - 100x100 check")]
public bool BenchmarkIsAreaFree()
{
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 10; x++)
{
if (x < 5 && y < 5)
{
var result = gridFiller.IsAreaFreePublic(x * 50, y * 50, 50, 50, 1);
if (!result) return false;
}
}
}
return true;
}
[Benchmark(Description = "Stretch factor calculation - 1000 iterations")]
public int BenchmarkStretchFactor()
{
int result = 0;
for (int w = 10; w < 100; w += 3)
{
for (int h = 10; h < 100; h += 3)
{
result += gridFiller.GetStretchFactorPublic(w, h);
}
}
return result;
}
[Benchmark(Description = "FillGrid - Full grid (Advanced)")]
public void FillGridFullAdvanced()
{
gridFiller.FillGrid(fillAll: true);
}
}
+47 -6
View File
@@ -75,9 +75,12 @@ public class GridFiller
Debug.WriteLine("Calculating candidates...");
_ = Parallel.For(y1.Value, y2.Value, (y, loopState) =>
ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
_ = Parallel.For(y1.Value, y2.Value, parallelOptions, (y, loopState) =>
{
for (int x = x1.Value; x < x2; x++)
int x = x1.Value;
while (x < x2)
{
if (Grid[y, x] == 0)
{
@@ -87,6 +90,11 @@ public class GridFiller
if (w > 0 && h > 0)
{
candidates.Add((x, y, w, h, tileWeight));
x += w;
}
else
{
x++;
}
if (w >= maxRectangleSize && h >= maxRectangleSize)
@@ -101,14 +109,24 @@ public class GridFiller
Debug.WriteLine($"Candidates Progress: {candidates.Count} / {cells - areaPlaced} ({(float)candidates.Count / (cells - areaPlaced):P2})");
}
}
else
{
x++;
}
}
});
Debug.WriteLine($"Candidates: {candidates.Count}");
if (candidates.Count == 0)
{
break;
}
Debug.WriteLine("Sorting candidates...");
var sortedCandidates = new List<(int x, int y, int w, int h, int weight)>(candidates.Count);
sortedCandidates.AddRange(candidates);
List<(int x, int y, int w, int h, int weight)> sortedCandidates = [.. candidates];
sortedCandidates.Sort((a, b) =>
{
int areaComparison = (b.w * b.h).CompareTo(a.w * a.h);
@@ -397,6 +415,8 @@ public class GridFiller
int bestHeight = 0;
int bestArea = 0;
int bestStretch = int.MaxValue;
int noImprovementCount = 0;
const int maxNoImprovement = 5;
for (int h = 1; h <= maxHeight; h++)
{
@@ -422,10 +442,15 @@ public class GridFiller
bestWidth = currentWidth;
bestHeight = h;
bestStretch = stretch;
noImprovementCount = 0;
}
else if (area < bestArea && h > bestHeight * 2)
else
{
break;
noImprovementCount++;
if (noImprovementCount >= maxNoImprovement)
{
//break;
}
}
}
@@ -566,4 +591,20 @@ public class GridFiller
int maxSide = Math.Max(width, height);
return maxSide / minSide;
}
// Public methods for benchmarking
public int GetRowContinuousWidthPublic(int startX, int y, int maxWidth, int requiredWeight)
{
return GetRowContinuousWidth(startX, y, maxWidth, requiredWeight);
}
public bool IsAreaFreePublic(int x, int y, int w, int h, int requiredWeight)
{
return IsAreaFree(x, y, w, h, requiredWeight);
}
public int GetStretchFactorPublic(int width, int height)
{
return GetStretchFactor(width, height);
}
}