diff --git a/.gitignore b/.gitignore
index 81ee42b..372f0f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
-# Ignore Build/Benchmarks.
-/src/AdvChkSys.Benchmarks
+# Ignore Build
/docfx_project
/src/AdvChkSys/bin
/src/AdvChkSys/obj
src/bindings/python/__pycache__/
+/src/AdvChkSys.Benchmarks/bin
+/src/AdvChkSys.Benchmarks/obj
diff --git a/src/AdvChkSys.Benchmarks/AdvChkSys.Benchmarks.csproj b/src/AdvChkSys.Benchmarks/AdvChkSys.Benchmarks.csproj
new file mode 100644
index 0000000..5257abc
--- /dev/null
+++ b/src/AdvChkSys.Benchmarks/AdvChkSys.Benchmarks.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net9.0
+ ChunkMark
+ latest
+ enable
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AdvChkSys.Benchmarks/ChunkMark.cs b/src/AdvChkSys.Benchmarks/ChunkMark.cs
new file mode 100644
index 0000000..9e763a1
--- /dev/null
+++ b/src/AdvChkSys.Benchmarks/ChunkMark.cs
@@ -0,0 +1,720 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Text;
+using AdvChkSys;
+using AdvChkSys.Chunk;
+using AdvChkSys.Manager;
+using AdvChkSys.Constraints;
+using AdvChkSys.Diagnostics;
+
+namespace ChunkMark
+{
+ public static class Program
+ {
+ private static readonly ConsoleColor _headerColor = ConsoleColor.Cyan;
+ private static readonly ConsoleColor _resultColor = ConsoleColor.Green;
+ private static readonly ConsoleColor _memoryColor = ConsoleColor.Yellow;
+ private static readonly ConsoleColor _errorColor = ConsoleColor.Red;
+ private static readonly ConsoleColor _infoColor = ConsoleColor.Gray;
+
+ private static List _results = new List();
+ private static bool _verboseMode = false;
+
+ public static void Main(string[] args)
+ {
+ int cpuCount = Environment.ProcessorCount;
+ int num2DChunks = 10000;
+ int chunk2DSize = 32;
+ int num3DChunks = 500;
+ int chunk3DWidth = 16, chunk3DHeight = 16, chunk3DDepth = 16;
+ int maxLoaded = 100000;
+ bool run2D = true, run3D = true;
+ bool runMemoryTest = false;
+ bool runStressTest = false;
+
+ // Parse args
+ foreach (var arg in args)
+ {
+ if (arg.StartsWith("--cpus=")) cpuCount = int.Parse(arg.Substring(7));
+ if (arg.StartsWith("--2d-chunks=")) num2DChunks = int.Parse(arg.Substring(12));
+ if (arg.StartsWith("--2d-size=")) chunk2DSize = int.Parse(arg.Substring(10));
+ if (arg.StartsWith("--3d-chunks=")) num3DChunks = int.Parse(arg.Substring(12));
+ if (arg.StartsWith("--3d-width=")) chunk3DWidth = int.Parse(arg.Substring(11));
+ if (arg.StartsWith("--3d-height=")) chunk3DHeight = int.Parse(arg.Substring(12));
+ if (arg.StartsWith("--3d-depth=")) chunk3DDepth = int.Parse(arg.Substring(11));
+ if (arg.StartsWith("--3d-size="))
+ {
+ int val = int.Parse(arg.Substring(10));
+ chunk3DWidth = chunk3DHeight = chunk3DDepth = val;
+ }
+ if (arg.StartsWith("--max-loaded=")) maxLoaded = int.Parse(arg.Substring(13));
+ if (arg == "--2d-only") { run3D = false; }
+ if (arg == "--3d-only") { run2D = false; }
+ if (arg == "--memory-test") { runMemoryTest = true; }
+ if (arg == "--stress-test") { runStressTest = true; }
+ if (arg == "--verbose") { _verboseMode = true; }
+ }
+
+ PrintHeader();
+
+ if (run2D)
+ Benchmark2D(cpuCount, num2DChunks, chunk2DSize, maxLoaded);
+
+ if (run3D)
+ {
+ while (true)
+ {
+ if (!CanAllocateChunks(num3DChunks, chunk3DWidth, chunk3DHeight, chunk3DDepth, 0.82))
+ {
+ long bytesPerChunk = (long)chunk3DWidth * chunk3DHeight * chunk3DDepth;
+ long totalBytes = bytesPerChunk * num3DChunks;
+ long available = GetAvailableMemoryBytes();
+ long safeLimit = (long)(available * 0.82);
+
+ Console.ForegroundColor = _errorColor;
+ Console.WriteLine($"ERROR: Requested 3D chunks would use {totalBytes / (1024 * 1024):N0} MB, but only {safeLimit / (1024 * 1024):N0} MB is safely available.");
+ Console.WriteLine("Please input new values for 3D chunks and size within your system's memory capacity.");
+ Console.ResetColor();
+
+ Console.ForegroundColor = _infoColor; // Using _infoColor here
+ Console.Write("Enter number of 3D chunks (or blank to exit): ");
+ Console.ResetColor();
+
+ var chunksInput = Console.ReadLine();
+ if (string.IsNullOrWhiteSpace(chunksInput))
+ {
+ Console.WriteLine("Exiting benchmark.");
+ break;
+ }
+ if (!int.TryParse(chunksInput, out num3DChunks) || num3DChunks <= 0)
+ {
+ Console.WriteLine("Invalid input. Exiting.");
+ break;
+ }
+
+ Console.ForegroundColor = _infoColor; // Using _infoColor here
+ Console.Write("Enter 3D chunk width: ");
+ Console.ResetColor();
+
+ var widthInput = Console.ReadLine();
+ if (string.IsNullOrWhiteSpace(widthInput) || !int.TryParse(widthInput, out chunk3DWidth) || chunk3DWidth <= 0)
+ {
+ Console.WriteLine("Invalid input. Exiting.");
+ break;
+ }
+
+ Console.ForegroundColor = _infoColor; // Using _infoColor here
+ Console.Write("Enter 3D chunk height: ");
+ Console.ResetColor();
+
+ var heightInput = Console.ReadLine();
+ if (string.IsNullOrWhiteSpace(heightInput) || !int.TryParse(heightInput, out chunk3DHeight) || chunk3DHeight <= 0)
+ {
+ Console.WriteLine("Invalid input. Exiting.");
+ break;
+ }
+
+ Console.ForegroundColor = _infoColor; // Using _infoColor here
+ Console.Write("Enter 3D chunk depth: ");
+ Console.ResetColor();
+
+ var depthInput = Console.ReadLine();
+ if (string.IsNullOrWhiteSpace(depthInput) || !int.TryParse(depthInput, out chunk3DDepth) || chunk3DDepth <= 0)
+ {
+ Console.WriteLine("Invalid input. Exiting.");
+ break;
+ }
+ Console.WriteLine();
+ // Loop will re-check with new values
+ }
+ else
+ {
+ try
+ {
+ Benchmark3D(cpuCount, num3DChunks, chunk3DWidth, chunk3DHeight, chunk3DDepth, maxLoaded);
+ }
+ catch (OutOfMemoryException)
+ {
+ Console.ForegroundColor = _errorColor;
+ Console.WriteLine("ERROR: Out of memory during 3D chunk allocation. Reduce chunk size or count.");
+ Console.ResetColor();
+ }
+ break;
+ }
+ }
+ }
+
+ if (runMemoryTest)
+ {
+ RunMemoryTest(cpuCount, chunk2DSize, chunk3DWidth, chunk3DHeight, chunk3DDepth);
+ }
+
+ if (runStressTest)
+ {
+ RunStressTest(cpuCount, chunk2DSize, chunk3DWidth, chunk3DHeight, chunk3DDepth);
+ }
+
+ PrintSummary();
+
+ Console.ForegroundColor = _infoColor; // Using _infoColor here
+ Console.WriteLine("\nDone. Press any key to exit.");
+ Console.ResetColor();
+ Console.ReadKey();
+ }
+
+ private static void PrintHeader()
+ {
+ Console.ForegroundColor = _headerColor;
+ Console.WriteLine("╔═══════════════════════════════════════════════════════════════╗");
+ Console.WriteLine("║ ChunkMark: AdvChkSys Benchmark ║");
+ Console.WriteLine("╚═══════════════════════════════════════════════════════════════╝");
+ Console.ResetColor();
+
+ Console.WriteLine($"Library version: {AdvChkSys.AdvChkSys.Version}");
+ Console.WriteLine($"CPU threads: {Environment.ProcessorCount}");
+ Console.WriteLine($"System memory: {FormatByteSize(AdvChkSys.AdvChkSys.GetMemoryUsage().TotalSystemMemoryBytes)}");
+ Console.WriteLine($"Available memory: {FormatByteSize(AdvChkSys.AdvChkSys.GetMemoryUsage().AvailableSystemMemoryBytes)}");
+ Console.WriteLine();
+ }
+
+ private static void PrintSummary()
+ {
+ Console.WriteLine();
+ Console.ForegroundColor = _headerColor;
+ Console.WriteLine("╔═══════════════════════════════════════════════════════════════╗");
+ Console.WriteLine("║ Benchmark Summary ║");
+ Console.WriteLine("╚═══════════════════════════════════════════════════════════════╝");
+ Console.ResetColor();
+
+ if (_results.Count == 0)
+ {
+ Console.WriteLine("No benchmark results to display.");
+ return;
+ }
+
+ var table = new StringBuilder();
+ table.AppendLine("┌────────────────┬─────────────┬────────────┬────────────┬────────────┐");
+ table.AppendLine("│ Test │ Chunks │ Create │ Access │ Unload │");
+ table.AppendLine("├────────────────┼─────────────┼────────────┼────────────┼────────────┤");
+
+ foreach (var result in _results)
+ {
+ string testName = result.TestName.PadRight(14).Substring(0, 14);
+ string chunkInfo = result.ChunkCount.ToString("N0").PadRight(11).Substring(0, 11);
+ string createTime = $"{result.CreateTime:F3} s".PadRight(10).Substring(0, 10);
+ string accessTime = $"{result.AccessTime:F3} s".PadRight(10).Substring(0, 10);
+ string unloadTime = $"{result.UnloadTime:F3} s".PadRight(10).Substring(0, 10);
+
+ table.AppendLine($"│ {testName} │ {chunkInfo} │ {createTime} │ {accessTime} │ {unloadTime} │");
+ }
+
+ table.AppendLine("└────────────────┴─────────────┴────────────┴────────────┴────────────┘");
+ Console.WriteLine(table.ToString());
+
+ // Calculate and display performance metrics
+ if (_results.Count > 0)
+ {
+ Console.ForegroundColor = _resultColor;
+ Console.WriteLine("Performance Metrics:");
+ Console.ResetColor();
+
+ var create2DRate = _results.Where(r => r.TestName.Contains("2D")).Select(r => r.ChunkCount / r.CreateTime).FirstOrDefault();
+ var create3DRate = _results.Where(r => r.TestName.Contains("3D")).Select(r => r.ChunkCount / r.CreateTime).FirstOrDefault();
+
+ if (create2DRate > 0)
+ Console.WriteLine($"2D Chunk Creation Rate: {create2DRate:N0} chunks/second");
+ if (create3DRate > 0)
+ Console.WriteLine($"3D Chunk Creation Rate: {create3DRate:N0} chunks/second");
+ }
+ }
+
+ private static void Benchmark2D(int cpuCount, int numChunks, int chunkSize, int maxLoaded)
+ {
+ Console.ForegroundColor = _headerColor;
+ Console.WriteLine($"[2D] {numChunks:N0} chunks of size {chunkSize}x{chunkSize}, maxLoaded={maxLoaded:N0}");
+ Console.ResetColor();
+
+ var constraints = new WorldConstraints
+ {
+ MinChunkX = 0,
+ MaxChunkX = 9999,
+ MinChunkY = 0,
+ MaxChunkY = 9999,
+ MaxLoadedChunks = maxLoaded
+ };
+ var manager = new ChunkManager2D(constraints, maxLoaded);
+
+ // Memory before test
+ var memoryBefore = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ if (_verboseMode)
+ {
+ Console.ForegroundColor = _memoryColor;
+ Console.WriteLine($" Memory before: {FormatByteSize(memoryBefore.EstimatedChunkMemoryBytes)}");
+ Console.ResetColor();
+ }
+
+ // Create and fill
+ var sw = Stopwatch.StartNew();
+ Parallel.For(0, numChunks, new ParallelOptions { MaxDegreeOfParallelism = cpuCount }, i =>
+ {
+ int x = i % 1000, y = i / 1000;
+ var chunk = manager.LoadOrCreateChunk(x, y, chunkSize, chunkSize);
+ for (int cx = 0; cx < chunkSize; cx++)
+ for (int cy = 0; cy < chunkSize; cy++)
+ chunk[cx, cy] = (byte)((cx + cy) % 256);
+ });
+ sw.Stop();
+ double createTime = sw.Elapsed.TotalSeconds;
+ Console.WriteLine($" Created and filled {numChunks:N0} chunks in {createTime:F3} s");
+
+ // Memory after creation
+ var memoryAfterCreate = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ if (_verboseMode)
+ {
+ Console.ForegroundColor = _memoryColor;
+ Console.WriteLine($" Memory after creation: {FormatByteSize(memoryAfterCreate.EstimatedChunkMemoryBytes)} " +
+ $"(+{FormatByteSize(memoryAfterCreate.EstimatedChunkMemoryBytes - memoryBefore.EstimatedChunkMemoryBytes)})");
+ Console.ResetColor();
+ }
+
+ // Access (sum all cells)
+ sw.Restart();
+ long total = 0;
+ Parallel.For(0, numChunks, new ParallelOptions { MaxDegreeOfParallelism = cpuCount }, () => 0L,
+ (i, state, localSum) =>
+ {
+ int x = i % 1000, y = i / 1000;
+ var chunk = manager.GetChunk(x, y);
+ if (chunk != null)
+ {
+ for (int cx = 0; cx < chunkSize; cx++)
+ for (int cy = 0; cy < chunkSize; cy++)
+ localSum += chunk[cx, cy];
+ }
+ return localSum;
+ },
+ localSum => Interlocked.Add(ref total, localSum)
+ );
+ sw.Stop();
+ double accessTime = sw.Elapsed.TotalSeconds;
+ Console.WriteLine($" Accessed {numChunks:N0} chunks in {accessTime:F3} s (sum: {total:N0})");
+
+ // Unload
+ sw.Restart();
+ Parallel.For(0, numChunks, new ParallelOptions { MaxDegreeOfParallelism = cpuCount }, i =>
+ {
+ int x = i % 1000, y = i / 1000;
+ manager.UnloadChunk(x, y);
+ });
+ sw.Stop();
+ double unloadTime = sw.Elapsed.TotalSeconds;
+ Console.WriteLine($" Unloaded {numChunks:N0} chunks in {unloadTime:F3} s");
+
+ // Memory after test
+ var memoryAfter = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ if (_verboseMode)
+ {
+ Console.ForegroundColor = _memoryColor;
+ Console.WriteLine($" Memory after unload: {FormatByteSize(memoryAfter.EstimatedChunkMemoryBytes)} " +
+ $"(change: {FormatByteSize(memoryAfter.EstimatedChunkMemoryBytes - memoryBefore.EstimatedChunkMemoryBytes)})");
+ Console.ResetColor();
+ }
+
+ // Store results
+ _results.Add(new BenchmarkResult
+ {
+ TestName = "2D Chunks",
+ ChunkCount = numChunks,
+ ChunkSize = $"{chunkSize}x{chunkSize}",
+ CreateTime = createTime,
+ AccessTime = accessTime,
+ UnloadTime = unloadTime,
+ MemoryUsed = memoryAfterCreate.EstimatedChunkMemoryBytes - memoryBefore.EstimatedChunkMemoryBytes
+ });
+
+ // Force GC to clean up
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+
+ private static void Benchmark3D(int cpuCount, int numChunks, int width, int height, int depth, int maxLoaded)
+ {
+ Console.ForegroundColor = _headerColor;
+ Console.WriteLine($"[3D] {numChunks:N0} chunks of size {width}x{height}x{depth}, maxLoaded={maxLoaded:N0}");
+ Console.ResetColor();
+
+ var constraints = new WorldConstraints
+ {
+ MinChunkX = 0,
+ MaxChunkX = 9999,
+ MinChunkY = 0,
+ MaxChunkY = 9999,
+ MaxLoadedChunks = maxLoaded
+ };
+ var manager = new ChunkManager3D(constraints, maxLoaded);
+
+ // Memory before test
+ var memoryBefore = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ if (_verboseMode)
+ {
+ Console.ForegroundColor = _memoryColor;
+ Console.WriteLine($" Memory before: {FormatByteSize(memoryBefore.EstimatedChunkMemoryBytes)}");
+ Console.ResetColor();
+ }
+
+ // Create and fill
+ var sw = Stopwatch.StartNew();
+ Parallel.For(0, numChunks, new ParallelOptions { MaxDegreeOfParallelism = cpuCount }, i =>
+ {
+ int x = i % 100, y = i / 100 % 100, z = i / 10000;
+ var chunk = manager.LoadOrCreateChunk(x, y, z, width, height, depth);
+ for (int cx = 0; cx < width; cx++)
+ for (int cy = 0; cy < height; cy++)
+ for (int cz = 0; cz < depth; cz++)
+ chunk[cx, cy, cz] = (byte)((cx + cy + cz) % 256);
+ });
+ sw.Stop();
+ double createTime = sw.Elapsed.TotalSeconds;
+ Console.WriteLine($" Created and filled {numChunks:N0} chunks in {createTime:F3} s");
+
+ // Memory after creation
+ var memoryAfterCreate = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ if (_verboseMode)
+ {
+ Console.ForegroundColor = _memoryColor;
+ Console.WriteLine($" Memory after creation: {FormatByteSize(memoryAfterCreate.EstimatedChunkMemoryBytes)} " +
+ $"(+{FormatByteSize(memoryAfterCreate.EstimatedChunkMemoryBytes - memoryBefore.EstimatedChunkMemoryBytes)})");
+ Console.ResetColor();
+ }
+
+ // Access (sum all cells)
+ sw.Restart();
+ long total = 0;
+ Parallel.For(0, numChunks, new ParallelOptions { MaxDegreeOfParallelism = cpuCount }, () => 0L,
+ (i, state, localSum) =>
+ {
+ int x = i % 100, y = i / 100 % 100, z = i / 10000;
+ var chunk = manager.GetChunk(x, y, z);
+ if (chunk != null)
+ {
+ for (int cx = 0; cx < width; cx++)
+ for (int cy = 0; cy < height; cy++)
+ for (int cz = 0; cz < depth; cz++)
+ localSum += chunk[cx, cy, cz];
+ }
+ return localSum;
+ },
+ localSum => Interlocked.Add(ref total, localSum)
+ );
+ sw.Stop();
+ double accessTime = sw.Elapsed.TotalSeconds;
+ Console.WriteLine($" Accessed {numChunks:N0} chunks in {accessTime:F3} s (sum: {total:N0})");
+
+ // Unload
+ sw.Restart();
+ Parallel.For(0, numChunks, new ParallelOptions { MaxDegreeOfParallelism = cpuCount }, i =>
+ {
+ int x = i % 100, y = i / 100 % 100, z = i / 10000;
+ manager.UnloadChunk(x, y, z);
+ });
+ sw.Stop();
+ double unloadTime = sw.Elapsed.TotalSeconds;
+ Console.WriteLine($" Unloaded {numChunks:N0} chunks in {unloadTime:F3} s");
+
+ // Memory after test
+ var memoryAfter = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ if (_verboseMode)
+ {
+ Console.ForegroundColor = _memoryColor;
+ Console.WriteLine($" Memory after unload: {FormatByteSize(memoryAfter.EstimatedChunkMemoryBytes)} " +
+ $"(change: {FormatByteSize(memoryAfter.EstimatedChunkMemoryBytes - memoryBefore.EstimatedChunkMemoryBytes)})");
+ Console.ResetColor();
+ }
+
+ // Store results
+ _results.Add(new BenchmarkResult
+ {
+ TestName = "3D Chunks",
+ ChunkCount = numChunks,
+ ChunkSize = $"{width}x{height}x{depth}",
+ CreateTime = createTime,
+ AccessTime = accessTime,
+ UnloadTime = unloadTime,
+ MemoryUsed = memoryAfterCreate.EstimatedChunkMemoryBytes - memoryBefore.EstimatedChunkMemoryBytes
+ });
+
+ // Force GC to clean up
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+
+ private static void RunMemoryTest(int cpuCount, int chunk2DSize, int chunk3DWidth, int chunk3DHeight, int chunk3DDepth)
+ {
+ Console.ForegroundColor = _headerColor;
+ Console.WriteLine("╔═══════════════════════════════════════════════════════════════╗");
+ Console.WriteLine("║ Memory Usage Test ║");
+ Console.WriteLine("╚═══════════════════════════════════════════════════════════════╝");
+ Console.ResetColor();
+
+ // Initial memory state
+ var initialMemory = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ Console.WriteLine($"Initial memory state: {FormatByteSize(initialMemory.EstimatedChunkMemoryBytes)}");
+
+ // Test 2D memory scaling
+ Console.ForegroundColor = _headerColor;
+ Console.WriteLine("\n[2D Memory Scaling Test]");
+ Console.ResetColor();
+
+ var manager2D = new ChunkManager2D(null, 1000000);
+ var memoryPoints2D = new List<(int chunkCount, ulong memoryUsed)>();
+
+ for (int i = 1; i <= 10; i++)
+ {
+ int chunkCount = i * 1000;
+ Console.WriteLine($" Loading {chunkCount:N0} 2D chunks...");
+
+ for (int j = (i - 1) * 1000; j < i * 1000; j++)
+ {
+ int x = j % 1000, y = j / 1000;
+ var chunk = manager2D.LoadOrCreateChunk(x, y, chunk2DSize, chunk2DSize);
+ // Fill with some data
+ for (int cx = 0; cx < chunk2DSize; cx++)
+ for (int cy = 0; cy < chunk2DSize; cy++)
+ chunk[cx, cy] = (byte)((cx + cy) % 256);
+ }
+
+ var memoryReport = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ memoryPoints2D.Add((chunkCount, memoryReport.EstimatedChunkMemoryBytes));
+ Console.WriteLine($" Memory used: {FormatByteSize(memoryReport.EstimatedChunkMemoryBytes)}");
+ }
+
+ // Clean up 2D chunks
+ manager2D = null;
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ // Test 3D memory scaling
+ Console.ForegroundColor = _headerColor;
+ Console.WriteLine("\n[3D Memory Scaling Test]");
+ Console.ResetColor();
+
+ var manager3D = new ChunkManager3D(null, 1000000);
+ var memoryPoints3D = new List<(int chunkCount, ulong memoryUsed)>();
+
+ for (int i = 1; i <= 5; i++)
+ {
+ int chunkCount = i * 10;
+ Console.WriteLine($" Loading {chunkCount:N0} 3D chunks...");
+
+ for (int j = (i - 1) * 10; j < i * 10; j++)
+ {
+ int x = j % 10, y = j / 10 % 10, z = j / 100;
+ var chunk = manager3D.LoadOrCreateChunk(x, y, z, chunk3DWidth, chunk3DHeight, chunk3DDepth);
+ // Fill with some data
+ for (int cx = 0; cx < chunk3DWidth; cx++)
+ for (int cy = 0; cy < chunk3DHeight; cy++)
+ for (int cz = 0; cz < chunk3DDepth; cz++)
+ chunk[cx, cy, cz] = (byte)((cx + cy + cz) % 256);
+ }
+
+ var memoryReport = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ memoryPoints3D.Add((chunkCount, memoryReport.EstimatedChunkMemoryBytes));
+ Console.WriteLine($" Memory used: {FormatByteSize(memoryReport.EstimatedChunkMemoryBytes)}");
+ }
+
+ // Clean up 3D chunks
+ manager3D = null;
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ // Display memory scaling results
+ Console.ForegroundColor = _headerColor;
+ Console.WriteLine("\n[Memory Scaling Results]");
+ Console.ResetColor();
+
+ Console.WriteLine("2D Memory Scaling:");
+ for (int i = 0; i < memoryPoints2D.Count; i++)
+ {
+ var point = memoryPoints2D[i];
+ ulong bytesPerChunk = i > 0
+ ? (point.memoryUsed - memoryPoints2D[i - 1].memoryUsed) / (ulong)(point.chunkCount - memoryPoints2D[i - 1].chunkCount)
+ : point.memoryUsed / (ulong)point.chunkCount;
+
+ Console.WriteLine($" {point.chunkCount:N0} chunks: {FormatByteSize(point.memoryUsed)} " +
+ $"(~{FormatByteSize(bytesPerChunk)}/chunk)");
+ }
+
+ Console.WriteLine("\n3D Memory Scaling:");
+ for (int i = 0; i < memoryPoints3D.Count; i++)
+ {
+ var point = memoryPoints3D[i];
+ ulong bytesPerChunk = i > 0
+ ? (point.memoryUsed - memoryPoints3D[i - 1].memoryUsed) / (ulong)(point.chunkCount - memoryPoints3D[i - 1].chunkCount)
+ : point.memoryUsed / (ulong)point.chunkCount;
+
+ Console.WriteLine($" {point.chunkCount:N0} chunks: {FormatByteSize(point.memoryUsed)} " +
+ $"(~{FormatByteSize(bytesPerChunk)}/chunk)");
+ }
+
+ // Final memory state
+ var finalMemory = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ Console.WriteLine($"\nFinal memory state: {FormatByteSize(finalMemory.EstimatedChunkMemoryBytes)}");
+ Console.WriteLine($"Memory change: {FormatByteSize(finalMemory.EstimatedChunkMemoryBytes - initialMemory.EstimatedChunkMemoryBytes)}");
+ }
+
+ private static void RunStressTest(int cpuCount, int chunk2DSize, int chunk3DWidth, int chunk3DHeight, int chunk3DDepth)
+ {
+ Console.ForegroundColor = _headerColor;
+ Console.WriteLine("╔═══════════════════════════════════════════════════════════════╗");
+ Console.WriteLine("║ Stress Test ║");
+ Console.WriteLine("╚═══════════════════════════════════════════════════════════════╝");
+ Console.ResetColor();
+
+ // Initial memory state
+ var initialMemory = AdvChkSys.AdvChkSys.GetMemoryUsage();
+
+ // Create managers with small cache size to force evictions
+ var manager2D = new ChunkManager2D(null, 100);
+ var manager3D = new ChunkManager3D(null, 50);
+
+ Console.WriteLine("Running load/unload stress test (rapid chunk cycling)...");
+
+ var sw = Stopwatch.StartNew();
+ int iterations = 5;
+ int chunksPerIteration = 500;
+
+ for (int iter = 0; iter < iterations; iter++)
+ {
+ Console.WriteLine($" Iteration {iter + 1}/{iterations}...");
+
+ // 2D stress
+ Parallel.For(0, chunksPerIteration, new ParallelOptions { MaxDegreeOfParallelism = cpuCount }, i =>
+ {
+ int x = i % 100, y = i / 100;
+ // Load chunk
+ var chunk = manager2D.LoadOrCreateChunk(x, y, chunk2DSize, chunk2DSize);
+ // Fill with data
+ for (int cx = 0; cx < chunk2DSize; cx++)
+ for (int cy = 0; cy < chunk2DSize; cy++)
+ chunk[cx, cy] = (byte)((cx + cy + i) % 256);
+
+ // Access chunk
+ long sum = 0;
+ for (int cx = 0; cx < chunk2DSize; cx++)
+ for (int cy = 0; cy < chunk2DSize; cy++)
+ sum += chunk[cx, cy];
+
+ // Force some evictions by loading other chunks
+ for (int j = 0; j < 3; j++)
+ {
+ int otherX = (i + j * 200) % 1000;
+ int otherY = (i + j * 200) / 1000;
+ var otherChunk = manager2D.LoadOrCreateChunk(otherX, otherY, chunk2DSize, chunk2DSize);
+ otherChunk[0, 0] = (byte)(i + j);
+ }
+ });
+
+ // 3D stress
+ Parallel.For(0, chunksPerIteration / 10, new ParallelOptions { MaxDegreeOfParallelism = cpuCount }, i =>
+ {
+ int x = i % 10, y = i / 10 % 10, z = i / 100;
+ // Load chunk
+ var chunk = manager3D.LoadOrCreateChunk(x, y, z, chunk3DWidth, chunk3DHeight, chunk3DDepth);
+ // Fill with data (just a portion to save time)
+ for (int cx = 0; cx < chunk3DWidth; cx++)
+ for (int cy = 0; cy < chunk3DHeight / 4; cy++)
+ for (int cz = 0; cz < chunk3DDepth; cz++)
+ chunk[cx, cy, cz] = (byte)((cx + cy + cz + i) % 256);
+
+ // Access chunk
+ long sum = 0;
+ for (int cx = 0; cx < chunk3DWidth; cx++)
+ for (int cy = 0; cy < chunk3DHeight / 4; cy++)
+ for (int cz = 0; cz < chunk3DDepth; cz++)
+ sum += chunk[cx, cy, cz];
+
+ // Force some evictions by loading other chunks
+ for (int j = 0; j < 2; j++)
+ {
+ int otherX = (i + j * 20) % 30;
+ int otherY = (i + j * 20) / 30 % 30;
+ int otherZ = (i + j * 20) / 900;
+ var otherChunk = manager3D.LoadOrCreateChunk(otherX, otherY, otherZ, chunk3DWidth, chunk3DHeight, chunk3DDepth);
+ otherChunk[0, 0, 0] = (byte)(i + j);
+ }
+ });
+
+ // Check memory after each iteration
+ var memoryReport = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ Console.ForegroundColor = _memoryColor;
+ Console.WriteLine($" Memory: {FormatByteSize(memoryReport.EstimatedChunkMemoryBytes)} " +
+ $"({memoryReport.MemoryUsagePercentage:F2}% of system memory)");
+ Console.ResetColor();
+
+ // Force GC between iterations
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+
+ sw.Stop();
+
+ Console.WriteLine($"Stress test completed in {sw.Elapsed.TotalSeconds:F2} seconds");
+
+ // Final memory state
+ var finalMemory = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ Console.WriteLine($"Final memory state: {FormatByteSize(finalMemory.EstimatedChunkMemoryBytes)}");
+ Console.WriteLine($"Memory change: {FormatByteSize(finalMemory.EstimatedChunkMemoryBytes - initialMemory.EstimatedChunkMemoryBytes)}");
+
+ // Clean up
+ manager2D = null;
+ manager3D = null;
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+
+ private static bool CanAllocateChunks(int numChunks, int width, int height, int depth, double safetyFactor)
+ {
+ long bytesPerChunk = (long)width * height * depth;
+ long totalBytes = bytesPerChunk * numChunks;
+ long available = GetAvailableMemoryBytes();
+ long safeLimit = (long)(available * safetyFactor);
+
+ return totalBytes <= safeLimit;
+ }
+
+ private static long GetAvailableMemoryBytes()
+ {
+ return (long)AdvChkSys.AdvChkSys.GetMemoryUsage().AvailableSystemMemoryBytes;
+ }
+
+ private static string FormatByteSize(ulong bytes)
+ {
+ string[] sizes = { "B", "KB", "MB", "GB", "TB" };
+ double formattedSize = bytes;
+ int order = 0;
+
+ while (formattedSize >= 1024 && order < sizes.Length - 1)
+ {
+ order++;
+ formattedSize /= 1024;
+ }
+
+ return $"{formattedSize:F2} {sizes[order]}";
+ }
+ }
+
+ public class BenchmarkResult
+ {
+ public string TestName { get; set; } = "";
+ public int ChunkCount { get; set; }
+ public string ChunkSize { get; set; } = "";
+ public double CreateTime { get; set; }
+ public double AccessTime { get; set; }
+ public double UnloadTime { get; set; }
+ public ulong MemoryUsed { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/AdvChkSys.Benchmarks/MemoryReporter.cs b/src/AdvChkSys.Benchmarks/MemoryReporter.cs
new file mode 100644
index 0000000..868f0f9
--- /dev/null
+++ b/src/AdvChkSys.Benchmarks/MemoryReporter.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using AdvChkSys.Diagnostics;
+
+namespace ChunkMark
+{
+ ///
+ /// Provides detailed memory reporting and visualization for benchmarks.
+ ///
+ public class MemoryReporter
+ {
+ private readonly List _snapshots = new List();
+ private readonly Stopwatch _timer = new Stopwatch();
+ private readonly string _reportName;
+
+ public MemoryReporter(string reportName)
+ {
+ _reportName = reportName;
+ _timer.Start();
+ }
+
+ ///
+ /// Takes a memory snapshot with the given label.
+ ///
+ public void TakeSnapshot(string label)
+ {
+ var report = AdvChkSys.AdvChkSys.GetMemoryUsage();
+ _snapshots.Add(new MemorySnapshot
+ {
+ Label = label,
+ TimeSeconds = _timer.Elapsed.TotalSeconds,
+ Report = report
+ });
+ }
+
+ ///
+ /// Generates a detailed memory report as a string.
+ ///
+ public string GenerateReport()
+ {
+ if (_snapshots.Count == 0)
+ return "No memory snapshots recorded.";
+
+ var sb = new StringBuilder();
+ sb.AppendLine($"Memory Report: {_reportName}");
+ sb.AppendLine("=".PadRight(50, '='));
+ sb.AppendLine();
+
+ sb.AppendLine("Snapshots:");
+ sb.AppendLine("┌─────────────────┬──────────┬─────────────────┬─────────────┬───────────┐");
+ sb.AppendLine("│ Label │ Time (s) │ Chunk Memory │ Active │ Usage % │");
+ sb.AppendLine("├─────────────────┼──────────┼─────────────────┼─────────────┼───────────┤");
+
+ foreach (var snapshot in _snapshots)
+ {
+ string label = snapshot.Label.PadRight(15).Substring(0, 15);
+ string time = $"{snapshot.TimeSeconds:F2}".PadRight(8).Substring(0, 8);
+ string memory = FormatByteSize(snapshot.Report.EstimatedChunkMemoryBytes).PadRight(15).Substring(0, 15);
+ string active = $"{snapshot.Report.ActiveChunkCount:N0}".PadRight(11).Substring(0, 11);
+ string usage = $"{snapshot.Report.MemoryUsagePercentage:F2}%".PadRight(9).Substring(0, 9);
+
+ sb.AppendLine($"│ {label} │ {time} │ {memory} │ {active} │ {usage} │");
+ }
+ sb.AppendLine("└─────────────────┴──────────┴─────────────────┴─────────────┴───────────┘");
+ sb.AppendLine();
+
+ // Memory change analysis
+ if (_snapshots.Count >= 2)
+ {
+ var first = _snapshots.First();
+ var last = _snapshots.Last();
+ var maxMem = _snapshots.Max(s => s.Report.EstimatedChunkMemoryBytes);
+ var minMem = _snapshots.Min(s => s.Report.EstimatedChunkMemoryBytes);
+
+ sb.AppendLine("Memory Analysis:");
+ sb.AppendLine($" Initial Memory: {FormatByteSize(first.Report.EstimatedChunkMemoryBytes)}");
+ sb.AppendLine($" Final Memory: {FormatByteSize(last.Report.EstimatedChunkMemoryBytes)}");
+ sb.AppendLine($" Net Change: {FormatByteSize(last.Report.EstimatedChunkMemoryBytes - first.Report.EstimatedChunkMemoryBytes)}");
+ sb.AppendLine($" Peak Memory: {FormatByteSize(maxMem)}");
+ sb.AppendLine($" Minimum Memory: {FormatByteSize(minMem)}");
+ sb.AppendLine($" Memory Volatility: {FormatByteSize(maxMem - minMem)}");
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Saves the memory report to a file.
+ ///
+ public void SaveReportToFile(string filePath)
+ {
+ string report = GenerateReport();
+ File.WriteAllText(filePath, report);
+ }
+
+ ///
+ /// Formats a byte size into a human-readable string (KB, MB, GB).
+ ///
+ private static string FormatByteSize(ulong bytes)
+ {
+ string[] sizes = { "B", "KB", "MB", "GB", "TB" };
+ double formattedSize = bytes;
+ int order = 0;
+
+ while (formattedSize >= 1024 && order < sizes.Length - 1)
+ {
+ order++;
+ formattedSize /= 1024;
+ }
+
+ return $"{formattedSize:F2} {sizes[order]}";
+ }
+
+ private class MemorySnapshot
+ {
+ public string Label { get; set; } = "";
+ public double TimeSeconds { get; set; }
+ public MemoryUsageReport Report { get; set; } = null!;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AdvChkSys.Benchmarks/README.md b/src/AdvChkSys.Benchmarks/README.md
new file mode 100644
index 0000000..ee0ff93
--- /dev/null
+++ b/src/AdvChkSys.Benchmarks/README.md
@@ -0,0 +1,107 @@
+# ChunkMark - AdvChkSys Benchmarking Tool
+
+ChunkMark is a comprehensive benchmarking and diagnostic tool for the AdvChkSys chunk management library. It provides detailed performance metrics and memory usage analysis to help developers optimize their chunk-based applications.
+
+## Features
+
+- **Performance Benchmarking**: Measures chunk creation, access, and unloading speeds
+- **Memory Analysis**: Tracks memory consumption patterns during chunk operations
+- **Stress Testing**: Simulates high-load scenarios with rapid chunk cycling
+- **Memory Scaling Tests**: Shows how memory usage scales with increasing chunk counts
+- **Detailed Reporting**: Provides formatted tables and summaries of benchmark results
+
+## Usage
+
+```
+ChunkMark.exe [options]
+```
+
+### Command Line Options
+
+| Option | Description |
+|--------|-------------|
+| `--cpus=N` | Set the number of CPU threads to use (default: all available) |
+| `--2d-chunks=N` | Number of 2D chunks to benchmark (default: 10000) |
+| `--2d-size=N` | Size of 2D chunks (NxN) (default: 32) |
+| `--3d-chunks=N` | Number of 3D chunks to benchmark (default: 500) |
+| `--3d-width=N` | Width of 3D chunks (default: 16) |
+| `--3d-height=N` | Height of 3D chunks (default: 16) |
+| `--3d-depth=N` | Depth of 3D chunks (default: 16) |
+| `--3d-size=N` | Set all 3D dimensions to N (overrides individual settings) |
+| `--max-loaded=N` | Maximum chunks to keep in memory (default: 100000) |
+| `--2d-only` | Run only 2D benchmarks |
+| `--3d-only` | Run only 3D benchmarks |
+| `--memory-test` | Run memory scaling tests |
+| `--stress-test` | Run chunk cycling stress tests |
+| `--verbose` | Show detailed memory information during tests |
+
+### Examples
+
+Basic benchmark with default settings:
+```
+ChunkMark.exe
+```
+
+Run only 2D benchmarks with 50,000 chunks of size 64x64:
+```
+ChunkMark.exe --2d-only --2d-chunks=50000 --2d-size=64
+```
+
+Run 3D benchmarks with custom dimensions:
+```
+ChunkMark.exe --3d-only --3d-chunks=100 --3d-width=32 --3d-height=128 --3d-depth=32
+```
+
+Run memory scaling tests with verbose output:
+```
+ChunkMark.exe --memory-test --verbose
+```
+
+## Benchmark Types
+
+### Standard Benchmarks
+
+The standard benchmarks measure three key operations:
+
+1. **Creation**: Time to create and fill chunks with data
+2. **Access**: Time to read all data from chunks
+3. **Unload**: Time to unload chunks from memory
+
+These benchmarks are run for both 2D and 3D chunks (unless limited by command line options).
+
+### Memory Scaling Test
+
+This test incrementally loads more chunks and measures memory consumption at each step. It helps visualize how memory usage scales with chunk count and calculates the average memory per chunk.
+
+### Stress Test
+
+The stress test rapidly cycles chunks in and out of the cache to test the LRU eviction mechanism and memory stability under high load. It's useful for identifying memory leaks or performance degradation during extended use.
+
+## Output
+
+ChunkMark provides formatted output with:
+
+- Benchmark results in tabular format
+- Memory usage statistics
+- Performance metrics (chunks/second)
+- Summary of all tests
+
+In verbose mode, it also shows detailed memory snapshots before and after each operation.
+
+## Memory Safety
+
+ChunkMark includes memory safety checks to prevent out-of-memory errors. If a benchmark would require more memory than is safely available, ChunkMark will prompt for alternative parameters or skip the test.
+
+## Interpreting Results
+
+- **Creation Time**: Lower is better. Measures how quickly chunks can be created and filled.
+- **Access Time**: Lower is better. Measures how quickly data can be read from chunks.
+- **Unload Time**: Lower is better. Measures how quickly chunks can be removed from memory.
+- **Memory Usage**: Lower is better for a given number of chunks.
+- **Chunks/Second**: Higher is better. Derived metric showing throughput.
+
+## System Requirements
+
+- .NET 5.0 or higher
+- Sufficient memory for the requested benchmark parameters
+- Windows, Linux, or macOS operating system
\ No newline at end of file
diff --git a/src/AdvChkSys.Benchmarks/benchmark_advchksys.py b/src/AdvChkSys.Benchmarks/benchmark_advchksys.py
new file mode 100644
index 0000000..e5057de
--- /dev/null
+++ b/src/AdvChkSys.Benchmarks/benchmark_advchksys.py
@@ -0,0 +1,164 @@
+# noqa: E501
+import time
+import advchksys
+from concurrent.futures import ThreadPoolExecutor
+
+
+def parallel_benchmark_2d(manager, num_chunks, chunk_size, max_workers=None):
+ print(
+ f"\n[2D] Parallel Benchmark: {num_chunks} chunks of size {chunk_size}x{chunk_size} (max_workers={max_workers})"
+ )
+ t0 = time.perf_counter()
+
+ def create_and_fill(i):
+ x, y = i % 100, i // 100
+ chunk = manager.LoadOrCreateChunk(x, y, chunk_size, chunk_size)
+ for cx in range(chunk_size):
+ for cy in range(chunk_size):
+ chunk[cx, cy] = (cx + cy) % 256
+
+ # Parallel chunk creation and filling
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
+ list(executor.map(create_and_fill, range(num_chunks)))
+
+ t1 = time.perf_counter()
+ print(
+ f" Created and filled {num_chunks} chunks in {t1-t0:.3f} seconds (parallel)"
+ )
+
+ # Parallel chunk access (sum all cells)
+ t2 = time.perf_counter()
+
+ def sum_chunk(i):
+ x, y = i % 100, i // 100
+ chunk = manager.GetChunk(x, y)
+ subtotal = 0
+ if chunk is not None:
+ for cx in range(chunk_size):
+ for cy in range(chunk_size):
+ subtotal += chunk[cx, cy]
+ return subtotal
+
+ total = 0
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
+ for subtotal in executor.map(sum_chunk, range(num_chunks)):
+ total += subtotal
+
+ t3 = time.perf_counter()
+ print(
+ f" Accessed {num_chunks} chunks in {t3-t2:.3f} seconds (sum: {total}, parallel)"
+ )
+
+ # Parallel chunk unload
+ t4 = time.perf_counter()
+
+ def unload_chunk(i):
+ x, y = i % 100, i // 100
+ manager.UnloadChunk(x, y)
+
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
+ list(executor.map(unload_chunk, range(num_chunks)))
+
+ t5 = time.perf_counter()
+ print(f" Unloaded {num_chunks} chunks in {t5-t4:.3f} seconds (parallel)")
+
+
+def parallel_benchmark_3d(
+ manager,
+ num_chunks,
+ chunk_size_x,
+ chunk_size_y,
+ chunk_size_z,
+ max_workers=None,
+):
+ print(
+ f"\n[3D] Parallel Benchmark: {num_chunks} chunks of size {chunk_size_x}x{chunk_size_y}x{chunk_size_z} (max_workers={max_workers})"
+ )
+ t0 = time.perf_counter()
+
+ def create_and_fill(i):
+ x, y, z = i % 10, (i // 10) % 10, i // 100
+ chunk = manager.LoadOrCreateChunk(
+ x, y, z, chunk_size_x, chunk_size_y, chunk_size_z
+ )
+ for cx in range(chunk_size_x):
+ for cy in range(chunk_size_y):
+ for cz in range(chunk_size_z):
+ chunk[cx, cy, cz] = (cx + cy + cz) % 256
+
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
+ list(executor.map(create_and_fill, range(num_chunks)))
+
+ t1 = time.perf_counter()
+ print(
+ f" Created and filled {num_chunks} chunks in {t1-t0:.3f} seconds (parallel)"
+ )
+
+ # Parallel chunk access (sum all cells)
+ t2 = time.perf_counter()
+
+ def sum_chunk(i):
+ x, y, z = i % 10, (i // 10) % 10, i // 100
+ chunk = manager.GetChunk(x, y, z)
+ subtotal = 0
+ if chunk is not None:
+ for cx in range(chunk_size_x):
+ for cy in range(chunk_size_y):
+ for cz in range(chunk_size_z):
+ subtotal += chunk[cx, cy, cz]
+ return subtotal
+
+ total = 0
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
+ for subtotal in executor.map(sum_chunk, range(num_chunks)):
+ total += subtotal
+
+ t3 = time.perf_counter()
+ print(
+ f" Accessed {num_chunks} chunks in {t3-t2:.3f} seconds (sum: {total}, parallel)"
+ )
+
+ # Parallel chunk unload
+ t4 = time.perf_counter()
+
+ def unload_chunk(i):
+ x, y, z = i % 10, (i // 10) % 10, i // 100
+ manager.UnloadChunk(x, y, z)
+
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
+ list(executor.map(unload_chunk, range(num_chunks)))
+
+ t5 = time.perf_counter()
+ print(f" Unloaded {num_chunks} chunks in {t5-t4:.3f} seconds (parallel)")
+
+
+def main():
+ print("AdvChkSys Python Parallel Benchmark")
+ print("Library version:", advchksys.get_version())
+
+ # 2D Parallel Benchmark
+ constraints2d = advchksys.create_constraints(
+ min_x=0, max_x=99, min_y=0, max_y=200, max_loaded=10000000
+ )
+ manager2d = advchksys.create_2d_manager(constraints2d)
+ parallel_benchmark_2d(
+ manager2d, num_chunks=20000, chunk_size=32, max_workers=4
+ )
+
+ # 3D Parallel Benchmark
+ constraints3d = advchksys.create_constraints(
+ min_x=0, max_x=9, min_y=0, max_y=9, max_loaded=10000
+ )
+ manager3d = advchksys.create_3d_manager(constraints3d)
+ parallel_benchmark_3d(
+ manager3d,
+ num_chunks=100,
+ chunk_size_x=16,
+ chunk_size_y=16,
+ chunk_size_z=32,
+ max_workers=4,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/AdvChkSys/AdvChkSys.cs b/src/AdvChkSys/AdvChkSys.cs
index 2626528..9e3b98d 100644
--- a/src/AdvChkSys/AdvChkSys.cs
+++ b/src/AdvChkSys/AdvChkSys.cs
@@ -3,6 +3,8 @@ using AdvChkSys.Constraints;
using AdvChkSys.Manager;
using AdvChkSys.Chunk;
using AdvChkSys.Resources;
+using AdvChkSys.Diagnostics;
+using System;
namespace AdvChkSys
{
@@ -34,6 +36,19 @@ namespace AdvChkSys
public static ChunkManager3D Create3DManager(WorldConstraints? constraints = null) =>
new ChunkManager3D(constraints);
+ ///
+ /// Gets a detailed report of the current memory usage by the chunk system.
+ ///
+ /// A report containing memory usage statistics
+ public static MemoryUsageReport GetMemoryUsage() => MemoryUsageReporter.GetMemoryUsage();
+
+ ///
+ /// Logs the current memory usage to the provided logging action.
+ ///
+ /// Action that will receive the log messages
+ public static void LogMemoryUsage(Action logAction) =>
+ MemoryUsageReporter.LogMemoryUsage(logAction);
+
///
/// Performs a basic self-test of the AdvChkSys core functionality.
/// Returns true if all core systems are operational.
diff --git a/src/AdvChkSys/Chunk/Chunk2D.cs b/src/AdvChkSys/Chunk/Chunk2D.cs
index 0ddba60..31501e7 100644
--- a/src/AdvChkSys/Chunk/Chunk2D.cs
+++ b/src/AdvChkSys/Chunk/Chunk2D.cs
@@ -1,4 +1,5 @@
#nullable enable
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using AdvChkSys.Interfaces;
@@ -9,7 +10,7 @@ namespace AdvChkSys.Chunk
/// Represents a 2D chunk of data in the world.
/// Supports all-air singleton for memory efficiency and chunk array pooling.
///
- public class Chunk2D : IChunk
+ public class Chunk2D : IChunk, IDisposable
{
// Flyweight: one all-air instance per size
private static readonly Dictionary<(int, int), Chunk2D> _allAirChunks = new();
@@ -36,12 +37,29 @@ namespace AdvChkSys.Chunk
private T[,]? _data;
// Properties required by IChunk
+ ///
+ /// The chunk's X position in chunk coordinates.
+ ///
public int X { get; private set; }
+ ///
+ /// The chunk's Y position in chunk coordinates.
+ ///
public int Y { get; private set; }
+ ///
+ /// The width of the chunk in cells.
+ ///
public int Width { get; }
+ ///
+ /// The height of the chunk in cells.
+ ///
public int Height { get; }
+ ///
+ /// Metadata dictionary for arbitrary chunk information.
+ ///
public Dictionary Metadata { get; }
+ private bool _disposed;
+
///
/// Normal constructor (private for singleton/factory).
///
@@ -70,11 +88,18 @@ namespace AdvChkSys.Chunk
///
public T this[int localX, int localY]
{
- get => _isAllAir ? default! : _data![localX, localY];
+ get
+ {
+ if (localX < 0 || localX >= Width || localY < 0 || localY >= Height)
+ throw new ArgumentOutOfRangeException($"Coordinates ({localX}, {localY}) out of bounds [0-{Width-1}, 0-{Height-1}]");
+ return _isAllAir ? default! : _data![localX, localY];
+ }
set
{
+ if (localX < 0 || localX >= Width || localY < 0 || localY >= Height)
+ throw new ArgumentOutOfRangeException($"Coordinates ({localX}, {localY}) out of bounds [0-{Width-1}, 0-{Height-1}]");
if (_isAllAir)
- throw new System.InvalidOperationException("Cannot set cell in all-air chunk.");
+ throw new InvalidOperationException("Cannot set cell in all-air chunk.");
_data![localX, localY] = value;
}
}
@@ -108,7 +133,10 @@ namespace AdvChkSys.Chunk
{
if (_data != null)
{
- ReturnArray(_data);
+ if (!_isAllAir)
+ {
+ ReturnArray(_data);
+ }
_data = null;
}
}
@@ -130,5 +158,17 @@ namespace AdvChkSys.Chunk
{
_arrayPool.Add(arr);
}
+
+ ///
+ /// Disposes the chunk and returns its resources to the pool.
+ ///
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ ReleaseDataArray();
+ _disposed = true;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/AdvChkSys/Chunk/Chunk3D.cs b/src/AdvChkSys/Chunk/Chunk3D.cs
index 621a55a..eba7aff 100644
--- a/src/AdvChkSys/Chunk/Chunk3D.cs
+++ b/src/AdvChkSys/Chunk/Chunk3D.cs
@@ -1,68 +1,215 @@
+#nullable enable
+using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using AdvChkSys.Interfaces;
namespace AdvChkSys.Chunk
{
- ///
- /// Represents a 3D chunk of data in the world.
- /// Perspective-agnostic and supports arbitrary data types and metadata.
- ///
- public class Chunk3D : IChunk
- {
///
- /// The chunk's position in chunk coordinates (not world coordinates).
+ /// Represents a 3D chunk of data in the world.
+ /// Perspective-agnostic and supports arbitrary data types and metadata.
///
- public int X { get; }
- public int Y { get; }
- public int Z { get; }
-
- ///
- /// The width, height, and depth of the chunk (in cells/tiles/units).
- ///
- public int Width { get; }
- public int Height { get; }
- public int Depth { get; }
-
- ///
- /// The chunk's data array.
- ///
- private readonly T[,,] _data;
-
- ///
- /// Metadata dictionary for arbitrary chunk information (e.g., biome, tags).
- ///
- public Dictionary Metadata { get; }
-
- public Chunk3D(int x, int y, int z, int width, int height, int depth)
+ public class Chunk3D : IChunk, IDisposable
{
- X = x;
- Y = y;
- Z = z;
- Width = width;
- Height = height;
- Depth = depth;
- _data = new T[width, height, depth];
- Metadata = new Dictionary();
- }
+ // Array pool for chunk data arrays
+ private static readonly ConcurrentBag _arrayPool = new();
+
+ // Flyweight: one all-air instance per size
+ private static readonly Dictionary<(int, int, int), Chunk3D> _allAirChunks = new();
- ///
- /// Gets or sets the value at the given local chunk coordinates.
- ///
- public T this[int localX, int localY, int localZ]
- {
- get => _data[localX, localY, localZ];
- set => _data[localX, localY, localZ] = value;
- }
+ ///
+ /// Returns a singleton all-air chunk for the given size and position.
+ ///
+ public static Chunk3D AllAir(int x, int y, int z, int width, int height, int depth)
+ {
+ var key = (width, height, depth);
+ if (!_allAirChunks.TryGetValue(key, out var chunk))
+ {
+ chunk = new Chunk3D(x, y, z, width, height, depth, isAllAir: true);
+ _allAirChunks[key] = chunk;
+ }
+ chunk.SetPosition(x, y, z); // Use explicit method for position update
+ return chunk;
+ }
- ///
- /// Fills the chunk with a specified value.
- ///
- public void Fill(T value)
- {
- for (int x = 0; x < Width; x++)
- for (int y = 0; y < Height; y++)
- for (int z = 0; z < Depth; z++)
- _data[x, y, z] = value;
+ ///
+ /// The chunk's X position in chunk coordinates.
+ ///
+ public int X { get; private set; }
+
+ ///
+ /// The chunk's Y position in chunk coordinates.
+ ///
+ public int Y { get; private set; }
+
+ ///
+ /// The chunk's Z position in chunk coordinates.
+ ///
+ public int Z { get; private set; }
+
+ ///
+ /// The width of the chunk in cells.
+ ///
+ public int Width { get; }
+
+ ///
+ /// The height of the chunk in cells.
+ ///
+ public int Height { get; }
+
+ ///
+ /// The depth of the chunk in cells.
+ ///
+ public int Depth { get; }
+
+ ///
+ /// The chunk's data array.
+ ///
+ private readonly T[,,]? _data;
+
+ ///
+ /// Metadata dictionary for arbitrary chunk information (e.g., biome, tags).
+ ///
+ public Dictionary Metadata { get; }
+
+ ///
+ /// Returns true if this chunk is the all-air singleton.
+ ///
+ public bool IsAllAir { get; private set; } = false;
+
+ ///
+ /// Tracks whether the chunk has been disposed.
+ ///
+ private bool _disposed;
+
+ ///
+ /// Creates a new 3D chunk at the specified position with the given dimensions.
+ ///
+ /// X coordinate in chunk space
+ /// Y coordinate in chunk space
+ /// Z coordinate in chunk space
+ /// Width of the chunk in cells
+ /// Height of the chunk in cells
+ /// Depth of the chunk in cells
+ public Chunk3D(int x, int y, int z, int width, int height, int depth)
+ : this(x, y, z, width, height, depth, false)
+ {
+ }
+
+ ///
+ /// Creates a new 3D chunk at the specified position with the given dimensions.
+ ///
+ /// X coordinate in chunk space
+ /// Y coordinate in chunk space
+ /// Z coordinate in chunk space
+ /// Width of the chunk in cells
+ /// Height of the chunk in cells
+ /// Depth of the chunk in cells
+ /// Whether this is an all-air chunk
+ public Chunk3D(int x, int y, int z, int width, int height, int depth, bool isAllAir = false)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ Width = width;
+ Height = height;
+ Depth = depth;
+ IsAllAir = isAllAir;
+ _data = isAllAir ? null : RentArray(width, height, depth);
+ Metadata = new Dictionary();
+ }
+
+ ///
+ /// Explicitly sets the chunk's position. Used internally for all-air singleton.
+ ///
+ internal void SetPosition(int x, int y, int z)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ ///
+ /// Gets or sets the value at the given local chunk coordinates.
+ ///
+ public T this[int localX, int localY, int localZ]
+ {
+ get
+ {
+ if (IsAllAir)
+ return default!;
+ return _data![localX, localY, localZ];
+ }
+ set
+ {
+ if (IsAllAir)
+ throw new InvalidOperationException("Cannot modify an all-air chunk.");
+ _data![localX, localY, localZ] = value;
+ }
+ }
+
+ ///
+ /// Fills the chunk with a specified value.
+ ///
+ public void Fill(T value)
+ {
+ if (IsAllAir)
+ throw new InvalidOperationException("Cannot fill an all-air chunk.");
+
+ for (int x = 0; x < Width; x++)
+ for (int y = 0; y < Height; y++)
+ for (int z = 0; z < Depth; z++)
+ _data![x, y, z] = value;
+ }
+
+ ///
+ /// Rents an array from the pool or creates a new one.
+ ///
+ private static T[,,] RentArray(int width, int height, int depth)
+ {
+ if (_arrayPool.TryTake(out var arr) &&
+ arr.GetLength(0) == width &&
+ arr.GetLength(1) == height &&
+ arr.GetLength(2) == depth)
+ return arr;
+ return new T[width, height, depth];
+ }
+
+ ///
+ /// Returns an array to the pool.
+ ///
+ internal static void ReturnArray(T[,,] arr)
+ {
+ _arrayPool.Add(arr);
+ }
+
+ ///
+ /// Returns the underlying data array (for pooling).
+ ///
+ internal T[,,]? GetDataArray() => _data;
+
+ ///
+ /// Releases the data array back to the pool.
+ ///
+ internal void ReleaseDataArray()
+ {
+ if (_data != null && !IsAllAir)
+ {
+ ReturnArray(_data);
+ }
+ }
+
+ ///
+ /// Disposes the chunk and returns its resources to the pool.
+ ///
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ ReleaseDataArray();
+ _disposed = true;
+ }
+ }
}
- }
}
\ No newline at end of file
diff --git a/src/AdvChkSys/Diagnostics/MemoryUsageReporter.cs b/src/AdvChkSys/Diagnostics/MemoryUsageReporter.cs
new file mode 100644
index 0000000..2f7626c
--- /dev/null
+++ b/src/AdvChkSys/Diagnostics/MemoryUsageReporter.cs
@@ -0,0 +1,140 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using AdvChkSys.Resources;
+using AdvChkSys.Util;
+
+namespace AdvChkSys.Diagnostics
+{
+ ///
+ /// Provides memory usage statistics and reporting for the AdvChkSys library.
+ /// Helps client code monitor memory consumption of chunks and related resources.
+ ///
+ public static class MemoryUsageReporter
+ {
+ ///
+ /// Gets the current memory usage statistics for the AdvChkSys library.
+ ///
+ /// A MemoryUsageReport containing detailed memory statistics
+ public static MemoryUsageReport GetMemoryUsage()
+ {
+ var report = new MemoryUsageReport
+ {
+ ActiveChunkCount = ChunkResourceManager.GetActiveChunkCount(),
+ TotalSystemMemoryBytes = GetTotalSystemMemory(),
+ AvailableSystemMemoryBytes = MemoryHelper.GetAvailableMemoryBytes(),
+ Timestamp = DateTime.UtcNow
+ };
+
+ // Calculate estimated chunk memory usage
+ report.EstimatedChunkMemoryBytes = EstimateChunkMemoryUsage(report.ActiveChunkCount);
+
+ return report;
+ }
+
+ ///
+ /// Estimates the memory usage of chunks based on the active chunk count and average chunk size.
+ ///
+ private static ulong EstimateChunkMemoryUsage(int chunkCount)
+ {
+ // This is an estimate based on typical chunk sizes
+ // For more accurate reporting, we would need to track actual sizes
+ const int averageChunkSizeBytes = 32 * 32 * 4; // Assuming 32x32 chunks with 4 bytes per cell on average
+ return (ulong)chunkCount * (ulong)averageChunkSizeBytes;
+ }
+
+ ///
+ /// Gets the total system memory in bytes.
+ ///
+ private static ulong GetTotalSystemMemory()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ var memStatus = new MemoryHelper.MEMORYSTATUSEX();
+ memStatus.dwLength = (uint)Marshal.SizeOf(typeof(MemoryHelper.MEMORYSTATUSEX));
+ if (MemoryHelper.GlobalMemoryStatusEx(ref memStatus))
+ {
+ return memStatus.ullTotalPhys;
+ }
+ }
+
+ // For non-Windows platforms or if Windows call fails, use a fallback
+ return 8UL * 1024 * 1024 * 1024; // Assume 8GB as fallback
+ }
+
+ ///
+ /// Logs memory usage information to the provided action.
+ ///
+ /// Action to handle the log message
+ public static void LogMemoryUsage(Action logAction)
+ {
+ var report = GetMemoryUsage();
+
+ logAction($"--- AdvChkSys Memory Report ({report.Timestamp:yyyy-MM-dd HH:mm:ss}) ---");
+ logAction($"Active Chunks: {report.ActiveChunkCount:N0}");
+ logAction($"Estimated Chunk Memory: {FormatByteSize(report.EstimatedChunkMemoryBytes)}");
+ logAction($"Available System Memory: {FormatByteSize(report.AvailableSystemMemoryBytes)}");
+ logAction($"Total System Memory: {FormatByteSize(report.TotalSystemMemoryBytes)}");
+ logAction($"Memory Usage %: {report.MemoryUsagePercentage:F2}%");
+ logAction("-------------------------------------------");
+ }
+
+ ///
+ /// Formats a byte size into a human-readable string (KB, MB, GB).
+ ///
+ private static string FormatByteSize(ulong bytes)
+ {
+ string[] sizes = { "B", "KB", "MB", "GB", "TB" };
+ double formattedSize = bytes;
+ int order = 0;
+
+ while (formattedSize >= 1024 && order < sizes.Length - 1)
+ {
+ order++;
+ formattedSize /= 1024;
+ }
+
+ return $"{formattedSize:F2} {sizes[order]}";
+ }
+ }
+
+ ///
+ /// Contains detailed memory usage statistics for the AdvChkSys library.
+ ///
+ public class MemoryUsageReport
+ {
+ ///
+ /// The number of active chunks currently being managed.
+ ///
+ public int ActiveChunkCount { get; internal set; }
+
+ ///
+ /// Estimated memory usage of all chunks in bytes.
+ ///
+ public ulong EstimatedChunkMemoryBytes { get; internal set; }
+
+ ///
+ /// Available system memory in bytes.
+ ///
+ public ulong AvailableSystemMemoryBytes { get; internal set; }
+
+ ///
+ /// Total system memory in bytes.
+ ///
+ public ulong TotalSystemMemoryBytes { get; internal set; }
+
+ ///
+ /// Timestamp when this report was generated.
+ ///
+ public DateTime Timestamp { get; internal set; }
+
+ ///
+ /// Percentage of total system memory used by chunks.
+ ///
+ public double MemoryUsagePercentage =>
+ TotalSystemMemoryBytes > 0
+ ? (double)EstimatedChunkMemoryBytes / TotalSystemMemoryBytes * 100
+ : 0;
+ }
+}
\ No newline at end of file
diff --git a/src/AdvChkSys/Interfaces/IChunk.cs b/src/AdvChkSys/Interfaces/IChunk.cs
index 2610be6..59ea3eb 100644
--- a/src/AdvChkSys/Interfaces/IChunk.cs
+++ b/src/AdvChkSys/Interfaces/IChunk.cs
@@ -9,9 +9,13 @@ namespace AdvChkSys.Interfaces
public interface IChunk
{
///
- /// The chunk's position in chunk-space coordinates.
+ /// The chunk's X position in chunk-space coordinates.
///
int X { get; }
+
+ ///
+ /// The chunk's Y position in chunk-space coordinates.
+ ///
int Y { get; }
///
diff --git a/src/AdvChkSys/Manager/ChunkManager2D.cs b/src/AdvChkSys/Manager/ChunkManager2D.cs
index 012cc79..20ad0ee 100644
--- a/src/AdvChkSys/Manager/ChunkManager2D.cs
+++ b/src/AdvChkSys/Manager/ChunkManager2D.cs
@@ -19,7 +19,36 @@ namespace AdvChkSys.Manager
private readonly LRUCache<(int, int), Chunk2D> _chunks;
private readonly WorldConstraints? _constraints;
private readonly int _capacity;
+ private readonly Dictionary<(int, int), Task>> _loadingChunks = new();
+ private readonly object _lock = new object();
+ ///
+ /// Delegate for custom air check logic
+ ///
+ private Func? _airCheckDelegate;
+
+ ///
+ /// World generator for determining empty regions
+ ///
+ private IWorldGenerator? _worldGenerator;
+
+ ///
+ /// Data provider for determining empty regions
+ ///
+ private IDataProvider? _dataProvider;
+
+ ///
+ /// Height map for determining sky chunks
+ ///
+ private IHeightMap? _heightMap;
+
+ ///
+ /// Initializes a new instance of the 2D chunk manager.
+ ///
+ /// Optional world constraints
+ /// Maximum number of chunks to keep in memory (0 for auto-calculation)
+ /// Default chunk width in cells
+ /// Default chunk height in cells
public ChunkManager2D(WorldConstraints? constraints = null, int capacity = 0, int chunkWidth = 32, int chunkHeight = 32)
{
_constraints = constraints;
@@ -82,25 +111,39 @@ namespace AdvChkSys.Manager
private void OnChunkEvicted((int, int) key, Chunk2D chunk)
{
- var arrField = chunk.GetType().GetField("_data", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
- T[,]? arr = null;
- if (arrField != null)
- {
- arr = arrField.GetValue(chunk) as T[,];
- }
+ if (chunk == null) return;
+
+ var arr = chunk.GetDataArray();
if (!chunk.IsAllAir && arr != null)
{
Chunk2D.ReturnArray(arr);
}
ChunkResourceManager.ReleaseChunk(chunk);
ChunkEvents.OnChunkUnloaded(chunk);
- // Optionally: compress or persist chunk here
}
- // Example stub: you can implement your own logic for "all air"
+ ///
+ /// Determines if a region should be represented as an all-air chunk.
+ ///
private bool ShouldBeAllAir(int x, int y, int width, int height)
{
- // For now, always false. In a real system, check worldgen or disk.
+ // Check if we have a custom air check delegate
+ if (_airCheckDelegate != null)
+ return _airCheckDelegate(x, y, width, height);
+
+ // Check if we have a world generator
+ if (_worldGenerator != null)
+ return _worldGenerator.IsRegionEmpty(x, y, width, height);
+
+ // Check if we have a data provider
+ if (_dataProvider != null)
+ return _dataProvider.IsEmptyRegion(x, y, width, height);
+
+ // Check height-based air (for sky chunks)
+ if (_heightMap != null)
+ return y > _heightMap.GetMaxHeight(x, x + width);
+
+ // Default to non-air to ensure data integrity
return false;
}
@@ -110,8 +153,54 @@ namespace AdvChkSys.Manager
///
public async Task> LoadOrCreateChunkAsync(int x, int y, int width, int height)
{
- // Simulate async work (e.g., loading from disk/network)
- return await Task.Run(() => LoadOrCreateChunk(x, y, width, height));
+ var key = (x, y);
+ Task>? task = null;
+
+ // First check if we already have a task
+ lock (_lock)
+ {
+ if (_loadingChunks.TryGetValue(key, out task))
+ {
+ // We found an existing task, no need to create a new one
+ }
+ }
+
+ // If we found a task, await it outside the lock
+ if (task != null)
+ {
+ return await task;
+ }
+
+ // Otherwise, create a new task outside the lock
+ task = Task.Run(() => LoadOrCreateChunk(x, y, width, height));
+
+ // Register the task
+ lock (_lock)
+ {
+ // Check again in case another thread created the task while we were creating ours
+ if (_loadingChunks.TryGetValue(key, out var existingTask))
+ {
+ // Use the existing task instead
+ task = existingTask;
+ }
+ else
+ {
+ // Register our task
+ _loadingChunks[key] = task;
+ }
+ }
+
+ try
+ {
+ return await task;
+ }
+ finally
+ {
+ lock (_lock)
+ {
+ _loadingChunks.Remove(key);
+ }
+ }
}
///
@@ -121,13 +210,16 @@ namespace AdvChkSys.Manager
{
if (_chunks.Remove((x, y), out var chunk))
{
- var arr = chunk.GetDataArray();
- if (!chunk.IsAllAir && arr != null)
+ if (chunk != null) // null check
{
- Chunk2D.ReturnArray(arr);
+ var arr = chunk.GetDataArray();
+ if (!chunk.IsAllAir && arr != null)
+ {
+ Chunk2D.ReturnArray(arr);
+ }
+ ChunkResourceManager.ReleaseChunk(chunk);
+ ChunkEvents.OnChunkUnloaded(chunk);
}
- ChunkResourceManager.ReleaseChunk(chunk);
- ChunkEvents.OnChunkUnloaded(chunk);
return true;
}
return false;
@@ -152,5 +244,70 @@ namespace AdvChkSys.Manager
IChunk IChunkManager.LoadOrCreateChunk(int x, int y, int width, int height) => LoadOrCreateChunk(x, y, width, height);
bool IChunkManager.UnloadChunk(int x, int y) => UnloadChunk(x, y);
IEnumerable IChunkManager.GetAllChunks() => _chunks.Values;
+
+ ///
+ /// Sets a custom delegate for determining if a region should be all air.
+ ///
+ public void SetAirCheckDelegate(Func airCheckDelegate)
+ {
+ _airCheckDelegate = airCheckDelegate;
+ }
+
+ ///
+ /// Sets a world generator for determining empty regions.
+ ///
+ public void SetWorldGenerator(IWorldGenerator worldGenerator)
+ {
+ _worldGenerator = worldGenerator;
+ }
+
+ ///
+ /// Sets a data provider for determining empty regions.
+ ///
+ public void SetDataProvider(IDataProvider dataProvider)
+ {
+ _dataProvider = dataProvider;
+ }
+
+ ///
+ /// Sets a height map for determining sky chunks.
+ ///
+ public void SetHeightMap(IHeightMap heightMap)
+ {
+ _heightMap = heightMap;
+ }
+ }
+
+ ///
+ /// Interface for world generators that can determine if regions are empty.
+ ///
+ public interface IWorldGenerator
+ {
+ ///
+ /// Determines if a region is empty (all air).
+ ///
+ bool IsRegionEmpty(int x, int y, int width, int height);
+ }
+
+ ///
+ /// Interface for data providers that can determine if regions are empty.
+ ///
+ public interface IDataProvider
+ {
+ ///
+ /// Determines if a region is empty (all air).
+ ///
+ bool IsEmptyRegion(int x, int y, int width, int height);
+ }
+
+ ///
+ /// Interface for height maps that can determine the maximum height at a position.
+ ///
+ public interface IHeightMap
+ {
+ ///
+ /// Gets the maximum height at a position range.
+ ///
+ int GetMaxHeight(int startX, int endX);
}
}
\ No newline at end of file
diff --git a/src/AdvChkSys/Manager/ChunkManager3D.cs b/src/AdvChkSys/Manager/ChunkManager3D.cs
index 1c99b2a..37c629b 100644
--- a/src/AdvChkSys/Manager/ChunkManager3D.cs
+++ b/src/AdvChkSys/Manager/ChunkManager3D.cs
@@ -19,7 +19,32 @@ namespace AdvChkSys.Manager
private readonly LRUCache<(int, int, int), Chunk3D> _chunks;
private readonly WorldConstraints? _constraints;
private readonly int _capacity;
+
+ ///
+ /// Delegate for custom air check logic
+ ///
+ private Func? _airCheckDelegate;
+ ///
+ /// World generator for determining empty regions
+ ///
+ private IWorldGenerator3D? _worldGenerator;
+
+ ///
+ /// Data provider for determining empty regions
+ ///
+ private IDataProvider3D? _dataProvider;
+
+ ///
+ /// Height map for determining sky chunks
+ ///
+ private IHeightMap3D? _heightMap;
+
+ ///
+ /// Initializes a new instance of the 3D chunk manager.
+ ///
+ /// Optional world constraints
+ /// Maximum number of chunks to keep in memory
public ChunkManager3D(WorldConstraints? constraints = null, int capacity = 4096)
{
_constraints = constraints;
@@ -40,6 +65,31 @@ namespace AdvChkSys.Manager
_chunks.TryGet((x, y, z), out var chunk);
return chunk;
}
+
+ ///
+ /// Determines if a region should be represented as an all-air chunk.
+ ///
+ private bool ShouldBeAllAir(int x, int y, int z, int width, int height, int depth)
+ {
+ // Check if we have a custom air check delegate
+ if (_airCheckDelegate != null)
+ return _airCheckDelegate(x, y, z, width, height, depth);
+
+ // Check if we have a world generator
+ if (_worldGenerator != null)
+ return _worldGenerator.IsRegionEmpty(x, y, z, width, height, depth);
+
+ // Check if we have a data provider
+ if (_dataProvider != null)
+ return _dataProvider.IsEmptyRegion(x, y, z, width, height, depth);
+
+ // Check height-based air (for sky chunks)
+ if (_heightMap != null)
+ return y > _heightMap.GetMaxHeight(x, z, x + width, z + depth);
+
+ // Default to non-air to ensure data integrity
+ return false;
+ }
///
/// Loads or creates a chunk at (x, y, z) with the given size.
@@ -57,7 +107,12 @@ namespace AdvChkSys.Manager
if (!_chunks.TryGet((x, y, z), out var chunk))
{
- chunk = new Chunk3D(x, y, z, width, height, depth);
+ // If this region is all air, use the singleton
+ if (ShouldBeAllAir(x, y, z, width, height, depth))
+ chunk = Chunk3D.AllAir(x, y, z, width, height, depth);
+ else
+ chunk = new Chunk3D(x, y, z, width, height, depth);
+
_chunks.Add((x, y, z), chunk, OnChunkEvicted);
ChunkResourceManager.AllocateChunk(chunk);
ChunkEvents.OnChunkLoaded(chunk);
@@ -67,9 +122,24 @@ namespace AdvChkSys.Manager
private void OnChunkEvicted((int, int, int) key, Chunk3D chunk)
{
- ChunkResourceManager.ReleaseChunk(chunk);
- ChunkEvents.OnChunkUnloaded(chunk);
- // Optionally: compress or persist chunk here
+ if (chunk == null) return;
+
+ var arrField = chunk.GetType().GetField("_data", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+ T[,,]? arr = null;
+ if (arrField != null)
+ {
+ arr = arrField.GetValue(chunk) as T[,,];
+ }
+ if (!chunk.IsAllAir && arr != null)
+ {
+ Chunk3D.ReturnArray(arr);
+ }
+
+ if (chunk != null)
+ {
+ ChunkResourceManager.ReleaseChunk(chunk);
+ ChunkEvents.OnChunkUnloaded(chunk);
+ }
}
///
@@ -77,7 +147,7 @@ namespace AdvChkSys.Manager
///
public bool UnloadChunk(int x, int y, int z)
{
- if (_chunks.Remove((x, y, z), out var chunk))
+ if (_chunks.Remove((x, y, z), out var chunk) && chunk != null) // null check
{
ChunkResourceManager.ReleaseChunk(chunk);
ChunkEvents.OnChunkUnloaded(chunk);
@@ -98,5 +168,70 @@ namespace AdvChkSys.Manager
LoadOrCreateChunk(x, y, 0, width, height, 1);
bool IChunkManager.UnloadChunk(int x, int y) => UnloadChunk(x, y, 0);
IEnumerable IChunkManager.GetAllChunks() => _chunks.Values;
+
+ ///
+ /// Sets a custom delegate for determining if a region should be all air.
+ ///
+ public void SetAirCheckDelegate(Func airCheckDelegate)
+ {
+ _airCheckDelegate = airCheckDelegate;
+ }
+
+ ///
+ /// Sets a world generator for determining empty regions.
+ ///
+ public void SetWorldGenerator(IWorldGenerator3D worldGenerator)
+ {
+ _worldGenerator = worldGenerator;
+ }
+
+ ///
+ /// Sets a data provider for determining empty regions.
+ ///
+ public void SetDataProvider(IDataProvider3D dataProvider)
+ {
+ _dataProvider = dataProvider;
+ }
+
+ ///
+ /// Sets a height map for determining sky chunks.
+ ///
+ public void SetHeightMap(IHeightMap3D heightMap)
+ {
+ _heightMap = heightMap;
+ }
+ }
+
+ ///
+ /// Interface for 3D world generators that can determine if regions are empty.
+ ///
+ public interface IWorldGenerator3D
+ {
+ ///
+ /// Determines if a region is empty (all air).
+ ///
+ bool IsRegionEmpty(int x, int y, int z, int width, int height, int depth);
+ }
+
+ ///
+ /// Interface for 3D data providers that can determine if regions are empty.
+ ///
+ public interface IDataProvider3D
+ {
+ ///
+ /// Determines if a region is empty (all air).
+ ///
+ bool IsEmptyRegion(int x, int y, int z, int width, int height, int depth);
+ }
+
+ ///
+ /// Interface for 3D height maps that can determine the maximum height at a position.
+ ///
+ public interface IHeightMap3D
+ {
+ ///
+ /// Gets the maximum height at a position range.
+ ///
+ int GetMaxHeight(int startX, int startZ, int endX, int endZ);
}
}
\ No newline at end of file
diff --git a/src/AdvChkSys/Resources/ChunkResourceManager.cs b/src/AdvChkSys/Resources/ChunkResourceManager.cs
index bee1665..2617277 100644
--- a/src/AdvChkSys/Resources/ChunkResourceManager.cs
+++ b/src/AdvChkSys/Resources/ChunkResourceManager.cs
@@ -2,6 +2,7 @@
using AdvChkSys.Interfaces;
using System;
using System.Collections.Concurrent;
+using System.Collections.Generic;
namespace AdvChkSys.Resources
{
@@ -13,6 +14,9 @@ public static class ChunkResourceManager
{
// Example: Track allocated chunks (for diagnostics, pooling, or resource limits)
private static readonly ConcurrentDictionary _allocatedChunks = new ConcurrentDictionary();
+ private static int _allocatedChunkCount;
+ private static readonly object _lock = new object();
+ private static readonly HashSet _activeChunks = new();
///
/// Called when a chunk is allocated/loaded into memory.
@@ -20,8 +24,11 @@ public static class ChunkResourceManager
///
public static void AllocateChunk(IChunk chunk)
{
- _allocatedChunks[chunk] = DateTime.UtcNow;
- // Optionally: implement resource limits, pooling, or logging here.
+ lock (_lock)
+ {
+ _allocatedChunkCount++;
+ _activeChunks.Add(chunk);
+ }
}
///
@@ -30,8 +37,11 @@ public static class ChunkResourceManager
///
public static void ReleaseChunk(IChunk chunk)
{
- _allocatedChunks.TryRemove(chunk, out _);
- // Optionally: implement cleanup, pooling, or logging here.
+ lock (_lock)
+ {
+ _allocatedChunkCount--;
+ _activeChunks.Remove(chunk);
+ }
}
///
@@ -46,5 +56,17 @@ public static class ChunkResourceManager
{
_allocatedChunks.Clear();
}
+
+ ///
+ /// Gets the current count of active chunks being tracked by the resource manager.
+ ///
+ /// The number of active chunks
+ public static int GetActiveChunkCount()
+ {
+ lock (_lock)
+ {
+ return _activeChunks.Count;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/AdvChkSys/Serialization/ChunkSerializer.cs b/src/AdvChkSys/Serialization/ChunkSerializer.cs
index e6ca88e..d615a1c 100644
--- a/src/AdvChkSys/Serialization/ChunkSerializer.cs
+++ b/src/AdvChkSys/Serialization/ChunkSerializer.cs
@@ -5,14 +5,20 @@ using AdvChkSys.Chunk;
namespace AdvChkSys.Serialization
{
- //
- // Provides serialization and deserialization for chunk instances.
- // Supports Chunk2D and Chunk3D with primitive types (e.g., byte, int, float).
- //
+ ///
+ /// Provides serialization and deserialization for chunk instances.
+ /// Supports Chunk2D T and Chunk3D T with primitive types (e.g., byte, int, float).
+ /// .
public static class ChunkSerializer
{
// -------- 2D --------
+ ///
+ /// Serializes a 2D chunk to a byte array.
+ ///
+ /// The type of data stored in the chunk
+ /// The chunk to serialize
+ /// Serialized byte array
public static byte[] Serialize2D(Chunk2D chunk) where T : struct
{
using var ms = new MemoryStream();
@@ -38,6 +44,12 @@ namespace AdvChkSys.Serialization
return ms.ToArray();
}
+ ///
+ /// Deserializes a byte array into a 2D chunk.
+ ///
+ /// The type of data stored in the chunk
+ /// The serialized chunk data
+ /// Deserialized chunk
public static Chunk2D Deserialize2D(byte[] data) where T : struct
{
using var ms = new MemoryStream(data);
@@ -67,6 +79,12 @@ namespace AdvChkSys.Serialization
// -------- 3D --------
+ ///
+ /// Serializes a 3D chunk to a byte array.
+ ///
+ /// The type of data stored in the chunk
+ /// The chunk to serialize
+ /// Serialized byte array
public static byte[] Serialize3D(Chunk3D chunk) where T : struct
{
using var ms = new MemoryStream();
@@ -95,6 +113,12 @@ namespace AdvChkSys.Serialization
return ms.ToArray();
}
+ ///
+ /// Deserializes a byte array into a 3D chunk.
+ ///
+ /// The type of data stored in the chunk
+ /// The serialized chunk data
+ /// Deserialized chunk
public static Chunk3D Deserialize3D(byte[] data) where T : struct
{
using var ms = new MemoryStream(data);
diff --git a/src/AdvChkSys/Util/CacheCapacityHelper.cs b/src/AdvChkSys/Util/CacheCapacityHelper.cs
index f978bf2..fbce962 100644
--- a/src/AdvChkSys/Util/CacheCapacityHelper.cs
+++ b/src/AdvChkSys/Util/CacheCapacityHelper.cs
@@ -3,6 +3,9 @@ using System.Runtime.InteropServices;
namespace AdvChkSys.Util
{
+ ///
+ /// Provides utility methods for calculating optimal cache capacities based on system memory.
+ ///
public static class CacheCapacityHelper
{
///
diff --git a/src/AdvChkSys/Util/LRUCache.cs b/src/AdvChkSys/Util/LRUCache.cs
index ba4a985..b84a117 100644
--- a/src/AdvChkSys/Util/LRUCache.cs
+++ b/src/AdvChkSys/Util/LRUCache.cs
@@ -15,6 +15,10 @@ namespace AdvChkSys.Util
private readonly LinkedList<(TKey key, TValue value)> _lruList;
private readonly object _lock = new();
+ ///
+ /// Initializes a new instance of the LRU cache with the specified capacity.
+ ///
+ /// Maximum number of items to store in the cache
public LRUCache(int capacity)
{
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
@@ -23,6 +27,12 @@ namespace AdvChkSys.Util
_lruList = new LinkedList<(TKey, TValue)>();
}
+ ///
+ /// Attempts to retrieve a value from the cache by key.
+ ///
+ /// The key to look up
+ /// The retrieved value if found, default otherwise
+ /// True if the key was found, false otherwise
public bool TryGet(TKey key, out TValue value)
{
lock (_lock)
@@ -39,6 +49,12 @@ namespace AdvChkSys.Util
}
}
+ ///
+ /// Adds or updates a key-value pair in the cache.
+ ///
+ /// The key to add or update
+ /// The value to store
+ /// Optional callback when an item is evicted
public void Add(TKey key, TValue value, Action? onEvict = null)
{
lock (_lock)
@@ -68,6 +84,12 @@ namespace AdvChkSys.Util
}
}
+ ///
+ /// Removes a key-value pair from the cache.
+ ///
+ /// The key to remove
+ /// The removed value if found
+ /// True if the key was found and removed, false otherwise
public bool Remove(TKey key, out TValue? value)
{
lock (_lock)
@@ -84,6 +106,9 @@ namespace AdvChkSys.Util
}
}
+ ///
+ /// Gets all values currently in the cache.
+ ///
public IEnumerable Values
{
get
@@ -96,6 +121,9 @@ namespace AdvChkSys.Util
}
}
+ ///
+ /// Gets the current number of items in the cache.
+ ///
public int Count
{
get
diff --git a/src/AdvChkSys/Util/MemoryHelper.cs b/src/AdvChkSys/Util/MemoryHelper.cs
index 500de99..d72a330 100644
--- a/src/AdvChkSys/Util/MemoryHelper.cs
+++ b/src/AdvChkSys/Util/MemoryHelper.cs
@@ -3,8 +3,15 @@ using System.Runtime.InteropServices;
namespace AdvChkSys.Util
{
+ ///
+ /// Provides utility methods for querying system memory information.
+ ///
public static class MemoryHelper
{
+ ///
+ /// Gets the amount of available physical memory in bytes.
+ ///
+ /// Available memory in bytes
public static ulong GetAvailableMemoryBytes()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -75,7 +82,7 @@ namespace AdvChkSys.Util
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
- private struct MEMORYSTATUSEX
+ public struct MEMORYSTATUSEX
{
public uint dwLength;
public uint dwMemoryLoad;
@@ -89,6 +96,6 @@ namespace AdvChkSys.Util
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
+ public static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
}
}
\ No newline at end of file