updated memory memory handling to be more safe.

added some missing function.
added XML documentation formatting.
added MemoryUsageReporter to track memory usage.
added ChunkMark Benchmarking tool to benchmark the performance of the chunk manager.
added ChunkMark MemoryReporter to track memory usage.

fixed some bugs.
This commit is contained in:
Stan44 2025-05-10 02:36:54 -05:00
parent f1b3868d2c
commit becedccadd
18 changed files with 1948 additions and 94 deletions

5
.gitignore vendored
View File

@ -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

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>ChunkMark</RootNamespace>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AdvChkSys\AdvChkSys.csproj" />
</ItemGroup>
</Project>

View File

@ -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<BenchmarkResult> _results = new List<BenchmarkResult>();
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<byte>(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<byte>(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<byte>(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<byte>(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<byte>(null, 100);
var manager3D = new ChunkManager3D<byte>(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; }
}
}

View File

@ -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
{
/// <summary>
/// Provides detailed memory reporting and visualization for benchmarks.
/// </summary>
public class MemoryReporter
{
private readonly List<MemorySnapshot> _snapshots = new List<MemorySnapshot>();
private readonly Stopwatch _timer = new Stopwatch();
private readonly string _reportName;
public MemoryReporter(string reportName)
{
_reportName = reportName;
_timer.Start();
}
/// <summary>
/// Takes a memory snapshot with the given label.
/// </summary>
public void TakeSnapshot(string label)
{
var report = AdvChkSys.AdvChkSys.GetMemoryUsage();
_snapshots.Add(new MemorySnapshot
{
Label = label,
TimeSeconds = _timer.Elapsed.TotalSeconds,
Report = report
});
}
/// <summary>
/// Generates a detailed memory report as a string.
/// </summary>
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();
}
/// <summary>
/// Saves the memory report to a file.
/// </summary>
public void SaveReportToFile(string filePath)
{
string report = GenerateReport();
File.WriteAllText(filePath, report);
}
/// <summary>
/// Formats a byte size into a human-readable string (KB, MB, GB).
/// </summary>
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!;
}
}
}

View File

@ -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

View File

@ -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()

View File

@ -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<byte> Create3DManager(WorldConstraints? constraints = null) =>
new ChunkManager3D<byte>(constraints);
/// <summary>
/// Gets a detailed report of the current memory usage by the chunk system.
/// </summary>
/// <returns>A report containing memory usage statistics</returns>
public static MemoryUsageReport GetMemoryUsage() => MemoryUsageReporter.GetMemoryUsage();
/// <summary>
/// Logs the current memory usage to the provided logging action.
/// </summary>
/// <param name="logAction">Action that will receive the log messages</param>
public static void LogMemoryUsage(Action<string> logAction) =>
MemoryUsageReporter.LogMemoryUsage(logAction);
/// <summary>
/// Performs a basic self-test of the AdvChkSys core functionality.
/// Returns true if all core systems are operational.

View File

@ -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.
/// </summary>
public class Chunk2D<T> : IChunk
public class Chunk2D<T> : IChunk, IDisposable
{
// Flyweight: one all-air instance per size
private static readonly Dictionary<(int, int), Chunk2D<T>> _allAirChunks = new();
@ -36,12 +37,29 @@ namespace AdvChkSys.Chunk
private T[,]? _data;
// Properties required by IChunk
/// <summary>
/// The chunk's X position in chunk coordinates.
/// </summary>
public int X { get; private set; }
/// <summary>
/// The chunk's Y position in chunk coordinates.
/// </summary>
public int Y { get; private set; }
/// <summary>
/// The width of the chunk in cells.
/// </summary>
public int Width { get; }
/// <summary>
/// The height of the chunk in cells.
/// </summary>
public int Height { get; }
/// <summary>
/// Metadata dictionary for arbitrary chunk information.
/// </summary>
public Dictionary<string, object> Metadata { get; }
private bool _disposed;
/// <summary>
/// Normal constructor (private for singleton/factory).
/// </summary>
@ -70,11 +88,18 @@ namespace AdvChkSys.Chunk
/// </summary>
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);
}
/// <summary>
/// Disposes the chunk and returns its resources to the pool.
/// </summary>
public void Dispose()
{
if (!_disposed)
{
ReleaseDataArray();
_disposed = true;
}
}
}
}

View File

@ -1,68 +1,215 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using AdvChkSys.Interfaces;
namespace AdvChkSys.Chunk
{
/// <summary>
/// Represents a 3D chunk of data in the world.
/// Perspective-agnostic and supports arbitrary data types and metadata.
/// </summary>
public class Chunk3D<T> : IChunk
{
/// <summary>
/// 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.
/// </summary>
public int X { get; }
public int Y { get; }
public int Z { get; }
/// <summary>
/// The width, height, and depth of the chunk (in cells/tiles/units).
/// </summary>
public int Width { get; }
public int Height { get; }
public int Depth { get; }
/// <summary>
/// The chunk's data array.
/// </summary>
private readonly T[,,] _data;
/// <summary>
/// Metadata dictionary for arbitrary chunk information (e.g., biome, tags).
/// </summary>
public Dictionary<string, object> Metadata { get; }
public Chunk3D(int x, int y, int z, int width, int height, int depth)
public class Chunk3D<T> : IChunk, IDisposable
{
X = x;
Y = y;
Z = z;
Width = width;
Height = height;
Depth = depth;
_data = new T[width, height, depth];
Metadata = new Dictionary<string, object>();
}
// Array pool for chunk data arrays
private static readonly ConcurrentBag<T[,,]> _arrayPool = new();
// Flyweight: one all-air instance per size
private static readonly Dictionary<(int, int, int), Chunk3D<T>> _allAirChunks = new();
/// <summary>
/// Gets or sets the value at the given local chunk coordinates.
/// </summary>
public T this[int localX, int localY, int localZ]
{
get => _data[localX, localY, localZ];
set => _data[localX, localY, localZ] = value;
}
/// <summary>
/// Returns a singleton all-air chunk for the given size and position.
/// </summary>
public static Chunk3D<T> 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<T>(x, y, z, width, height, depth, isAllAir: true);
_allAirChunks[key] = chunk;
}
chunk.SetPosition(x, y, z); // Use explicit method for position update
return chunk;
}
/// <summary>
/// Fills the chunk with a specified value.
/// </summary>
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;
/// <summary>
/// The chunk's X position in chunk coordinates.
/// </summary>
public int X { get; private set; }
/// <summary>
/// The chunk's Y position in chunk coordinates.
/// </summary>
public int Y { get; private set; }
/// <summary>
/// The chunk's Z position in chunk coordinates.
/// </summary>
public int Z { get; private set; }
/// <summary>
/// The width of the chunk in cells.
/// </summary>
public int Width { get; }
/// <summary>
/// The height of the chunk in cells.
/// </summary>
public int Height { get; }
/// <summary>
/// The depth of the chunk in cells.
/// </summary>
public int Depth { get; }
/// <summary>
/// The chunk's data array.
/// </summary>
private readonly T[,,]? _data;
/// <summary>
/// Metadata dictionary for arbitrary chunk information (e.g., biome, tags).
/// </summary>
public Dictionary<string, object> Metadata { get; }
/// <summary>
/// Returns true if this chunk is the all-air singleton.
/// </summary>
public bool IsAllAir { get; private set; } = false;
/// <summary>
/// Tracks whether the chunk has been disposed.
/// </summary>
private bool _disposed;
/// <summary>
/// Creates a new 3D chunk at the specified position with the given dimensions.
/// </summary>
/// <param name="x">X coordinate in chunk space</param>
/// <param name="y">Y coordinate in chunk space</param>
/// <param name="z">Z coordinate in chunk space</param>
/// <param name="width">Width of the chunk in cells</param>
/// <param name="height">Height of the chunk in cells</param>
/// <param name="depth">Depth of the chunk in cells</param>
public Chunk3D(int x, int y, int z, int width, int height, int depth)
: this(x, y, z, width, height, depth, false)
{
}
/// <summary>
/// Creates a new 3D chunk at the specified position with the given dimensions.
/// </summary>
/// <param name="x">X coordinate in chunk space</param>
/// <param name="y">Y coordinate in chunk space</param>
/// <param name="z">Z coordinate in chunk space</param>
/// <param name="width">Width of the chunk in cells</param>
/// <param name="height">Height of the chunk in cells</param>
/// <param name="depth">Depth of the chunk in cells</param>
/// <param name="isAllAir">Whether this is an all-air chunk</param>
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<string, object>();
}
/// <summary>
/// Explicitly sets the chunk's position. Used internally for all-air singleton.
/// </summary>
internal void SetPosition(int x, int y, int z)
{
X = x;
Y = y;
Z = z;
}
/// <summary>
/// Gets or sets the value at the given local chunk coordinates.
/// </summary>
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;
}
}
/// <summary>
/// Fills the chunk with a specified value.
/// </summary>
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;
}
/// <summary>
/// Rents an array from the pool or creates a new one.
/// </summary>
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];
}
/// <summary>
/// Returns an array to the pool.
/// </summary>
internal static void ReturnArray(T[,,] arr)
{
_arrayPool.Add(arr);
}
/// <summary>
/// Returns the underlying data array (for pooling).
/// </summary>
internal T[,,]? GetDataArray() => _data;
/// <summary>
/// Releases the data array back to the pool.
/// </summary>
internal void ReleaseDataArray()
{
if (_data != null && !IsAllAir)
{
ReturnArray(_data);
}
}
/// <summary>
/// Disposes the chunk and returns its resources to the pool.
/// </summary>
public void Dispose()
{
if (!_disposed)
{
ReleaseDataArray();
_disposed = true;
}
}
}
}
}

View File

@ -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
{
/// <summary>
/// Provides memory usage statistics and reporting for the AdvChkSys library.
/// Helps client code monitor memory consumption of chunks and related resources.
/// </summary>
public static class MemoryUsageReporter
{
/// <summary>
/// Gets the current memory usage statistics for the AdvChkSys library.
/// </summary>
/// <returns>A MemoryUsageReport containing detailed memory statistics</returns>
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;
}
/// <summary>
/// Estimates the memory usage of chunks based on the active chunk count and average chunk size.
/// </summary>
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;
}
/// <summary>
/// Gets the total system memory in bytes.
/// </summary>
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
}
/// <summary>
/// Logs memory usage information to the provided action.
/// </summary>
/// <param name="logAction">Action to handle the log message</param>
public static void LogMemoryUsage(Action<string> 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("-------------------------------------------");
}
/// <summary>
/// Formats a byte size into a human-readable string (KB, MB, GB).
/// </summary>
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]}";
}
}
/// <summary>
/// Contains detailed memory usage statistics for the AdvChkSys library.
/// </summary>
public class MemoryUsageReport
{
/// <summary>
/// The number of active chunks currently being managed.
/// </summary>
public int ActiveChunkCount { get; internal set; }
/// <summary>
/// Estimated memory usage of all chunks in bytes.
/// </summary>
public ulong EstimatedChunkMemoryBytes { get; internal set; }
/// <summary>
/// Available system memory in bytes.
/// </summary>
public ulong AvailableSystemMemoryBytes { get; internal set; }
/// <summary>
/// Total system memory in bytes.
/// </summary>
public ulong TotalSystemMemoryBytes { get; internal set; }
/// <summary>
/// Timestamp when this report was generated.
/// </summary>
public DateTime Timestamp { get; internal set; }
/// <summary>
/// Percentage of total system memory used by chunks.
/// </summary>
public double MemoryUsagePercentage =>
TotalSystemMemoryBytes > 0
? (double)EstimatedChunkMemoryBytes / TotalSystemMemoryBytes * 100
: 0;
}
}

View File

@ -9,9 +9,13 @@ namespace AdvChkSys.Interfaces
public interface IChunk
{
/// <summary>
/// The chunk's position in chunk-space coordinates.
/// The chunk's X position in chunk-space coordinates.
/// </summary>
int X { get; }
/// <summary>
/// The chunk's Y position in chunk-space coordinates.
/// </summary>
int Y { get; }
/// <summary>

View File

@ -19,7 +19,36 @@ namespace AdvChkSys.Manager
private readonly LRUCache<(int, int), Chunk2D<T>> _chunks;
private readonly WorldConstraints? _constraints;
private readonly int _capacity;
private readonly Dictionary<(int, int), Task<Chunk2D<T>>> _loadingChunks = new();
private readonly object _lock = new object();
/// <summary>
/// Delegate for custom air check logic
/// </summary>
private Func<int, int, int, int, bool>? _airCheckDelegate;
/// <summary>
/// World generator for determining empty regions
/// </summary>
private IWorldGenerator? _worldGenerator;
/// <summary>
/// Data provider for determining empty regions
/// </summary>
private IDataProvider? _dataProvider;
/// <summary>
/// Height map for determining sky chunks
/// </summary>
private IHeightMap? _heightMap;
/// <summary>
/// Initializes a new instance of the 2D chunk manager.
/// </summary>
/// <param name="constraints">Optional world constraints</param>
/// <param name="capacity">Maximum number of chunks to keep in memory (0 for auto-calculation)</param>
/// <param name="chunkWidth">Default chunk width in cells</param>
/// <param name="chunkHeight">Default chunk height in cells</param>
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<T> 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<T>.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"
/// <summary>
/// Determines if a region should be represented as an all-air chunk.
/// </summary>
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
/// </summary>
public async Task<Chunk2D<T>> 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<Chunk2D<T>>? 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);
}
}
}
/// <summary>
@ -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<T>.ReturnArray(arr);
var arr = chunk.GetDataArray();
if (!chunk.IsAllAir && arr != null)
{
Chunk2D<T>.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<IChunk> IChunkManager.GetAllChunks() => _chunks.Values;
/// <summary>
/// Sets a custom delegate for determining if a region should be all air.
/// </summary>
public void SetAirCheckDelegate(Func<int, int, int, int, bool> airCheckDelegate)
{
_airCheckDelegate = airCheckDelegate;
}
/// <summary>
/// Sets a world generator for determining empty regions.
/// </summary>
public void SetWorldGenerator(IWorldGenerator worldGenerator)
{
_worldGenerator = worldGenerator;
}
/// <summary>
/// Sets a data provider for determining empty regions.
/// </summary>
public void SetDataProvider(IDataProvider dataProvider)
{
_dataProvider = dataProvider;
}
/// <summary>
/// Sets a height map for determining sky chunks.
/// </summary>
public void SetHeightMap(IHeightMap heightMap)
{
_heightMap = heightMap;
}
}
/// <summary>
/// Interface for world generators that can determine if regions are empty.
/// </summary>
public interface IWorldGenerator
{
/// <summary>
/// Determines if a region is empty (all air).
/// </summary>
bool IsRegionEmpty(int x, int y, int width, int height);
}
/// <summary>
/// Interface for data providers that can determine if regions are empty.
/// </summary>
public interface IDataProvider
{
/// <summary>
/// Determines if a region is empty (all air).
/// </summary>
bool IsEmptyRegion(int x, int y, int width, int height);
}
/// <summary>
/// Interface for height maps that can determine the maximum height at a position.
/// </summary>
public interface IHeightMap
{
/// <summary>
/// Gets the maximum height at a position range.
/// </summary>
int GetMaxHeight(int startX, int endX);
}
}

View File

@ -19,7 +19,32 @@ namespace AdvChkSys.Manager
private readonly LRUCache<(int, int, int), Chunk3D<T>> _chunks;
private readonly WorldConstraints? _constraints;
private readonly int _capacity;
/// <summary>
/// Delegate for custom air check logic
/// </summary>
private Func<int, int, int, int, int, int, bool>? _airCheckDelegate;
/// <summary>
/// World generator for determining empty regions
/// </summary>
private IWorldGenerator3D? _worldGenerator;
/// <summary>
/// Data provider for determining empty regions
/// </summary>
private IDataProvider3D? _dataProvider;
/// <summary>
/// Height map for determining sky chunks
/// </summary>
private IHeightMap3D? _heightMap;
/// <summary>
/// Initializes a new instance of the 3D chunk manager.
/// </summary>
/// <param name="constraints">Optional world constraints</param>
/// <param name="capacity">Maximum number of chunks to keep in memory</param>
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;
}
/// <summary>
/// Determines if a region should be represented as an all-air chunk.
/// </summary>
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;
}
/// <summary>
/// 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<T>(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<T>.AllAir(x, y, z, width, height, depth);
else
chunk = new Chunk3D<T>(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<T> 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<T>.ReturnArray(arr);
}
if (chunk != null)
{
ChunkResourceManager.ReleaseChunk(chunk);
ChunkEvents.OnChunkUnloaded(chunk);
}
}
/// <summary>
@ -77,7 +147,7 @@ namespace AdvChkSys.Manager
/// </summary>
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<IChunk> IChunkManager.GetAllChunks() => _chunks.Values;
/// <summary>
/// Sets a custom delegate for determining if a region should be all air.
/// </summary>
public void SetAirCheckDelegate(Func<int, int, int, int, int, int, bool> airCheckDelegate)
{
_airCheckDelegate = airCheckDelegate;
}
/// <summary>
/// Sets a world generator for determining empty regions.
/// </summary>
public void SetWorldGenerator(IWorldGenerator3D worldGenerator)
{
_worldGenerator = worldGenerator;
}
/// <summary>
/// Sets a data provider for determining empty regions.
/// </summary>
public void SetDataProvider(IDataProvider3D dataProvider)
{
_dataProvider = dataProvider;
}
/// <summary>
/// Sets a height map for determining sky chunks.
/// </summary>
public void SetHeightMap(IHeightMap3D heightMap)
{
_heightMap = heightMap;
}
}
/// <summary>
/// Interface for 3D world generators that can determine if regions are empty.
/// </summary>
public interface IWorldGenerator3D
{
/// <summary>
/// Determines if a region is empty (all air).
/// </summary>
bool IsRegionEmpty(int x, int y, int z, int width, int height, int depth);
}
/// <summary>
/// Interface for 3D data providers that can determine if regions are empty.
/// </summary>
public interface IDataProvider3D
{
/// <summary>
/// Determines if a region is empty (all air).
/// </summary>
bool IsEmptyRegion(int x, int y, int z, int width, int height, int depth);
}
/// <summary>
/// Interface for 3D height maps that can determine the maximum height at a position.
/// </summary>
public interface IHeightMap3D
{
/// <summary>
/// Gets the maximum height at a position range.
/// </summary>
int GetMaxHeight(int startX, int startZ, int endX, int endZ);
}
}

View File

@ -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<IChunk, DateTime> _allocatedChunks = new ConcurrentDictionary<IChunk, DateTime>();
private static int _allocatedChunkCount;
private static readonly object _lock = new object();
private static readonly HashSet<IChunk> _activeChunks = new();
/// <summary>
/// Called when a chunk is allocated/loaded into memory.
@ -20,8 +24,11 @@ public static class ChunkResourceManager
/// </summary>
public static void AllocateChunk(IChunk chunk)
{
_allocatedChunks[chunk] = DateTime.UtcNow;
// Optionally: implement resource limits, pooling, or logging here.
lock (_lock)
{
_allocatedChunkCount++;
_activeChunks.Add(chunk);
}
}
/// <summary>
@ -30,8 +37,11 @@ public static class ChunkResourceManager
/// </summary>
public static void ReleaseChunk(IChunk chunk)
{
_allocatedChunks.TryRemove(chunk, out _);
// Optionally: implement cleanup, pooling, or logging here.
lock (_lock)
{
_allocatedChunkCount--;
_activeChunks.Remove(chunk);
}
}
/// <summary>
@ -46,5 +56,17 @@ public static class ChunkResourceManager
{
_allocatedChunks.Clear();
}
/// <summary>
/// Gets the current count of active chunks being tracked by the resource manager.
/// </summary>
/// <returns>The number of active chunks</returns>
public static int GetActiveChunkCount()
{
lock (_lock)
{
return _activeChunks.Count;
}
}
}
}

View File

@ -5,14 +5,20 @@ using AdvChkSys.Chunk;
namespace AdvChkSys.Serialization
{
// <summary>
// Provides serialization and deserialization for chunk instances.
// Supports Chunk2D<T> and Chunk3D<T> with primitive types (e.g., byte, int, float).
// </summary>
/// <summary>
/// Provides serialization and deserialization for chunk instances.
/// Supports Chunk2D T and Chunk3D T with primitive types (e.g., byte, int, float).
/// </summary>.
public static class ChunkSerializer
{
// -------- 2D --------
/// <summary>
/// Serializes a 2D chunk to a byte array.
/// </summary>
/// <typeparam name="T">The type of data stored in the chunk</typeparam>
/// <param name="chunk">The chunk to serialize</param>
/// <returns>Serialized byte array</returns>
public static byte[] Serialize2D<T>(Chunk2D<T> chunk) where T : struct
{
using var ms = new MemoryStream();
@ -38,6 +44,12 @@ namespace AdvChkSys.Serialization
return ms.ToArray();
}
/// <summary>
/// Deserializes a byte array into a 2D chunk.
/// </summary>
/// <typeparam name="T">The type of data stored in the chunk</typeparam>
/// <param name="data">The serialized chunk data</param>
/// <returns>Deserialized chunk</returns>
public static Chunk2D<T> Deserialize2D<T>(byte[] data) where T : struct
{
using var ms = new MemoryStream(data);
@ -67,6 +79,12 @@ namespace AdvChkSys.Serialization
// -------- 3D --------
/// <summary>
/// Serializes a 3D chunk to a byte array.
/// </summary>
/// <typeparam name="T">The type of data stored in the chunk</typeparam>
/// <param name="chunk">The chunk to serialize</param>
/// <returns>Serialized byte array</returns>
public static byte[] Serialize3D<T>(Chunk3D<T> chunk) where T : struct
{
using var ms = new MemoryStream();
@ -95,6 +113,12 @@ namespace AdvChkSys.Serialization
return ms.ToArray();
}
/// <summary>
/// Deserializes a byte array into a 3D chunk.
/// </summary>
/// <typeparam name="T">The type of data stored in the chunk</typeparam>
/// <param name="data">The serialized chunk data</param>
/// <returns>Deserialized chunk</returns>
public static Chunk3D<T> Deserialize3D<T>(byte[] data) where T : struct
{
using var ms = new MemoryStream(data);

View File

@ -3,6 +3,9 @@ using System.Runtime.InteropServices;
namespace AdvChkSys.Util
{
/// <summary>
/// Provides utility methods for calculating optimal cache capacities based on system memory.
/// </summary>
public static class CacheCapacityHelper
{
/// <summary>

View File

@ -15,6 +15,10 @@ namespace AdvChkSys.Util
private readonly LinkedList<(TKey key, TValue value)> _lruList;
private readonly object _lock = new();
/// <summary>
/// Initializes a new instance of the LRU cache with the specified capacity.
/// </summary>
/// <param name="capacity">Maximum number of items to store in the cache</param>
public LRUCache(int capacity)
{
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
@ -23,6 +27,12 @@ namespace AdvChkSys.Util
_lruList = new LinkedList<(TKey, TValue)>();
}
/// <summary>
/// Attempts to retrieve a value from the cache by key.
/// </summary>
/// <param name="key">The key to look up</param>
/// <param name="value">The retrieved value if found, default otherwise</param>
/// <returns>True if the key was found, false otherwise</returns>
public bool TryGet(TKey key, out TValue value)
{
lock (_lock)
@ -39,6 +49,12 @@ namespace AdvChkSys.Util
}
}
/// <summary>
/// Adds or updates a key-value pair in the cache.
/// </summary>
/// <param name="key">The key to add or update</param>
/// <param name="value">The value to store</param>
/// <param name="onEvict">Optional callback when an item is evicted</param>
public void Add(TKey key, TValue value, Action<TKey, TValue>? onEvict = null)
{
lock (_lock)
@ -68,6 +84,12 @@ namespace AdvChkSys.Util
}
}
/// <summary>
/// Removes a key-value pair from the cache.
/// </summary>
/// <param name="key">The key to remove</param>
/// <param name="value">The removed value if found</param>
/// <returns>True if the key was found and removed, false otherwise</returns>
public bool Remove(TKey key, out TValue? value)
{
lock (_lock)
@ -84,6 +106,9 @@ namespace AdvChkSys.Util
}
}
/// <summary>
/// Gets all values currently in the cache.
/// </summary>
public IEnumerable<TValue> Values
{
get
@ -96,6 +121,9 @@ namespace AdvChkSys.Util
}
}
/// <summary>
/// Gets the current number of items in the cache.
/// </summary>
public int Count
{
get

View File

@ -3,8 +3,15 @@ using System.Runtime.InteropServices;
namespace AdvChkSys.Util
{
/// <summary>
/// Provides utility methods for querying system memory information.
/// </summary>
public static class MemoryHelper
{
/// <summary>
/// Gets the amount of available physical memory in bytes.
/// </summary>
/// <returns>Available memory in bytes</returns>
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);
}
}