From 01d2cbf4b3cbaaf4467204fc9a0a0ad9b508dfe9 Mon Sep 17 00:00:00 2001 From: Stan44 Date: Sun, 11 May 2025 22:47:43 -0500 Subject: [PATCH] fixed code formatter. minor bug fixes to a few systems as well. --- src/AdvChkSys/AdvChkSys.cs | 16 +- src/AdvChkSys/Chunk/Chunk2D.cs | 4 +- src/AdvChkSys/Chunk/Chunk3D.cs | 374 +++++++++--------- src/AdvChkSys/Constraints/WorldConstraints.cs | 114 +++--- .../Diagnostics/MemoryUsageReporter.cs | 20 +- src/AdvChkSys/Events/ChunkEvents.cs | 222 +++++------ src/AdvChkSys/Interfaces/IChunk.cs | 2 +- src/AdvChkSys/Interfaces/IChunkManager.cs | 52 +-- src/AdvChkSys/Manager/ChunkManager2D.cs | 26 +- src/AdvChkSys/Manager/ChunkManager3D.cs | 10 +- .../Resources/ChunkResourceManager.cs | 114 +++--- src/AdvChkSys/Spatial/ChunkExtensions.cs | 6 +- src/AdvChkSys/Spatial/SpatialChunkIndex.cs | 312 +++++++-------- src/AdvChkSys/Threading/ChunkAsyncLock.cs | 50 +-- .../Threading/ChunkOperationQueue.cs | 68 ++-- .../Threading/ChunkParallelProcessor.cs | 64 +-- src/AdvChkSys/Threading/ChunkTaskScheduler.cs | 72 ++-- .../Threading/ChunkTaskSchedulerExtensions.cs | 10 +- .../Threading/ChunkThreadSafetyManager.cs | 78 ++-- .../Threading/ChunkThreadingConfiguration.cs | 16 +- .../Threading/ChunkThreadingDiagnostics.cs | 62 +-- .../Threading/ChunkThreadingExtensions.cs | 80 ++-- .../Threading/ChunkThreadingExtensions2.cs | 48 +-- .../LimitedConcurrencyTaskScheduler.cs | 2 +- src/AdvChkSys/Util/LRUCache.cs | 4 +- 25 files changed, 914 insertions(+), 912 deletions(-) diff --git a/src/AdvChkSys/AdvChkSys.cs b/src/AdvChkSys/AdvChkSys.cs index c371024..9774819 100644 --- a/src/AdvChkSys/AdvChkSys.cs +++ b/src/AdvChkSys/AdvChkSys.cs @@ -27,19 +27,19 @@ namespace AdvChkSys /// /// Creates a new WorldConstraints object. /// - public static WorldConstraints CreateDefaultConstraints() => new WorldConstraints(); + public static WorldConstraints CreateDefaultConstraints() => new(); /// /// Creates a new 2D chunk manager with optional constraints. /// public static ChunkManager2D Create2DManager(WorldConstraints? constraints = null) => - new ChunkManager2D(constraints); + new(constraints); /// /// Creates a new 3D chunk manager with optional constraints. /// public static ChunkManager3D Create3DManager(WorldConstraints? constraints = null) => - new ChunkManager3D(constraints); + new(constraints); /// /// Gets a detailed report of the current memory usage by the chunk system. @@ -51,7 +51,7 @@ namespace AdvChkSys /// Logs the current memory usage to the provided logging action. /// /// Action that will receive the log messages - public static void LogMemoryUsage(Action logAction) => + public static void LogMemoryUsage(Action logAction) => MemoryUsageReporter.LogMemoryUsage(logAction); /// @@ -62,7 +62,7 @@ namespace AdvChkSys /// /// Runs a batch of chunk operations in parallel. /// - public static Task RunParallelChunkOperationsAsync(IEnumerable operations, + public static Task RunParallelChunkOperationsAsync(IEnumerable operations, int? maxDegreeOfParallelism = null, CancellationToken cancellationToken = default) { @@ -73,13 +73,13 @@ namespace AdvChkSys /// Creates a spatial index for 2D chunks. /// public static SpatialChunkIndex> CreateSpatialIndex2D() => - new SpatialChunkIndex>(); + new(); /// /// Creates a spatial index for 3D chunks. /// public static SpatialChunkIndex> CreateSpatialIndex3D() => - new SpatialChunkIndex>(); + new(); /// /// Configures the threading system for high throughput. @@ -155,7 +155,7 @@ namespace AdvChkSys manager2D.UnloadChunk(1, 1); manager3D.UnloadChunk(1, 1, 1); - return resourceTracking2D && resourceTracking3D && constraintsWork && + return resourceTracking2D && resourceTracking3D && constraintsWork && spatialIndexWorks && threadingWorks; } } diff --git a/src/AdvChkSys/Chunk/Chunk2D.cs b/src/AdvChkSys/Chunk/Chunk2D.cs index 31501e7..b04f260 100644 --- a/src/AdvChkSys/Chunk/Chunk2D.cs +++ b/src/AdvChkSys/Chunk/Chunk2D.cs @@ -91,13 +91,13 @@ namespace AdvChkSys.Chunk 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}]"); + 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}]"); + throw new ArgumentOutOfRangeException($"Coordinates ({localX}, {localY}) out of bounds [0-{Width - 1}, 0-{Height - 1}]"); if (_isAllAir) throw new InvalidOperationException("Cannot set cell in all-air chunk."); _data![localX, localY] = value; diff --git a/src/AdvChkSys/Chunk/Chunk3D.cs b/src/AdvChkSys/Chunk/Chunk3D.cs index eba7aff..4fe2300 100644 --- a/src/AdvChkSys/Chunk/Chunk3D.cs +++ b/src/AdvChkSys/Chunk/Chunk3D.cs @@ -6,210 +6,210 @@ 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, IDisposable + { + // 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(); + /// - /// Represents a 3D chunk of data in the world. - /// Perspective-agnostic and supports arbitrary data types and metadata. + /// Returns a singleton all-air chunk for the given size and position. /// - public class Chunk3D : IChunk, IDisposable + public static Chunk3D AllAir(int x, int y, int z, int width, int height, int depth) { - // 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(); - - /// - /// 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)) { - 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; + 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; + } - /// - /// 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 chunk's X position in chunk coordinates. + /// + public int X { 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 Y position in chunk coordinates. + /// + public int Y { get; private set; } - /// - /// The chunk's data array. - /// - private readonly T[,,]? _data; + /// + /// The chunk's Z position in chunk coordinates. + /// + public int Z { get; private set; } - /// - /// Metadata dictionary for arbitrary chunk information (e.g., biome, tags). - /// - public Dictionary Metadata { get; } + /// + /// The width of the chunk in cells. + /// + public int Width { 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; + /// + /// The height of the chunk in cells. + /// + public int Height { get; } - /// - /// 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; - } + /// + /// The depth of the chunk in cells. + /// + public int Depth { get; } - /// - /// 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; - } - } + /// + /// The chunk's data array. + /// + private readonly T[,,]? _data; - /// - /// Fills the chunk with a specified value. - /// - public void Fill(T value) + /// + /// 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) - 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; + return default!; + return _data![localX, localY, localZ]; } - - /// - /// Rents an array from the pool or creates a new one. - /// - private static T[,,] RentArray(int width, int height, int depth) + set { - 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; - } + 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/Constraints/WorldConstraints.cs b/src/AdvChkSys/Constraints/WorldConstraints.cs index 7b97766..5362272 100644 --- a/src/AdvChkSys/Constraints/WorldConstraints.cs +++ b/src/AdvChkSys/Constraints/WorldConstraints.cs @@ -1,67 +1,67 @@ using System; namespace AdvChkSys.Constraints -{ -/// -/// Represents constraints and limits for the chunk world. -/// Can be used to restrict world size, chunk counts, and other resource limits. -/// -public class WorldConstraints { /// - /// If set, the minimum allowed chunk X coordinate (inclusive). + /// Represents constraints and limits for the chunk world. + /// Can be used to restrict world size, chunk counts, and other resource limits. /// - public int? MinChunkX { get; set; } - - /// - /// If set, the maximum allowed chunk X coordinate (inclusive). - /// - public int? MaxChunkX { get; set; } - - /// - /// If set, the minimum allowed chunk Y coordinate (inclusive). - /// - public int? MinChunkY { get; set; } - - /// - /// If set, the maximum allowed chunk Y coordinate (inclusive). - /// - public int? MaxChunkY { get; set; } - - /// - /// If set, the minimum allowed chunk Z coordinate (inclusive). - /// - public int? MinChunkZ { get; set; } - - /// - /// If set, the maximum allowed chunk Z coordinate (inclusive). - /// - public int? MaxChunkZ { get; set; } - - /// - /// If set, the maximum number of chunks allowed to be loaded in memory at once. - /// - public int? MaxLoadedChunks { get; set; } - - /// - /// Checks if the given chunk coordinates are within the allowed world bounds. - /// - public bool IsWithinBounds(int chunkX, int chunkY) + public class WorldConstraints { - if (MinChunkX.HasValue && chunkX < MinChunkX.Value) return false; - if (MaxChunkX.HasValue && chunkX > MaxChunkX.Value) return false; - if (MinChunkY.HasValue && chunkY < MinChunkY.Value) return false; - if (MaxChunkY.HasValue && chunkY > MaxChunkY.Value) return false; - return true; - } + /// + /// If set, the minimum allowed chunk X coordinate (inclusive). + /// + public int? MinChunkX { get; set; } - /// - /// Checks if the current number of loaded chunks is within the allowed limit. - /// - public bool IsWithinChunkLimit(int loadedChunkCount) - { - if (MaxLoadedChunks.HasValue && loadedChunkCount > MaxLoadedChunks.Value) return false; - return true; + /// + /// If set, the maximum allowed chunk X coordinate (inclusive). + /// + public int? MaxChunkX { get; set; } + + /// + /// If set, the minimum allowed chunk Y coordinate (inclusive). + /// + public int? MinChunkY { get; set; } + + /// + /// If set, the maximum allowed chunk Y coordinate (inclusive). + /// + public int? MaxChunkY { get; set; } + + /// + /// If set, the minimum allowed chunk Z coordinate (inclusive). + /// + public int? MinChunkZ { get; set; } + + /// + /// If set, the maximum allowed chunk Z coordinate (inclusive). + /// + public int? MaxChunkZ { get; set; } + + /// + /// If set, the maximum number of chunks allowed to be loaded in memory at once. + /// + public int? MaxLoadedChunks { get; set; } + + /// + /// Checks if the given chunk coordinates are within the allowed world bounds. + /// + public bool IsWithinBounds(int chunkX, int chunkY) + { + if (MinChunkX.HasValue && chunkX < MinChunkX.Value) return false; + if (MaxChunkX.HasValue && chunkX > MaxChunkX.Value) return false; + if (MinChunkY.HasValue && chunkY < MinChunkY.Value) return false; + if (MaxChunkY.HasValue && chunkY > MaxChunkY.Value) return false; + return true; + } + + /// + /// Checks if the current number of loaded chunks is within the allowed limit. + /// + public bool IsWithinChunkLimit(int loadedChunkCount) + { + if (MaxLoadedChunks.HasValue && loadedChunkCount > MaxLoadedChunks.Value) return false; + return true; + } } -} } \ No newline at end of file diff --git a/src/AdvChkSys/Diagnostics/MemoryUsageReporter.cs b/src/AdvChkSys/Diagnostics/MemoryUsageReporter.cs index 2f7626c..ce42c3a 100644 --- a/src/AdvChkSys/Diagnostics/MemoryUsageReporter.cs +++ b/src/AdvChkSys/Diagnostics/MemoryUsageReporter.cs @@ -29,7 +29,7 @@ namespace AdvChkSys.Diagnostics // Calculate estimated chunk memory usage report.EstimatedChunkMemoryBytes = EstimateChunkMemoryUsage(report.ActiveChunkCount); - + return report; } @@ -51,14 +51,16 @@ namespace AdvChkSys.Diagnostics { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - var memStatus = new MemoryHelper.MEMORYSTATUSEX(); - memStatus.dwLength = (uint)Marshal.SizeOf(typeof(MemoryHelper.MEMORYSTATUSEX)); + var memStatus = new MemoryHelper.MEMORYSTATUSEX + { + 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 } @@ -70,7 +72,7 @@ namespace AdvChkSys.Diagnostics 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)}"); @@ -88,7 +90,7 @@ namespace AdvChkSys.Diagnostics string[] sizes = { "B", "KB", "MB", "GB", "TB" }; double formattedSize = bytes; int order = 0; - + while (formattedSize >= 1024 && order < sizes.Length - 1) { order++; @@ -132,9 +134,9 @@ namespace AdvChkSys.Diagnostics /// /// Percentage of total system memory used by chunks. /// - public double MemoryUsagePercentage => - TotalSystemMemoryBytes > 0 - ? (double)EstimatedChunkMemoryBytes / TotalSystemMemoryBytes * 100 + public double MemoryUsagePercentage => + TotalSystemMemoryBytes > 0 + ? (double)EstimatedChunkMemoryBytes / TotalSystemMemoryBytes * 100 : 0; } } \ No newline at end of file diff --git a/src/AdvChkSys/Events/ChunkEvents.cs b/src/AdvChkSys/Events/ChunkEvents.cs index 2980bca..5afd54e 100644 --- a/src/AdvChkSys/Events/ChunkEvents.cs +++ b/src/AdvChkSys/Events/ChunkEvents.cs @@ -4,123 +4,123 @@ using System.Threading; namespace AdvChkSys.Events { -/// -/// Provides events for chunk lifecycle operations such as loading, unloading, saving, and more. -/// Thread-safe event subscription and invocation. -/// -public static class ChunkEvents -{ - // Backing fields for thread-safe event handling - private static Action? _chunkLoaded; - private static Action? _chunkUnloaded; - private static Action? _chunkLoading; - private static Action? _chunkUnloading; - private static Action? _chunkSaving; - private static Action? _chunkSaved; - /// - /// Occurs when a chunk has been loaded into memory. + /// Provides events for chunk lifecycle operations such as loading, unloading, saving, and more. + /// Thread-safe event subscription and invocation. /// - public static event Action ChunkLoaded + public static class ChunkEvents { - add => AddHandler(ref _chunkLoaded, value); - remove => RemoveHandler(ref _chunkLoaded, value); - } + // Backing fields for thread-safe event handling + private static Action? _chunkLoaded; + private static Action? _chunkUnloaded; + private static Action? _chunkLoading; + private static Action? _chunkUnloading; + private static Action? _chunkSaving; + private static Action? _chunkSaved; - /// - /// Occurs when a chunk is about to be loaded into memory. - /// - public static event Action ChunkLoading - { - add => AddHandler(ref _chunkLoading, value); - remove => RemoveHandler(ref _chunkLoading, value); - } - - /// - /// Occurs when a chunk is about to be unloaded from memory. - /// - public static event Action ChunkUnloading - { - add => AddHandler(ref _chunkUnloading, value); - remove => RemoveHandler(ref _chunkUnloading, value); - } - - /// - /// Occurs when a chunk has been unloaded from memory. - /// - public static event Action ChunkUnloaded - { - add => AddHandler(ref _chunkUnloaded, value); - remove => RemoveHandler(ref _chunkUnloaded, value); - } - - /// - /// Occurs when a chunk is about to be saved. - /// - public static event Action ChunkSaving - { - add => AddHandler(ref _chunkSaving, value); - remove => RemoveHandler(ref _chunkSaving, value); - } - - /// - /// Occurs when a chunk has been saved. - /// - public static event Action ChunkSaved - { - add => AddHandler(ref _chunkSaved, value); - remove => RemoveHandler(ref _chunkSaved, value); - } - - /// - /// Raises the ChunkLoading event. - /// - public static void OnChunkLoading(Interfaces.IChunk chunk) => _chunkLoading?.Invoke(chunk); - - /// - /// Raises the ChunkLoaded event. - /// - public static void OnChunkLoaded(Interfaces.IChunk chunk) => _chunkLoaded?.Invoke(chunk); - - /// - /// Raises the ChunkUnloading event. - /// - public static void OnChunkUnloading(Interfaces.IChunk chunk) => _chunkUnloading?.Invoke(chunk); - - /// - /// Raises the ChunkUnloaded event. - /// - public static void OnChunkUnloaded(Interfaces.IChunk chunk) => _chunkUnloaded?.Invoke(chunk); - - /// - /// Raises the ChunkSaving event. - /// - public static void OnChunkSaving(Interfaces.IChunk chunk) => _chunkSaving?.Invoke(chunk); - - /// - /// Raises the ChunkSaved event. - /// - public static void OnChunkSaved(Interfaces.IChunk chunk) => _chunkSaved?.Invoke(chunk); - - // Thread-safe add/remove for event handlers - private static void AddHandler(ref Action? field, Action handler) - { - Action? prevHandler, newHandler; - do + /// + /// Occurs when a chunk has been loaded into memory. + /// + public static event Action ChunkLoaded { - prevHandler = field; - newHandler = (Action?)Delegate.Combine(prevHandler, handler); - } while (Interlocked.CompareExchange(ref field, newHandler, prevHandler) != prevHandler); - } + add => AddHandler(ref _chunkLoaded, value); + remove => RemoveHandler(ref _chunkLoaded, value); + } - private static void RemoveHandler(ref Action? field, Action handler) - { - Action? prevHandler, newHandler; - do + /// + /// Occurs when a chunk is about to be loaded into memory. + /// + public static event Action ChunkLoading { - prevHandler = field; - newHandler = (Action?)Delegate.Remove(prevHandler, handler); - } while (Interlocked.CompareExchange(ref field, newHandler, prevHandler) != prevHandler); + add => AddHandler(ref _chunkLoading, value); + remove => RemoveHandler(ref _chunkLoading, value); + } + + /// + /// Occurs when a chunk is about to be unloaded from memory. + /// + public static event Action ChunkUnloading + { + add => AddHandler(ref _chunkUnloading, value); + remove => RemoveHandler(ref _chunkUnloading, value); + } + + /// + /// Occurs when a chunk has been unloaded from memory. + /// + public static event Action ChunkUnloaded + { + add => AddHandler(ref _chunkUnloaded, value); + remove => RemoveHandler(ref _chunkUnloaded, value); + } + + /// + /// Occurs when a chunk is about to be saved. + /// + public static event Action ChunkSaving + { + add => AddHandler(ref _chunkSaving, value); + remove => RemoveHandler(ref _chunkSaving, value); + } + + /// + /// Occurs when a chunk has been saved. + /// + public static event Action ChunkSaved + { + add => AddHandler(ref _chunkSaved, value); + remove => RemoveHandler(ref _chunkSaved, value); + } + + /// + /// Raises the ChunkLoading event. + /// + public static void OnChunkLoading(Interfaces.IChunk chunk) => _chunkLoading?.Invoke(chunk); + + /// + /// Raises the ChunkLoaded event. + /// + public static void OnChunkLoaded(Interfaces.IChunk chunk) => _chunkLoaded?.Invoke(chunk); + + /// + /// Raises the ChunkUnloading event. + /// + public static void OnChunkUnloading(Interfaces.IChunk chunk) => _chunkUnloading?.Invoke(chunk); + + /// + /// Raises the ChunkUnloaded event. + /// + public static void OnChunkUnloaded(Interfaces.IChunk chunk) => _chunkUnloaded?.Invoke(chunk); + + /// + /// Raises the ChunkSaving event. + /// + public static void OnChunkSaving(Interfaces.IChunk chunk) => _chunkSaving?.Invoke(chunk); + + /// + /// Raises the ChunkSaved event. + /// + public static void OnChunkSaved(Interfaces.IChunk chunk) => _chunkSaved?.Invoke(chunk); + + // Thread-safe add/remove for event handlers + private static void AddHandler(ref Action? field, Action handler) + { + Action? prevHandler, newHandler; + do + { + prevHandler = field; + newHandler = (Action?)Delegate.Combine(prevHandler, handler); + } while (Interlocked.CompareExchange(ref field, newHandler, prevHandler) != prevHandler); + } + + private static void RemoveHandler(ref Action? field, Action handler) + { + Action? prevHandler, newHandler; + do + { + prevHandler = field; + newHandler = (Action?)Delegate.Remove(prevHandler, handler); + } while (Interlocked.CompareExchange(ref field, newHandler, prevHandler) != prevHandler); + } } -} } \ No newline at end of file diff --git a/src/AdvChkSys/Interfaces/IChunk.cs b/src/AdvChkSys/Interfaces/IChunk.cs index 59ea3eb..b83440e 100644 --- a/src/AdvChkSys/Interfaces/IChunk.cs +++ b/src/AdvChkSys/Interfaces/IChunk.cs @@ -12,7 +12,7 @@ namespace AdvChkSys.Interfaces /// The chunk's X position in chunk-space coordinates. /// int X { get; } - + /// /// The chunk's Y position in chunk-space coordinates. /// diff --git a/src/AdvChkSys/Interfaces/IChunkManager.cs b/src/AdvChkSys/Interfaces/IChunkManager.cs index 95fd641..dd097e9 100644 --- a/src/AdvChkSys/Interfaces/IChunkManager.cs +++ b/src/AdvChkSys/Interfaces/IChunkManager.cs @@ -2,37 +2,37 @@ using System.Collections.Generic; namespace AdvChkSys.Interfaces -{ -/// -/// Interface for managing chunks in memory. -/// Provides methods for loading, unloading, and accessing chunks. -/// -public interface IChunkManager { /// - /// Returns true if the chunk at (x, y) is loaded. + /// Interface for managing chunks in memory. + /// Provides methods for loading, unloading, and accessing chunks. /// - bool IsChunkLoaded(int x, int y); + public interface IChunkManager + { + /// + /// Returns true if the chunk at (x, y) is loaded. + /// + bool IsChunkLoaded(int x, int y); - /// - /// Gets the chunk at (x, y) if loaded, or null if not. - /// - IChunk? GetChunk(int x, int y); + /// + /// Gets the chunk at (x, y) if loaded, or null if not. + /// + IChunk? GetChunk(int x, int y); - /// - /// Loads or creates a chunk at (x, y) with the given size. - /// Returns the loaded or newly created chunk. - /// - IChunk LoadOrCreateChunk(int x, int y, int width, int height); + /// + /// Loads or creates a chunk at (x, y) with the given size. + /// Returns the loaded or newly created chunk. + /// + IChunk LoadOrCreateChunk(int x, int y, int width, int height); - /// - /// Unloads (removes) the chunk at (x, y) if loaded. - /// - bool UnloadChunk(int x, int y); + /// + /// Unloads (removes) the chunk at (x, y) if loaded. + /// + bool UnloadChunk(int x, int y); - /// - /// Enumerates all loaded chunks. - /// - IEnumerable GetAllChunks(); -} + /// + /// Enumerates all loaded chunks. + /// + IEnumerable GetAllChunks(); + } } \ No newline at end of file diff --git a/src/AdvChkSys/Manager/ChunkManager2D.cs b/src/AdvChkSys/Manager/ChunkManager2D.cs index 20ad0ee..df1aa92 100644 --- a/src/AdvChkSys/Manager/ChunkManager2D.cs +++ b/src/AdvChkSys/Manager/ChunkManager2D.cs @@ -20,23 +20,23 @@ namespace AdvChkSys.Manager private readonly WorldConstraints? _constraints; private readonly int _capacity; private readonly Dictionary<(int, int), Task>> _loadingChunks = new(); - private readonly object _lock = new object(); + private readonly object _lock = new(); /// /// 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 /// @@ -155,7 +155,7 @@ namespace AdvChkSys.Manager { var key = (x, y); Task>? task = null; - + // First check if we already have a task lock (_lock) { @@ -173,7 +173,7 @@ namespace AdvChkSys.Manager // Otherwise, create a new task outside the lock task = Task.Run(() => LoadOrCreateChunk(x, y, width, height)); - + // Register the task lock (_lock) { @@ -244,7 +244,7 @@ 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. /// @@ -252,7 +252,7 @@ namespace AdvChkSys.Manager { _airCheckDelegate = airCheckDelegate; } - + /// /// Sets a world generator for determining empty regions. /// @@ -260,7 +260,7 @@ namespace AdvChkSys.Manager { _worldGenerator = worldGenerator; } - + /// /// Sets a data provider for determining empty regions. /// @@ -268,7 +268,7 @@ namespace AdvChkSys.Manager { _dataProvider = dataProvider; } - + /// /// Sets a height map for determining sky chunks. /// @@ -277,7 +277,7 @@ namespace AdvChkSys.Manager _heightMap = heightMap; } } - + /// /// Interface for world generators that can determine if regions are empty. /// @@ -288,7 +288,7 @@ namespace AdvChkSys.Manager /// bool IsRegionEmpty(int x, int y, int width, int height); } - + /// /// Interface for data providers that can determine if regions are empty. /// @@ -299,7 +299,7 @@ namespace AdvChkSys.Manager /// bool IsEmptyRegion(int x, int y, int width, int height); } - + /// /// Interface for height maps that can determine the maximum height at a position. /// diff --git a/src/AdvChkSys/Manager/ChunkManager3D.cs b/src/AdvChkSys/Manager/ChunkManager3D.cs index 149aa20..23013c0 100644 --- a/src/AdvChkSys/Manager/ChunkManager3D.cs +++ b/src/AdvChkSys/Manager/ChunkManager3D.cs @@ -21,7 +21,7 @@ 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 /// @@ -67,7 +67,7 @@ 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. /// @@ -139,7 +139,7 @@ namespace AdvChkSys.Manager arr = arrField.GetValue(chunk) as T[,,]; } } - + if (!chunk.IsAllAir && arr != null) { Chunk3D.ReturnArray(arr); @@ -175,7 +175,7 @@ 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. /// @@ -208,7 +208,7 @@ namespace AdvChkSys.Manager _heightMap = heightMap; } } - + /// /// Interface for 3D world generators that can determine if regions are empty. /// diff --git a/src/AdvChkSys/Resources/ChunkResourceManager.cs b/src/AdvChkSys/Resources/ChunkResourceManager.cs index 2617277..f4793dd 100644 --- a/src/AdvChkSys/Resources/ChunkResourceManager.cs +++ b/src/AdvChkSys/Resources/ChunkResourceManager.cs @@ -6,67 +6,67 @@ using System.Collections.Generic; namespace AdvChkSys.Resources { -/// -/// Manages allocation and release of chunk resources in memory. -/// Can be extended to track resource usage, pooling, or implement custom memory strategies. -/// -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. - /// Tracks the chunk and can be extended for pooling or resource limits. + /// Manages allocation and release of chunk resources in memory. + /// Can be extended to track resource usage, pooling, or implement custom memory strategies. /// - public static void AllocateChunk(IChunk chunk) + public static class ChunkResourceManager { - lock (_lock) + // Example: Track allocated chunks (for diagnostics, pooling, or resource limits) + private static readonly ConcurrentDictionary _allocatedChunks = new(); + private static int _allocatedChunkCount; + private static readonly object _lock = new(); + private static readonly HashSet _activeChunks = new(); + + /// + /// Called when a chunk is allocated/loaded into memory. + /// Tracks the chunk and can be extended for pooling or resource limits. + /// + public static void AllocateChunk(IChunk chunk) { - _allocatedChunkCount++; - _activeChunks.Add(chunk); + lock (_lock) + { + _allocatedChunkCount++; + _activeChunks.Add(chunk); + } + } + + /// + /// Called when a chunk is released/unloaded from memory. + /// Removes the chunk from tracking and can be extended for pooling or cleanup. + /// + public static void ReleaseChunk(IChunk chunk) + { + lock (_lock) + { + _allocatedChunkCount--; + _activeChunks.Remove(chunk); + } + } + + /// + /// Gets the current number of allocated chunks. + /// + public static int AllocatedChunkCount => _allocatedChunks.Count; + + /// + /// Clears all tracked chunks (for diagnostics or shutdown). + /// + public static void Clear() + { + _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; + } } } - - /// - /// Called when a chunk is released/unloaded from memory. - /// Removes the chunk from tracking and can be extended for pooling or cleanup. - /// - public static void ReleaseChunk(IChunk chunk) - { - lock (_lock) - { - _allocatedChunkCount--; - _activeChunks.Remove(chunk); - } - } - - /// - /// Gets the current number of allocated chunks. - /// - public static int AllocatedChunkCount => _allocatedChunks.Count; - - /// - /// Clears all tracked chunks (for diagnostics or shutdown). - /// - public static void Clear() - { - _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/Spatial/ChunkExtensions.cs b/src/AdvChkSys/Spatial/ChunkExtensions.cs index 95d355d..c37ec4e 100644 --- a/src/AdvChkSys/Spatial/ChunkExtensions.cs +++ b/src/AdvChkSys/Spatial/ChunkExtensions.cs @@ -15,19 +15,19 @@ namespace AdvChkSys.Spatial { return new Chunk3DAdapter(chunk); } - + /// /// Adapter to make Chunk3D implement IChunk3D. /// private class Chunk3DAdapter : IChunk3D { private readonly Chunk3D _chunk; - + public Chunk3DAdapter(Chunk3D chunk) { _chunk = chunk; } - + public int X => _chunk.X; public int Y => _chunk.Y; public int Z => _chunk.Z; diff --git a/src/AdvChkSys/Spatial/SpatialChunkIndex.cs b/src/AdvChkSys/Spatial/SpatialChunkIndex.cs index dc92d0a..23183f0 100644 --- a/src/AdvChkSys/Spatial/SpatialChunkIndex.cs +++ b/src/AdvChkSys/Spatial/SpatialChunkIndex.cs @@ -16,13 +16,13 @@ namespace AdvChkSys.Spatial { // Grid-based spatial index for fast lookups private readonly Dictionary<(int, int), HashSet> _grid = new(); - + // All chunks in the index for iteration private readonly HashSet _allChunks = new(); - + // Lock for thread safety private readonly object _lock = new(); - + /// /// Adds a chunk to the spatial index. /// @@ -31,12 +31,12 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { // Add to all chunks set _allChunks.Add(chunk); - + // Add to grid cells var key = (chunk.X, chunk.Y); if (!_grid.TryGetValue(key, out var chunks)) @@ -47,7 +47,7 @@ namespace AdvChkSys.Spatial chunks.Add(chunk); } } - + /// /// Removes a chunk from the spatial index. /// @@ -57,12 +57,12 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { // Remove from all chunks set bool removed = _allChunks.Remove(chunk); - + // Remove from grid cells var key = (chunk.X, chunk.Y); if (_grid.TryGetValue(key, out var chunks)) @@ -73,11 +73,11 @@ namespace AdvChkSys.Spatial _grid.Remove(key); } } - + return removed; } } - + /// /// Finds all chunks within a rectangular region. /// @@ -91,7 +91,7 @@ namespace AdvChkSys.Spatial lock (_lock) { var result = new HashSet(); - + // Check each grid cell that overlaps with the region for (int x = minX; x <= maxX; x++) { @@ -107,11 +107,11 @@ namespace AdvChkSys.Spatial } } } - + return result; } } - + /// /// Finds all chunks within a specified distance from a point. /// @@ -125,13 +125,13 @@ namespace AdvChkSys.Spatial { var result = new HashSet(); float radiusSquared = radius * radius; - + // Calculate the bounding box of the circle int minX = (int)(centerX - radius); int minY = (int)(centerY - radius); int maxX = (int)(centerX + radius); int maxY = (int)(centerY + radius); - + // Check each grid cell that might overlap with the circle for (int x = minX; x <= maxX; x++) { @@ -146,7 +146,7 @@ namespace AdvChkSys.Spatial float dx = chunk.X - centerX; float dy = chunk.Y - centerY; float distanceSquared = dx * dx + dy * dy; - + // Check if the chunk is within the radius if (distanceSquared <= radiusSquared) { @@ -156,11 +156,11 @@ namespace AdvChkSys.Spatial } } } - + return result; } } - + /// /// Finds the nearest chunk to a point. /// @@ -173,27 +173,27 @@ namespace AdvChkSys.Spatial { if (_allChunks.Count == 0) return default; - + T? nearest = default; float nearestDistanceSquared = float.MaxValue; - + foreach (var chunk in _allChunks) { float dx = chunk.X - x; float dy = chunk.Y - y; float distanceSquared = dx * dx + dy * dy; - + if (distanceSquared < nearestDistanceSquared) { nearestDistanceSquared = distanceSquared; nearest = chunk; } } - + return nearest; } } - + /// /// Finds all chunks that contain a point. /// @@ -205,7 +205,7 @@ namespace AdvChkSys.Spatial lock (_lock) { var result = new HashSet(); - + // Get the grid cell for this point var key = (x, y); if (_grid.TryGetValue(key, out var chunks)) @@ -220,11 +220,11 @@ namespace AdvChkSys.Spatial } } } - + return result; } } - + /// /// Gets all chunks in the index. /// @@ -235,7 +235,7 @@ namespace AdvChkSys.Spatial return _allChunks.ToList(); } } - + /// /// Clears all chunks from the index. /// @@ -247,7 +247,7 @@ namespace AdvChkSys.Spatial _grid.Clear(); } } - + /// /// Gets the number of chunks in the index. /// @@ -261,7 +261,7 @@ namespace AdvChkSys.Spatial } } } - + /// /// Checks if a chunk is in the index. /// @@ -271,13 +271,13 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { return _allChunks.Contains(chunk); } } - + /// /// Updates the position of a chunk in the spatial index. /// @@ -289,13 +289,13 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { // Remove from old grid cell var oldKey = (oldX, oldY); bool removed = false; - + if (_grid.TryGetValue(oldKey, out var chunks)) { removed = chunks.Remove(chunk); @@ -304,10 +304,10 @@ namespace AdvChkSys.Spatial _grid.Remove(oldKey); } } - + if (!removed) return false; - + // Add to new grid cell var newKey = (chunk.X, chunk.Y); if (!_grid.TryGetValue(newKey, out var newChunks)) @@ -316,11 +316,11 @@ namespace AdvChkSys.Spatial _grid[newKey] = newChunks; } newChunks.Add(chunk); - + return true; } } - + /// /// Performs a spatial query using a custom filter. /// @@ -330,13 +330,13 @@ namespace AdvChkSys.Spatial { if (filter == null) throw new ArgumentNullException(nameof(filter)); - + lock (_lock) { return _allChunks.Where(filter).ToList(); } } - + /// /// Finds all chunks that intersect with a given chunk. /// @@ -346,14 +346,14 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + return FindChunksInRegion( - chunk.X, - chunk.Y, - chunk.X + chunk.Width - 1, + chunk.X, + chunk.Y, + chunk.X + chunk.Width - 1, chunk.Y + chunk.Height - 1); } - + /// /// Finds all chunks that are neighbors of a given chunk. /// @@ -364,17 +364,17 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { var result = new HashSet(); - + // Check the four adjacent cells CheckNeighbor(chunk.X - 1, chunk.Y, result); CheckNeighbor(chunk.X + 1, chunk.Y, result); CheckNeighbor(chunk.X, chunk.Y - 1, result); CheckNeighbor(chunk.X, chunk.Y + 1, result); - + // Check diagonal cells if requested if (includeDiagonals) { @@ -383,11 +383,11 @@ namespace AdvChkSys.Spatial CheckNeighbor(chunk.X - 1, chunk.Y + 1, result); CheckNeighbor(chunk.X + 1, chunk.Y + 1, result); } - + return result; } } - + /// /// Helper method to check for chunks at a specific grid cell. /// @@ -402,7 +402,7 @@ namespace AdvChkSys.Spatial } } } - + /// /// Performs a spatial operation on chunks in parallel. /// @@ -417,21 +417,21 @@ namespace AdvChkSys.Spatial { if (operation == null) throw new ArgumentNullException(nameof(operation)); - + IEnumerable chunksToProcess; - + lock (_lock) { - chunksToProcess = filter != null + chunksToProcess = filter != null ? _allChunks.Where(filter).ToList() : _allChunks.ToList(); } - + return ChunkTaskScheduler.RunBatchParallelAsync( chunksToProcess.Select(chunk => new Action(() => operation(chunk))).ToArray(), maxDegreeOfParallelism); } - + /// /// Finds all chunks in a region and performs an operation on them in parallel. /// @@ -449,14 +449,14 @@ namespace AdvChkSys.Spatial { if (operation == null) throw new ArgumentNullException(nameof(operation)); - + var chunksInRegion = FindChunksInRegion(minX, minY, maxX, maxY).ToList(); - + return ChunkTaskScheduler.RunBatchParallelAsync( chunksInRegion.Select(chunk => new Action(() => operation(chunk))).ToArray(), maxDegreeOfParallelism); } - + /// /// Creates a quadtree-based spatial index for more efficient queries. /// @@ -468,7 +468,7 @@ namespace AdvChkSys.Spatial public QuadtreeSpatialIndex CreateQuadtreeIndex(int minX, int minY, int maxX, int maxY) { var quadtree = new QuadtreeSpatialIndex(minX, minY, maxX, maxY); - + lock (_lock) { foreach (var chunk in _allChunks) @@ -476,11 +476,11 @@ namespace AdvChkSys.Spatial quadtree.AddChunk(chunk); } } - + return quadtree; } } - + /// /// Provides a quadtree-based spatial index for more efficient region queries. /// @@ -489,7 +489,7 @@ namespace AdvChkSys.Spatial private readonly QuadtreeNode _root; private readonly HashSet _allChunks = new(); private readonly object _lock = new(); - + /// /// Initializes a new instance of the QuadtreeSpatialIndex class. /// @@ -501,7 +501,7 @@ namespace AdvChkSys.Spatial { _root = new QuadtreeNode(minX, minY, maxX, maxY); } - + /// /// Adds a chunk to the quadtree. /// @@ -510,14 +510,14 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { _allChunks.Add(chunk); _root.Insert(chunk); } } - + /// /// Removes a chunk from the quadtree. /// @@ -527,7 +527,7 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { bool removed = _allChunks.Remove(chunk); @@ -538,7 +538,7 @@ namespace AdvChkSys.Spatial return removed; } } - + /// /// Finds all chunks within a rectangular region. /// @@ -556,7 +556,7 @@ namespace AdvChkSys.Spatial return result; } } - + /// /// Gets all chunks in the quadtree. /// @@ -567,7 +567,7 @@ namespace AdvChkSys.Spatial return _allChunks.ToList(); } } - + /// /// Clears all chunks from the quadtree. /// @@ -579,7 +579,7 @@ namespace AdvChkSys.Spatial _root.Clear(); } } - + /// /// Gets the number of chunks in the quadtree. /// @@ -593,7 +593,7 @@ namespace AdvChkSys.Spatial } } } - + /// /// Represents a node in the quadtree. /// @@ -601,22 +601,22 @@ namespace AdvChkSys.Spatial { // Boundary of this node private readonly int _minX, _minY, _maxX, _maxY; - + // Children nodes (NW, NE, SW, SE) private QuadtreeNode? _northWest; private QuadtreeNode? _northEast; private QuadtreeNode? _southWest; private QuadtreeNode? _southEast; - + // Chunks in this node private readonly HashSet _chunks = new(); - + // Maximum number of chunks before splitting private const int MAX_CHUNKS = 8; - + // Minimum size of a node private const int MIN_SIZE = 1; - + /// /// Initializes a new instance of the QuadtreeNode class. /// @@ -627,7 +627,7 @@ namespace AdvChkSys.Spatial _maxX = maxX; _maxY = maxY; } - + /// /// Inserts a chunk into the quadtree. /// @@ -636,27 +636,27 @@ namespace AdvChkSys.Spatial // Check if the chunk is within this node's boundary if (!Intersects(chunk)) return; - + // If we haven't split yet and have room, add the chunk to this node if (_northWest == null && _chunks.Count < MAX_CHUNKS) { _chunks.Add(chunk); return; } - + // If we haven't split yet but need to, split the node if (_northWest == null) { Split(); } - + // Try to insert the chunk into the children _northWest?.Insert(chunk); _northEast?.Insert(chunk); _southWest?.Insert(chunk); _southEast?.Insert(chunk); } - + /// /// Removes a chunk from the quadtree. /// @@ -665,10 +665,10 @@ namespace AdvChkSys.Spatial // Check if the chunk is within this node's boundary if (!Intersects(chunk)) return false; - + // Try to remove from this node bool removed = _chunks.Remove(chunk); - + // If we have children, try to remove from them too if (_northWest != null) { @@ -677,10 +677,10 @@ namespace AdvChkSys.Spatial removed |= _southWest!.Remove(chunk); removed |= _southEast!.Remove(chunk); } - + return removed; } - + /// /// Queries the quadtree for chunks in a region. /// @@ -689,7 +689,7 @@ namespace AdvChkSys.Spatial // Check if the query region intersects this node if (maxX < _minX || minX > _maxX || maxY < _minY || minY > _maxY) return; - + // Add chunks from this node that intersect the query region foreach (var chunk in _chunks) { @@ -699,7 +699,7 @@ namespace AdvChkSys.Spatial result.Add(chunk); } } - + // If we have children, query them too if (_northWest != null) { @@ -709,7 +709,7 @@ namespace AdvChkSys.Spatial _southEast!.Query(minX, minY, maxX, maxY, result); } } - + /// /// Splits this node into four children. /// @@ -717,51 +717,51 @@ namespace AdvChkSys.Spatial { int midX = (_minX + _maxX) / 2; int midY = (_minY + _maxY) / 2; - + // Don't split if the node is too small if (midX == _minX || midY == _minY) return; - + _northWest = new QuadtreeNode(_minX, _minY, midX, midY); _northEast = new QuadtreeNode(midX, _minY, _maxX, midY); _southWest = new QuadtreeNode(_minX, midY, midX, _maxY); _southEast = new QuadtreeNode(midX, midY, _maxX, _maxY); - + // Redistribute chunks to children var chunksToRedistribute = new List(_chunks); _chunks.Clear(); - + foreach (var chunk in chunksToRedistribute) { Insert(chunk); } } - + /// /// Checks if a chunk intersects with this node's boundary. /// private bool Intersects(T chunk) { - return chunk.X <= _maxX && - chunk.X + chunk.Width >= _minX && - chunk.Y <= _maxY && + return chunk.X <= _maxX && + chunk.X + chunk.Width >= _minX && + chunk.Y <= _maxY && chunk.Y + chunk.Height >= _minY; } - + /// /// Clears all chunks from this node and its children. /// public void Clear() { _chunks.Clear(); - + if (_northWest != null) { _northWest.Clear(); _northEast!.Clear(); _southWest!.Clear(); _southEast!.Clear(); - + _northWest = null; _northEast = null; _southWest = null; @@ -770,7 +770,7 @@ namespace AdvChkSys.Spatial } } } - + /// /// Provides a spatial index for 3D chunks. /// @@ -778,13 +778,13 @@ namespace AdvChkSys.Spatial { // Grid-based spatial index for fast lookups private readonly Dictionary<(int, int, int), HashSet> _grid = new(); - + // All chunks in the index for iteration private readonly HashSet _allChunks = new(); - + // Lock for thread safety private readonly object _lock = new(); - + /// /// Adds a chunk to the spatial index. /// @@ -793,12 +793,12 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { // Add to all chunks set _allChunks.Add(chunk); - + // Add to grid cells var key = (chunk.X, chunk.Y, chunk.Z); if (!_grid.TryGetValue(key, out var chunks)) @@ -809,7 +809,7 @@ namespace AdvChkSys.Spatial chunks.Add(chunk); } } - + /// /// Removes a chunk from the spatial index. /// @@ -819,12 +819,12 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { // Remove from all chunks set bool removed = _allChunks.Remove(chunk); - + // Remove from grid cells var key = (chunk.X, chunk.Y, chunk.Z); if (_grid.TryGetValue(key, out var chunks)) @@ -835,11 +835,11 @@ namespace AdvChkSys.Spatial _grid.Remove(key); } } - + return removed; } } - + /// /// Finds all chunks within a rectangular region. /// @@ -855,7 +855,7 @@ namespace AdvChkSys.Spatial lock (_lock) { var result = new HashSet(); - + // Check each grid cell that overlaps with the region for (int x = minX; x <= maxX; x++) { @@ -874,11 +874,11 @@ namespace AdvChkSys.Spatial } } } - + return result; } } - + /// /// Finds all chunks within a specified distance from a point. /// @@ -893,7 +893,7 @@ namespace AdvChkSys.Spatial { var result = new HashSet(); float radiusSquared = radius * radius; - + // Calculate the bounding box of the sphere int minX = (int)(centerX - radius); int minY = (int)(centerY - radius); @@ -901,7 +901,7 @@ namespace AdvChkSys.Spatial int maxX = (int)(centerX + radius); int maxY = (int)(centerY + radius); int maxZ = (int)(centerZ + radius); - + // Check each grid cell that might overlap with the sphere for (int x = minX; x <= maxX; x++) { @@ -919,7 +919,7 @@ namespace AdvChkSys.Spatial float dy = chunk.Y - centerY; float dz = chunk.Z - centerZ; float distanceSquared = dx * dx + dy * dy + dz * dz; - + // Check if the chunk is within the radius if (distanceSquared <= radiusSquared) { @@ -930,11 +930,11 @@ namespace AdvChkSys.Spatial } } } - + return result; } } - + /// /// Finds the nearest chunk to a point. /// @@ -948,28 +948,28 @@ namespace AdvChkSys.Spatial { if (_allChunks.Count == 0) return default; - + T? nearest = default; float nearestDistanceSquared = float.MaxValue; - + foreach (var chunk in _allChunks) { float dx = chunk.X - x; float dy = chunk.Y - y; float dz = chunk.Z - z; float distanceSquared = dx * dx + dy * dy + dz * dz; - + if (distanceSquared < nearestDistanceSquared) { nearestDistanceSquared = distanceSquared; nearest = chunk; } } - + return nearest; } } - + /// /// Gets all chunks in the index. /// @@ -980,7 +980,7 @@ namespace AdvChkSys.Spatial return _allChunks.ToList(); } } - + /// /// Clears all chunks from the index. /// @@ -992,7 +992,7 @@ namespace AdvChkSys.Spatial _grid.Clear(); } } - + /// /// Gets the number of chunks in the index. /// @@ -1006,7 +1006,7 @@ namespace AdvChkSys.Spatial } } } - + /// /// Checks if a chunk is in the index. /// @@ -1016,13 +1016,13 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { return _allChunks.Contains(chunk); } } - + /// /// Updates the position of a chunk in the spatial index. /// @@ -1035,13 +1035,13 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { // Remove from old grid cell var oldKey = (oldX, oldY, oldZ); bool removed = false; - + if (_grid.TryGetValue(oldKey, out var chunks)) { removed = chunks.Remove(chunk); @@ -1050,10 +1050,10 @@ namespace AdvChkSys.Spatial _grid.Remove(oldKey); } } - + if (!removed) return false; - + // Add to new grid cell var newKey = (chunk.X, chunk.Y, chunk.Z); if (!_grid.TryGetValue(newKey, out var newChunks)) @@ -1062,11 +1062,11 @@ namespace AdvChkSys.Spatial _grid[newKey] = newChunks; } newChunks.Add(chunk); - + return true; } } - + /// /// Performs a spatial query using a custom filter. /// @@ -1076,13 +1076,13 @@ namespace AdvChkSys.Spatial { if (filter == null) throw new ArgumentNullException(nameof(filter)); - + lock (_lock) { return _allChunks.Where(filter).ToList(); } } - + /// /// Finds all chunks that intersect with a given chunk. /// @@ -1092,16 +1092,16 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + return FindChunksInRegion( - chunk.X, - chunk.Y, + chunk.X, + chunk.Y, chunk.Z, - chunk.X + chunk.Width - 1, + chunk.X + chunk.Width - 1, chunk.Y + chunk.Height - 1, chunk.Z + chunk.Depth - 1); } - + /// /// Finds all chunks that are neighbors of a given chunk. /// @@ -1113,11 +1113,11 @@ namespace AdvChkSys.Spatial { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + lock (_lock) { var result = new HashSet(); - + // Check the six adjacent cells (faces) CheckNeighbor(chunk.X - 1, chunk.Y, chunk.Z, result); CheckNeighbor(chunk.X + 1, chunk.Y, chunk.Z, result); @@ -1125,7 +1125,7 @@ namespace AdvChkSys.Spatial CheckNeighbor(chunk.X, chunk.Y + 1, chunk.Z, result); CheckNeighbor(chunk.X, chunk.Y, chunk.Z - 1, result); CheckNeighbor(chunk.X, chunk.Y, chunk.Z + 1, result); - + // Check edge neighbors if requested if (includeEdges) { @@ -1134,20 +1134,20 @@ namespace AdvChkSys.Spatial CheckNeighbor(chunk.X + 1, chunk.Y - 1, chunk.Z, result); CheckNeighbor(chunk.X - 1, chunk.Y + 1, chunk.Z, result); CheckNeighbor(chunk.X + 1, chunk.Y + 1, chunk.Z, result); - + // X-Z edges CheckNeighbor(chunk.X - 1, chunk.Y, chunk.Z - 1, result); CheckNeighbor(chunk.X + 1, chunk.Y, chunk.Z - 1, result); CheckNeighbor(chunk.X - 1, chunk.Y, chunk.Z + 1, result); CheckNeighbor(chunk.X + 1, chunk.Y, chunk.Z + 1, result); - + // Y-Z edges CheckNeighbor(chunk.X, chunk.Y - 1, chunk.Z - 1, result); CheckNeighbor(chunk.X, chunk.Y + 1, chunk.Z - 1, result); CheckNeighbor(chunk.X, chunk.Y - 1, chunk.Z + 1, result); CheckNeighbor(chunk.X, chunk.Y + 1, chunk.Z + 1, result); } - + // Check diagonal corners if requested if (includeDiagonals) { @@ -1160,11 +1160,11 @@ namespace AdvChkSys.Spatial CheckNeighbor(chunk.X - 1, chunk.Y + 1, chunk.Z + 1, result); CheckNeighbor(chunk.X + 1, chunk.Y + 1, chunk.Z + 1, result); } - + return result; } } - + /// /// Helper method to check for chunks at a specific grid cell. /// @@ -1179,7 +1179,7 @@ namespace AdvChkSys.Spatial } } } - + /// /// Performs a spatial operation on chunks in parallel. /// @@ -1194,22 +1194,22 @@ namespace AdvChkSys.Spatial { if (operation == null) throw new ArgumentNullException(nameof(operation)); - + IEnumerable chunksToProcess; - + lock (_lock) { - chunksToProcess = filter != null + chunksToProcess = filter != null ? _allChunks.Where(filter).ToList() : _allChunks.ToList(); } - + return ChunkTaskScheduler.RunBatchParallelAsync( chunksToProcess.Select(chunk => new Action(() => operation(chunk))).ToArray(), maxDegreeOfParallelism); } } - + /// /// Interface for 3D chunks. /// @@ -1219,7 +1219,7 @@ namespace AdvChkSys.Spatial /// The chunk's Z position in chunk coordinates. /// int Z { get; } - + /// /// The depth of the chunk (in cells/tiles/units). /// diff --git a/src/AdvChkSys/Threading/ChunkAsyncLock.cs b/src/AdvChkSys/Threading/ChunkAsyncLock.cs index 27c9088..58ac3e1 100644 --- a/src/AdvChkSys/Threading/ChunkAsyncLock.cs +++ b/src/AdvChkSys/Threading/ChunkAsyncLock.cs @@ -13,16 +13,16 @@ namespace AdvChkSys.Threading { // Lock objects for each chunk private readonly ConcurrentDictionary _locks = new(); - + // Timer for cleanup private readonly Timer _cleanupTimer; - + // Last access time for each lock private readonly ConcurrentDictionary _lastAccessTime = new(); - + // Cleanup interval in minutes private readonly int _cleanupIntervalMinutes; - + /// /// Initializes a new instance of the ChunkAsyncLock class. /// @@ -30,11 +30,11 @@ namespace AdvChkSys.Threading public ChunkAsyncLock(int cleanupIntervalMinutes = 10) { _cleanupIntervalMinutes = cleanupIntervalMinutes; - _cleanupTimer = new Timer(CleanupUnusedLocks, null, - TimeSpan.FromMinutes(cleanupIntervalMinutes), + _cleanupTimer = new Timer(CleanupUnusedLocks, null, + TimeSpan.FromMinutes(cleanupIntervalMinutes), TimeSpan.FromMinutes(cleanupIntervalMinutes)); } - + /// /// Acquires a lock on a chunk asynchronously. /// @@ -45,7 +45,7 @@ namespace AdvChkSys.Threading { var semaphore = _locks.GetOrAdd(chunk, _ => new SemaphoreSlim(1, 1)); _lastAccessTime[chunk] = DateTime.UtcNow; - + try { await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -60,13 +60,13 @@ namespace AdvChkSys.Threading _lastAccessTime.TryRemove(chunk, out _); semaphore.Dispose(); } - + throw; } - + return new LockReleaser(semaphore, chunk, this); } - + /// /// Tries to acquire a lock on a chunk asynchronously with a timeout. /// @@ -75,24 +75,24 @@ namespace AdvChkSys.Threading /// Cancellation token /// A tuple with a boolean indicating success and the lock releaser if successful public async Task<(bool Success, IDisposable? LockReleaser)> TryLockAsync( - IChunk chunk, - TimeSpan timeout, + IChunk chunk, + TimeSpan timeout, CancellationToken cancellationToken = default) { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + var semaphore = _locks.GetOrAdd(chunk, _ => new SemaphoreSlim(1, 1)); _lastAccessTime[chunk] = DateTime.UtcNow; - + if (await semaphore.WaitAsync(timeout, cancellationToken).ConfigureAwait(false)) { return (true, new LockReleaser(semaphore, chunk, this)); } - + return (false, null); } - + /// /// Cleans up unused locks. /// @@ -100,10 +100,10 @@ namespace AdvChkSys.Threading { var now = DateTime.UtcNow; var threshold = now.AddMinutes(-_cleanupIntervalMinutes); - + foreach (var chunk in _lastAccessTime.Keys) { - if (_lastAccessTime.TryGetValue(chunk, out var lastAccess) && + if (_lastAccessTime.TryGetValue(chunk, out var lastAccess) && lastAccess < threshold && _locks.TryGetValue(chunk, out var semaphore)) { @@ -119,23 +119,23 @@ namespace AdvChkSys.Threading } } } - + /// /// Disposes resources. /// public void Dispose() { _cleanupTimer.Dispose(); - + foreach (var semaphore in _locks.Values) { semaphore.Dispose(); } - + _locks.Clear(); _lastAccessTime.Clear(); } - + /// /// Releases a lock when disposed. /// @@ -145,14 +145,14 @@ namespace AdvChkSys.Threading private readonly IChunk _chunk; private readonly ChunkAsyncLock _parent; private bool _disposed; - + public LockReleaser(SemaphoreSlim semaphore, IChunk chunk, ChunkAsyncLock parent) { _semaphore = semaphore; _chunk = chunk; _parent = parent; } - + public void Dispose() { if (!_disposed) diff --git a/src/AdvChkSys/Threading/ChunkOperationQueue.cs b/src/AdvChkSys/Threading/ChunkOperationQueue.cs index 0a54717..601735c 100644 --- a/src/AdvChkSys/Threading/ChunkOperationQueue.cs +++ b/src/AdvChkSys/Threading/ChunkOperationQueue.cs @@ -14,16 +14,16 @@ namespace AdvChkSys.Threading { // Queue of pending operations for each chunk private readonly ConcurrentDictionary Operation, TaskCompletionSource Completion)>> _pendingOperations = new(); - + // Currently active operations private readonly ConcurrentDictionary _activeOperations = new(); - + // Semaphore to limit concurrent operations private readonly SemaphoreSlim _semaphore; - + // Cancellation for shutdown private readonly CancellationTokenSource _shutdownCts = new(); - + /// /// Initializes a new instance of the ChunkOperationQueue class. /// @@ -32,7 +32,7 @@ namespace AdvChkSys.Threading { _semaphore = new SemaphoreSlim(maxConcurrentOperations, maxConcurrentOperations); } - + /// /// Enqueues an operation to be performed on a chunk. /// @@ -43,31 +43,31 @@ namespace AdvChkSys.Threading { if (chunk == null) throw new ArgumentNullException(nameof(chunk)); - + if (operation == null) throw new ArgumentNullException(nameof(operation)); - + // Create a completion source for this operation var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - + // Get or create the queue for this chunk var queue = _pendingOperations.GetOrAdd(chunk, _ => new Queue<(Func, TaskCompletionSource)>()); - + // Add the operation to the queue lock (queue) { queue.Enqueue((operation, completion)); - + // If this is the only operation, start processing if (queue.Count == 1 && !_activeOperations.ContainsKey(chunk)) { StartProcessingChunkOperations(chunk); } } - + return completion.Task; } - + /// /// Starts processing operations for a chunk. /// @@ -75,7 +75,7 @@ namespace AdvChkSys.Threading { // Wait for a semaphore slot await _semaphore.WaitAsync(_shutdownCts.Token).ConfigureAwait(false); - + try { // Process operations until the queue is empty @@ -83,9 +83,9 @@ namespace AdvChkSys.Threading { // Get the next operation (Func operation, TaskCompletionSource completion) nextOperation; - + var queue = _pendingOperations.GetOrAdd(chunk, _ => new Queue<(Func, TaskCompletionSource)>()); - + lock (queue) { if (queue.Count == 0) @@ -94,17 +94,17 @@ namespace AdvChkSys.Threading _activeOperations.TryRemove(chunk, out _); break; } - + nextOperation = queue.Peek(); } - + // Execute the operation var task = ExecuteOperationAsync(chunk, nextOperation.operation, nextOperation.completion); _activeOperations[chunk] = task; - + // Wait for completion await task.ConfigureAwait(false); - + // Remove the completed operation lock (queue) { @@ -132,12 +132,12 @@ namespace AdvChkSys.Threading { // Track the operation for diagnostics var operationId = ChunkThreadingDiagnostics.TrackOperationStart("ChunkOperation", chunk); - + try { // Execute the operation await operation().ConfigureAwait(false); - + // Complete the task completion.TrySetResult(null); } @@ -145,7 +145,7 @@ namespace AdvChkSys.Threading { // Set the exception completion.TrySetException(ex); - + // Log the error ChunkThreadingDiagnostics.LogEvent("OperationError", $"Error in chunk operation: {ex.Message}"); } @@ -159,7 +159,7 @@ namespace AdvChkSys.Threading { // This should never happen, but just in case completion.TrySetException(ex); - + // Log the error ChunkThreadingDiagnostics.LogEvent("CriticalError", $"Critical error in operation execution: {ex.Message}"); } @@ -177,7 +177,7 @@ namespace AdvChkSys.Threading return queue.Count; } } - + return 0; } @@ -217,18 +217,18 @@ namespace AdvChkSys.Threading lock (queue) { int count = queue.Count; - + // Cancel all pending operations while (queue.Count > 0) { - var operation = queue.Dequeue(); - operation.Completion.TrySetCanceled(); + var (Operation, Completion) = queue.Dequeue(); + Completion.TrySetCanceled(); } - + return count; } } - + return 0; } @@ -239,12 +239,12 @@ namespace AdvChkSys.Threading public int CancelAllOperations() { int count = 0; - + foreach (var chunk in _pendingOperations.Keys) { count += CancelOperations(chunk); } - + return count; } @@ -255,17 +255,17 @@ namespace AdvChkSys.Threading { // Cancel all operations _shutdownCts.Cancel(); - + // Cancel all pending operations CancelAllOperations(); - + // Wait for active operations to complete var tasks = new List(_activeOperations.Values); if (tasks.Count > 0) { await Task.WhenAll(tasks).ConfigureAwait(false); } - + // Dispose resources _shutdownCts.Dispose(); _semaphore.Dispose(); diff --git a/src/AdvChkSys/Threading/ChunkParallelProcessor.cs b/src/AdvChkSys/Threading/ChunkParallelProcessor.cs index f435c6d..a44f23f 100644 --- a/src/AdvChkSys/Threading/ChunkParallelProcessor.cs +++ b/src/AdvChkSys/Threading/ChunkParallelProcessor.cs @@ -30,7 +30,7 @@ namespace AdvChkSys.Threading { // Build dependency graph var dependencyGraph = BuildDependencyGraph(chunks, getDependencies); - + // Process in dependency order await ProcessDependencyGraphAsync( dependencyGraph, @@ -38,7 +38,7 @@ namespace AdvChkSys.Threading maxDegreeOfParallelism, cancellationToken).ConfigureAwait(false); } - + /// /// Builds a dependency graph for chunks. /// @@ -47,13 +47,13 @@ namespace AdvChkSys.Threading Func> getDependencies) { var graph = new DependencyGraph(); - + // Add all chunks to the graph foreach (var chunk in chunks) { graph.AddNode(chunk); } - + // Add dependencies foreach (var chunk in chunks) { @@ -63,10 +63,10 @@ namespace AdvChkSys.Threading graph.AddDependency(chunk, dependency); } } - + return graph; } - + /// /// Processes a dependency graph in parallel. /// @@ -79,28 +79,28 @@ namespace AdvChkSys.Threading // Set up semaphore for parallelism control var semaphore = new SemaphoreSlim( maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism); - + // Track completed chunks var completed = new ConcurrentDictionary(); - + // Get initial set of chunks with no dependencies var readyChunks = new ConcurrentQueue(graph.GetNodesWithNoDependencies()); - + // Track active tasks var activeTasks = new ConcurrentDictionary(); - + // Process until all chunks are completed while (!readyChunks.IsEmpty || activeTasks.Count > 0) { // Check for cancellation cancellationToken.ThrowIfCancellationRequested(); - + // Start processing ready chunks while (readyChunks.TryDequeue(out var chunk)) { // Wait for a semaphore slot await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - + // Start processing this chunk var task = ProcessChunkAsync( chunk, @@ -110,27 +110,27 @@ namespace AdvChkSys.Threading readyChunks, semaphore, cancellationToken); - + activeTasks[chunk] = task; - + // When the task completes, remove it from active tasks - _ = task.ContinueWith(_ => + _ = task.ContinueWith(_ => { activeTasks.TryRemove(chunk, out _); }, TaskContinuationOptions.ExecuteSynchronously); } - + // If no ready chunks but active tasks, wait for one to complete if (readyChunks.IsEmpty && activeTasks.Count > 0) { await Task.WhenAny(activeTasks.Values).ConfigureAwait(false); } } - + // Clean up semaphore.Dispose(); } - + /// /// Processes a single chunk and updates the ready queue. /// @@ -147,10 +147,10 @@ namespace AdvChkSys.Threading { // Process the chunk await processor(chunk).ConfigureAwait(false); - + // Mark as completed completed[chunk] = true; - + // Find dependents that are now ready var dependents = graph.GetDependents(chunk); foreach (var dependent in dependents) @@ -170,7 +170,7 @@ namespace AdvChkSys.Threading semaphore.Release(); } } - + /// /// Represents a dependency graph for chunks. /// @@ -178,10 +178,10 @@ namespace AdvChkSys.Threading { // Map of chunk to its dependencies private readonly Dictionary> _dependencies = new(); - + // Map of chunk to chunks that depend on it private readonly Dictionary> _dependents = new(); - + /// /// Adds a node to the graph. /// @@ -191,13 +191,13 @@ namespace AdvChkSys.Threading { _dependencies[chunk] = new HashSet(); } - + if (!_dependents.ContainsKey(chunk)) { _dependents[chunk] = new HashSet(); } } - + /// /// Adds a dependency between two nodes. /// @@ -206,14 +206,14 @@ namespace AdvChkSys.Threading // Add nodes if they don't exist AddNode(dependent); AddNode(dependency); - + // Add dependency _dependencies[dependent].Add(dependency); - + // Add dependent _dependents[dependency].Add(dependent); } - + /// /// Gets all nodes with no dependencies. /// @@ -221,7 +221,7 @@ namespace AdvChkSys.Threading { return _dependencies.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key); } - + /// /// Gets all dependencies of a node. /// @@ -231,10 +231,10 @@ namespace AdvChkSys.Threading { return dependencies; } - + return Enumerable.Empty(); } - + /// /// Gets all dependents of a node. /// @@ -244,7 +244,7 @@ namespace AdvChkSys.Threading { return dependents; } - + return Enumerable.Empty(); } } diff --git a/src/AdvChkSys/Threading/ChunkTaskScheduler.cs b/src/AdvChkSys/Threading/ChunkTaskScheduler.cs index ea13bc4..e7933a9 100644 --- a/src/AdvChkSys/Threading/ChunkTaskScheduler.cs +++ b/src/AdvChkSys/Threading/ChunkTaskScheduler.cs @@ -92,18 +92,18 @@ namespace AdvChkSys.Threading using var timeoutCts = new CancellationTokenSource(timeout); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( timeoutCts.Token, cancellationToken); - + var completedTask = await Task.WhenAny(task, Task.Delay(timeout, linkedCts.Token)) .ConfigureAwait(false); - + if (completedTask == task) { return await task.ConfigureAwait(false); } - + throw new TimeoutException($"The operation timed out after {timeout.TotalMilliseconds}ms"); } - + /// /// Runs a task with a timeout. /// @@ -119,19 +119,19 @@ namespace AdvChkSys.Threading using var timeoutCts = new CancellationTokenSource(timeout); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( timeoutCts.Token, cancellationToken); - + var completedTask = await Task.WhenAny(task, Task.Delay(timeout, linkedCts.Token)) .ConfigureAwait(false); - + if (completedTask == task) { await task.ConfigureAwait(false); return; } - + throw new TimeoutException($"The operation timed out after {timeout.TotalMilliseconds}ms"); } - + /// /// Runs a task with a fallback value if it times out. /// @@ -156,25 +156,25 @@ namespace AdvChkSys.Threading return fallbackValue; } } - + /// /// Runs multiple actions in parallel with a limit on the degree of parallelism. /// - public static Task RunBatchParallelAsync(IEnumerable actions, int? maxDegreeOfParallelism = null, + public static Task RunBatchParallelAsync(IEnumerable actions, int? maxDegreeOfParallelism = null, CancellationToken cancellationToken = default) { - return Task.Run(() => + return Task.Run(() => { var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism, CancellationToken = cancellationToken }; - + Parallel.ForEach(actions, options, action => action()); }, cancellationToken); } - + /// /// Runs a batch of functions in parallel and returns the results. /// @@ -190,24 +190,24 @@ namespace AdvChkSys.Threading { var funcs = functions.ToArray(); var results = new T[funcs.Length]; - - await Task.Run(() => + + await Task.Run(() => { var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism, CancellationToken = cancellationToken }; - - Parallel.For(0, funcs.Length, options, i => + + Parallel.For(0, funcs.Length, options, i => { results[i] = funcs[i](); }); }, cancellationToken).ConfigureAwait(false); - + return results; } - + /// /// Runs a batch of async functions in parallel. /// @@ -220,24 +220,24 @@ namespace AdvChkSys.Threading CancellationToken cancellationToken = default) { var funcs = functions.ToArray(); - + if (funcs.Length == 0) return; - + // Use SemaphoreSlim to limit concurrency using var semaphore = new SemaphoreSlim( maxDegreeOfParallelism ?? MaxDegreeOfParallelism); - + // Create tasks for all functions var tasks = new List(funcs.Length); - + foreach (var func in funcs) { // Wait for a slot in the semaphore await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - + // Create a task that releases the semaphore when done - var task = Task.Run(async () => + var task = Task.Run(async () => { try { @@ -248,14 +248,14 @@ namespace AdvChkSys.Threading semaphore.Release(); } }, cancellationToken); - + tasks.Add(task); } - + // Wait for all tasks to complete await Task.WhenAll(tasks).ConfigureAwait(false); } - + /// /// Runs a batch of async functions in parallel and returns the results. /// @@ -270,27 +270,27 @@ namespace AdvChkSys.Threading CancellationToken cancellationToken = default) { var funcs = functions.ToArray(); - + if (funcs.Length == 0) return Array.Empty(); - + // Use SemaphoreSlim to limit concurrency using var semaphore = new SemaphoreSlim( maxDegreeOfParallelism ?? MaxDegreeOfParallelism); - + // Create tasks for all functions var tasks = new Task[funcs.Length]; - + for (int i = 0; i < funcs.Length; i++) { var func = funcs[i]; var index = i; - + // Wait for a slot in the semaphore await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - + // Create a task that releases the semaphore when done - tasks[index] = Task.Run(async () => + tasks[index] = Task.Run(async () => { try { @@ -302,7 +302,7 @@ namespace AdvChkSys.Threading } }, cancellationToken); } - + // Wait for all tasks to complete return await Task.WhenAll(tasks).ConfigureAwait(false); } diff --git a/src/AdvChkSys/Threading/ChunkTaskSchedulerExtensions.cs b/src/AdvChkSys/Threading/ChunkTaskSchedulerExtensions.cs index 1156206..e252071 100644 --- a/src/AdvChkSys/Threading/ChunkTaskSchedulerExtensions.cs +++ b/src/AdvChkSys/Threading/ChunkTaskSchedulerExtensions.cs @@ -14,7 +14,7 @@ namespace AdvChkSys.Threading /// Maximum degree of parallelism for chunk operations. /// public static int MaxDegreeOfParallelism { get; set; } = Environment.ProcessorCount; - + /// /// Runs a batch of actions in parallel. /// @@ -29,7 +29,7 @@ namespace AdvChkSys.Threading { if (actions == null || actions.Length == 0) return Task.CompletedTask; - + return Task.Run(() => { var options = new ParallelOptions @@ -37,11 +37,11 @@ namespace AdvChkSys.Threading MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism, CancellationToken = cancellationToken }; - + Parallel.ForEach(actions, options, action => action()); }, cancellationToken); } - + /// /// Creates a cancellation token with a timeout. /// @@ -54,7 +54,7 @@ namespace AdvChkSys.Threading { if (timeout == TimeSpan.MaxValue) return cancellationToken; - + var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); source.CancelAfter(timeout); return source.Token; diff --git a/src/AdvChkSys/Threading/ChunkThreadSafetyManager.cs b/src/AdvChkSys/Threading/ChunkThreadSafetyManager.cs index dcb4594..4a8581b 100644 --- a/src/AdvChkSys/Threading/ChunkThreadSafetyManager.cs +++ b/src/AdvChkSys/Threading/ChunkThreadSafetyManager.cs @@ -12,16 +12,16 @@ namespace AdvChkSys.Threading { // Lock objects for each chunk private readonly ConcurrentDictionary _locks = new(); - + // Timer for cleanup private readonly Timer _cleanupTimer; - + // Last access time for each lock private readonly ConcurrentDictionary _lastAccessTime = new(); - + // Cleanup interval in minutes private readonly int _cleanupIntervalMinutes; - + /// /// Initializes a new instance of the ChunkThreadSafetyManager class. /// @@ -29,11 +29,11 @@ namespace AdvChkSys.Threading public ChunkThreadSafetyManager(int cleanupIntervalMinutes = 10) { _cleanupIntervalMinutes = cleanupIntervalMinutes; - _cleanupTimer = new Timer(CleanupUnusedLocks, null, - TimeSpan.FromMinutes(cleanupIntervalMinutes), + _cleanupTimer = new Timer(CleanupUnusedLocks, null, + TimeSpan.FromMinutes(cleanupIntervalMinutes), TimeSpan.FromMinutes(cleanupIntervalMinutes)); } - + /// /// Acquires an exclusive lock on a chunk. /// @@ -43,12 +43,12 @@ namespace AdvChkSys.Threading { var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock()); _lastAccessTime[chunk] = DateTime.UtcNow; - + lockObj.EnterWriteLock(); - + return new LockReleaser(lockObj, chunk, this, LockType.Write); } - + /// /// Acquires a read lock on a chunk. /// @@ -58,12 +58,12 @@ namespace AdvChkSys.Threading { var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock()); _lastAccessTime[chunk] = DateTime.UtcNow; - + lockObj.EnterReadLock(); - + return new LockReleaser(lockObj, chunk, this, LockType.Read); } - + /// /// Acquires a write lock on a chunk. /// @@ -73,12 +73,12 @@ namespace AdvChkSys.Threading { var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock()); _lastAccessTime[chunk] = DateTime.UtcNow; - + lockObj.EnterWriteLock(); - + return new LockReleaser(lockObj, chunk, this, LockType.Write); } - + /// /// Tries to acquire a read lock on a chunk. /// @@ -89,18 +89,18 @@ namespace AdvChkSys.Threading { var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock()); _lastAccessTime[chunk] = DateTime.UtcNow; - + if (lockObj.TryEnterReadLock(timeout)) { return new LockReleaser(lockObj, chunk, this, LockType.Read); } - + // Track contention ChunkThreadingDiagnostics.TrackLockContention(chunk); - + return null; } - + /// /// Tries to acquire a write lock on a chunk. /// @@ -111,18 +111,18 @@ namespace AdvChkSys.Threading { var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock()); _lastAccessTime[chunk] = DateTime.UtcNow; - + if (lockObj.TryEnterWriteLock(timeout)) { return new LockReleaser(lockObj, chunk, this, LockType.Write); } - + // Track contention ChunkThreadingDiagnostics.TrackLockContention(chunk); - + return null; } - + /// /// Cleans up unused locks. /// @@ -130,10 +130,10 @@ namespace AdvChkSys.Threading { var now = DateTime.UtcNow; var threshold = now.AddMinutes(-_cleanupIntervalMinutes); - + foreach (var chunk in _lastAccessTime.Keys) { - if (_lastAccessTime.TryGetValue(chunk, out var lastAccess) && + if (_lastAccessTime.TryGetValue(chunk, out var lastAccess) && lastAccess < threshold && _locks.TryGetValue(chunk, out var lockObj)) { @@ -149,23 +149,23 @@ namespace AdvChkSys.Threading } } } - + /// /// Disposes resources. /// public void Dispose() { _cleanupTimer.Dispose(); - + foreach (var lockObj in _locks.Values) { lockObj.Dispose(); } - + _locks.Clear(); _lastAccessTime.Clear(); } - + /// /// Type of lock. /// @@ -174,28 +174,28 @@ namespace AdvChkSys.Threading Read, Write } - + /// /// Wrapper around ReaderWriterLockSlim. /// private class ChunkLock : IDisposable { private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion); - + public void EnterReadLock() => _lock.EnterReadLock(); public void ExitReadLock() => _lock.ExitReadLock(); public void EnterWriteLock() => _lock.EnterWriteLock(); public void ExitWriteLock() => _lock.ExitWriteLock(); - + public bool TryEnterReadLock(TimeSpan timeout) => _lock.TryEnterReadLock(timeout); public bool TryEnterWriteLock(TimeSpan timeout) => _lock.TryEnterWriteLock(timeout); - + public bool IsReadLockHeld => _lock.IsReadLockHeld; public bool IsWriteLockHeld => _lock.IsWriteLockHeld; - + public void Dispose() => _lock.Dispose(); } - + /// /// Releases a lock when disposed. /// @@ -206,7 +206,7 @@ namespace AdvChkSys.Threading private readonly ChunkThreadSafetyManager _parent; private readonly LockType _lockType; private bool _disposed; - + public LockReleaser(ChunkLock lockObj, IChunk chunk, ChunkThreadSafetyManager parent, LockType lockType) { _lock = lockObj; @@ -214,7 +214,7 @@ namespace AdvChkSys.Threading _parent = parent; _lockType = lockType; } - + public void Dispose() { if (!_disposed) @@ -227,7 +227,7 @@ namespace AdvChkSys.Threading { _lock.ExitWriteLock(); } - + _parent._lastAccessTime[_chunk] = DateTime.UtcNow; _disposed = true; } diff --git a/src/AdvChkSys/Threading/ChunkThreadingConfiguration.cs b/src/AdvChkSys/Threading/ChunkThreadingConfiguration.cs index 0e16d2c..6dfd143 100644 --- a/src/AdvChkSys/Threading/ChunkThreadingConfiguration.cs +++ b/src/AdvChkSys/Threading/ChunkThreadingConfiguration.cs @@ -11,7 +11,7 @@ namespace AdvChkSys.Threading private static int _defaultMaxDegreeOfParallelism = Environment.ProcessorCount; private static int _chunkOperationTimeout = 30000; // 30 seconds private static int _lockCleanupInterval = 10; // 10 minutes - + /// /// Gets or sets the default maximum degree of parallelism for chunk operations. /// @@ -26,7 +26,7 @@ namespace AdvChkSys.Threading ChunkTaskScheduler.MaxDegreeOfParallelism = value; } } - + /// /// Gets or sets the timeout in milliseconds for chunk operations. /// @@ -40,7 +40,7 @@ namespace AdvChkSys.Threading _chunkOperationTimeout = value; } } - + /// /// Gets or sets the interval in minutes for cleaning up unused locks. /// @@ -54,17 +54,17 @@ namespace AdvChkSys.Threading _lockCleanupInterval = value; } } - + /// /// Creates a cancellation token with the default timeout. /// public static CancellationToken CreateTimeoutToken(CancellationToken cancellationToken = default) { return ChunkTaskScheduler.CreateTimeoutToken( - TimeSpan.FromMilliseconds(_chunkOperationTimeout), + TimeSpan.FromMilliseconds(_chunkOperationTimeout), cancellationToken); } - + /// /// Configures the system for high throughput (more parallelism, longer timeouts). /// @@ -74,7 +74,7 @@ namespace AdvChkSys.Threading ChunkOperationTimeoutMs = 60000; // 1 minute LockCleanupIntervalMinutes = 30; } - + /// /// Configures the system for low latency (less parallelism, shorter timeouts). /// @@ -84,7 +84,7 @@ namespace AdvChkSys.Threading ChunkOperationTimeoutMs = 15000; // 15 seconds LockCleanupIntervalMinutes = 5; } - + /// /// Configures the system for memory efficiency (less parallelism, more aggressive cleanup). /// diff --git a/src/AdvChkSys/Threading/ChunkThreadingDiagnostics.cs b/src/AdvChkSys/Threading/ChunkThreadingDiagnostics.cs index dc9f915..5b210d3 100644 --- a/src/AdvChkSys/Threading/ChunkThreadingDiagnostics.cs +++ b/src/AdvChkSys/Threading/ChunkThreadingDiagnostics.cs @@ -16,19 +16,19 @@ namespace AdvChkSys.Threading { // Track operation durations private static readonly ConcurrentDictionary> _operationDurations = new(); - + // Track lock contention private static readonly ConcurrentDictionary _lockContentionCount = new(); - + // Track active operations private static readonly ConcurrentDictionary _activeOperations = new(); - + // Lock for thread safety private static readonly object _lock = new(); - + // Stopwatch for timing private static readonly Stopwatch _stopwatch = Stopwatch.StartNew(); - + /// /// Tracks the duration of an operation. /// @@ -38,7 +38,7 @@ namespace AdvChkSys.Threading { var operationId = Guid.NewGuid(); var startTime = _stopwatch.ElapsedMilliseconds; - + try { action(); @@ -46,7 +46,7 @@ namespace AdvChkSys.Threading finally { var duration = _stopwatch.ElapsedMilliseconds - startTime; - + lock (_lock) { if (!_operationDurations.TryGetValue(operationName, out var durations)) @@ -54,9 +54,9 @@ namespace AdvChkSys.Threading durations = new List(); _operationDurations[operationName] = durations; } - + durations.Add(duration); - + // Keep only the last 1000 durations if (durations.Count > 1000) { @@ -65,7 +65,7 @@ namespace AdvChkSys.Threading } } } - + /// /// Tracks the start of an operation on a chunk. /// @@ -78,7 +78,7 @@ namespace AdvChkSys.Threading _activeOperations[operationId] = (operationName, DateTime.UtcNow, chunk); return operationId; } - + /// /// Tracks the end of an operation. /// @@ -88,7 +88,7 @@ namespace AdvChkSys.Threading if (_activeOperations.TryRemove(operationId, out var info)) { var duration = (DateTime.UtcNow - info.StartTime).TotalMilliseconds; - + lock (_lock) { if (!_operationDurations.TryGetValue(info.Operation, out var durations)) @@ -96,9 +96,9 @@ namespace AdvChkSys.Threading durations = new List(); _operationDurations[info.Operation] = durations; } - + durations.Add((long)duration); - + // Keep only the last 1000 durations if (durations.Count > 1000) { @@ -107,7 +107,7 @@ namespace AdvChkSys.Threading } } } - + /// /// Tracks lock contention on a chunk. /// @@ -116,14 +116,14 @@ namespace AdvChkSys.Threading { _lockContentionCount.AddOrUpdate(chunk, 1, (_, count) => count + 1); } - + /// /// Gets statistics about operation durations. /// public static Dictionary GetOperationStatistics() { var result = new Dictionary(); - + lock (_lock) { foreach (var kvp in _operationDurations) @@ -140,10 +140,10 @@ namespace AdvChkSys.Threading } } } - + return result; } - + /// /// Gets the chunks with the most lock contention. /// @@ -156,7 +156,7 @@ namespace AdvChkSys.Threading .Select(kvp => (kvp.Key, kvp.Value)) .ToList(); } - + /// /// Gets information about currently active operations. /// @@ -172,7 +172,7 @@ namespace AdvChkSys.Threading .OrderByDescending(x => x.Item2) .ToList(); } - + /// /// Gets the total number of tracked operations. /// @@ -183,7 +183,7 @@ namespace AdvChkSys.Threading return _operationDurations.Values.Sum(list => list.Count); } } - + /// /// Gets the number of active operations. /// @@ -191,7 +191,7 @@ namespace AdvChkSys.Threading { return _activeOperations.Count; } - + /// /// Gets the total number of lock contentions. /// @@ -199,7 +199,7 @@ namespace AdvChkSys.Threading { return _lockContentionCount.Values.Sum(); } - + /// /// Clears all diagnostic data. /// @@ -212,18 +212,18 @@ namespace AdvChkSys.Threading // Don't clear active operations as they're still in progress } } - + /// /// Gets a comprehensive diagnostic report. /// public static string GenerateDiagnosticReport() { var report = new System.Text.StringBuilder(); - + report.AppendLine("=== Chunk Threading Diagnostic Report ==="); report.AppendLine($"Generated: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC"); report.AppendLine(); - + // Operation statistics report.AppendLine("== Operation Statistics =="); var stats = GetOperationStatistics(); @@ -236,7 +236,7 @@ namespace AdvChkSys.Threading report.AppendLine($" Avg: {kvp.Value.Average:F2}ms"); } report.AppendLine(); - + // Lock contention report.AppendLine("== Lock Contention =="); var contentions = GetTopContentionChunks(10); @@ -246,7 +246,7 @@ namespace AdvChkSys.Threading } report.AppendLine($"Total contentions: {GetTotalLockContentionCount()}"); report.AppendLine(); - + // Active operations report.AppendLine("== Active Operations =="); var activeOps = GetActiveOperations(); @@ -255,10 +255,10 @@ namespace AdvChkSys.Threading report.AppendLine($"{operation} on Chunk ({chunk.X}, {chunk.Y}): {duration.TotalMilliseconds:F2}ms"); } report.AppendLine($"Total active operations: {GetActiveOperationCount()}"); - + return report.ToString(); } - + /// /// Logs a diagnostic event. /// diff --git a/src/AdvChkSys/Threading/ChunkThreadingExtensions.cs b/src/AdvChkSys/Threading/ChunkThreadingExtensions.cs index 222a836..eeb5559 100644 --- a/src/AdvChkSys/Threading/ChunkThreadingExtensions.cs +++ b/src/AdvChkSys/Threading/ChunkThreadingExtensions.cs @@ -76,7 +76,7 @@ namespace AdvChkSys.Threading CancellationToken cancellationToken = default) { var chunks = new List>(); - + // Collect chunks in the region for (int x = minX; x <= maxX; x++) { @@ -89,7 +89,7 @@ namespace AdvChkSys.Threading } } } - + // Process in parallel await ChunkTaskScheduler.RunBatchParallelAsync( chunks.Select(c => new Action(() => action(c))).ToArray(), @@ -119,7 +119,7 @@ namespace AdvChkSys.Threading CancellationToken cancellationToken = default) { var chunks = new List>(); - + // Collect chunks in the region for (int x = minX; x <= maxX; x++) { @@ -135,7 +135,7 @@ namespace AdvChkSys.Threading } } } - + // Process in parallel await ChunkTaskScheduler.RunBatchParallelAsync( chunks.Select(c => new Action(() => action(c))).ToArray(), @@ -165,7 +165,7 @@ namespace AdvChkSys.Threading { // Create a loading priority system if not already integrated var loadingPriority = new ChunkLoadingPriority(); - + // Enqueue all chunks in the region var tasks = new List(); for (int x = minX; x <= maxX; x++) @@ -174,17 +174,17 @@ namespace AdvChkSys.Threading { if (cancellationToken.IsCancellationRequested) break; - + // Skip if already loaded if (manager.IsChunkLoaded(x, y)) continue; - + // Enqueue the request var request = loadingPriority.EnqueueRequest(x, y, width, height, priority); - + // Create a task that completes when the chunk is loaded var tcs = new TaskCompletionSource(); - + void Handler(object? sender, ChunkLoadingPriority.ChunkLoadRequest completedRequest) { if (completedRequest.RequestId == request.RequestId) @@ -193,24 +193,24 @@ namespace AdvChkSys.Threading tcs.TrySetResult(true); } } - + loadingPriority.RequestCompleted += Handler; - + // Add timeout to prevent indefinite waiting - _ = Task.Delay(30000, cancellationToken).ContinueWith(t => + _ = Task.Delay(30000, cancellationToken).ContinueWith(t => { loadingPriority.RequestCompleted -= Handler; if (!t.IsCanceled) tcs.TrySetResult(false); }, cancellationToken); - + tasks.Add(tcs.Task); } } - + // Wait for all chunks to be loaded await Task.WhenAll(tasks).ConfigureAwait(false); - + // Clean up await loadingPriority.ShutdownAsync().ConfigureAwait(false); } @@ -240,7 +240,7 @@ namespace AdvChkSys.Threading { // Create a loading priority system if not already integrated var loadingPriority = new ChunkLoadingPriority(); - + // Enqueue all chunks in the region var tasks = new List(); for (int x = minX; x <= maxX; x++) @@ -251,17 +251,17 @@ namespace AdvChkSys.Threading { if (cancellationToken.IsCancellationRequested) break; - + // Skip if already loaded if (manager.IsChunkLoaded(x, y, z)) continue; - + // Enqueue the request var request = loadingPriority.EnqueueRequest(x, y, z, width, height, depth, priority); - + // Create a task that completes when the chunk is loaded var tcs = new TaskCompletionSource(); - + void Handler(object? sender, ChunkLoadingPriority.ChunkLoadRequest completedRequest) { if (completedRequest.RequestId == request.RequestId) @@ -270,25 +270,25 @@ namespace AdvChkSys.Threading tcs.TrySetResult(true); } } - + loadingPriority.RequestCompleted += Handler; - + // Add timeout to prevent indefinite waiting - _ = Task.Delay(30000, cancellationToken).ContinueWith(t => + _ = Task.Delay(30000, cancellationToken).ContinueWith(t => { loadingPriority.RequestCompleted -= Handler; if (!t.IsCanceled) tcs.TrySetResult(false); }, cancellationToken); - + tasks.Add(tcs.Task); } } } - + // Wait for all chunks to be loaded await Task.WhenAll(tasks).ConfigureAwait(false); - + // Clean up await loadingPriority.ShutdownAsync().ConfigureAwait(false); } @@ -312,21 +312,21 @@ namespace AdvChkSys.Threading { // Get all loaded chunks var chunks = manager.GetAllChunks().ToArray(); - + // Filter chunks outside the region - var chunksToUnload = chunks.Where(c => + var chunksToUnload = chunks.Where(c => c.X < minX || c.X > maxX || c.Y < minY || c.Y > maxY).ToArray(); - + // Unload in parallel var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism ?? Environment.ProcessorCount, CancellationToken = cancellationToken }; - - await Task.Run(() => + + await Task.Run(() => { - Parallel.ForEach(chunksToUnload, options, chunk => + Parallel.ForEach(chunksToUnload, options, chunk => { manager.UnloadChunk(chunk.X, chunk.Y); }); @@ -354,23 +354,23 @@ namespace AdvChkSys.Threading { // Get all loaded chunks var chunks = manager.GetAllChunks().ToArray(); - + // Filter chunks outside the region - var chunksToUnload = chunks.Where(c => - c.X < minX || c.X > maxX || - c.Y < minY || c.Y > maxY || + var chunksToUnload = chunks.Where(c => + c.X < minX || c.X > maxX || + c.Y < minY || c.Y > maxY || c.Z < minZ || c.Z > maxZ).ToArray(); - + // Unload in parallel var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism ?? Environment.ProcessorCount, CancellationToken = cancellationToken }; - - await Task.Run(() => + + await Task.Run(() => { - Parallel.ForEach(chunksToUnload, options, chunk => + Parallel.ForEach(chunksToUnload, options, chunk => { manager.UnloadChunk(chunk.X, chunk.Y, chunk.Z); }); diff --git a/src/AdvChkSys/Threading/ChunkThreadingExtensions2.cs b/src/AdvChkSys/Threading/ChunkThreadingExtensions2.cs index 38d9388..b8a39f4 100644 --- a/src/AdvChkSys/Threading/ChunkThreadingExtensions2.cs +++ b/src/AdvChkSys/Threading/ChunkThreadingExtensions2.cs @@ -35,21 +35,21 @@ namespace AdvChkSys.Threading { // Generate spiral coordinates var coordinates = GenerateSpiralCoordinates(centerX, centerY, radius).ToArray(); - + // Process in batches to maintain the spiral ordering while still using parallelism int batchSize = Math.Max(1, (int)Math.Sqrt(coordinates.Length)); int batchCount = (coordinates.Length + batchSize - 1) / batchSize; - + for (int i = 0; i < batchCount; i++) { if (cancellationToken.IsCancellationRequested) break; - + var batchCoords = coordinates .Skip(i * batchSize) .Take(batchSize) .ToArray(); - + var tasks = new List(); foreach (var (x, y) in batchCoords) { @@ -59,14 +59,14 @@ namespace AdvChkSys.Threading tasks.Add(processor(chunk)); } } - + if (tasks.Count > 0) { await Task.WhenAll(tasks).ConfigureAwait(false); } } } - + /// /// Generates coordinates in a spiral pattern from the center outward. /// @@ -74,7 +74,7 @@ namespace AdvChkSys.Threading { // Start with the center yield return (centerX, centerY); - + // Spiral outward for (int layer = 1; layer <= radius; layer++) { @@ -83,19 +83,19 @@ namespace AdvChkSys.Threading { yield return (x, centerY - layer); } - + // Right edge (moving down) for (int y = centerY - layer + 1; y <= centerY + layer; y++) { yield return (centerX + layer, y); } - + // Bottom edge (moving left) for (int x = centerX + layer - 1; x >= centerX - layer; x--) { yield return (x, centerY + layer); } - + // Left edge (moving up) for (int y = centerY + layer - 1; y >= centerY - layer; y--) { @@ -103,7 +103,7 @@ namespace AdvChkSys.Threading } } } - + /// /// Processes chunks with dependency awareness. /// @@ -121,7 +121,7 @@ namespace AdvChkSys.Threading CancellationToken cancellationToken = default) { var chunks = manager.GetAllChunks().ToArray(); - + await ChunkParallelProcessor.ProcessChunksWithDependenciesAsync( chunks, chunk => processor((Chunk2D)chunk), @@ -129,7 +129,7 @@ namespace AdvChkSys.Threading maxDegreeOfParallelism, cancellationToken).ConfigureAwait(false); } - + /// /// Processes chunks with dependency awareness. /// @@ -147,7 +147,7 @@ namespace AdvChkSys.Threading CancellationToken cancellationToken = default) { var chunks = manager.GetAllChunks().ToArray(); - + await ChunkParallelProcessor.ProcessChunksWithDependenciesAsync( chunks, chunk => processor((Chunk3D)chunk), @@ -155,7 +155,7 @@ namespace AdvChkSys.Threading maxDegreeOfParallelism, cancellationToken).ConfigureAwait(false); } - + /// /// Processes chunks with thread safety. /// @@ -172,22 +172,22 @@ namespace AdvChkSys.Threading { var chunks = manager.GetAllChunks().ToArray(); var threadingManager = ChunkThreadingManager.Instance; - + var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism, CancellationToken = cancellationToken }; - - await Task.Run(() => Parallel.ForEach(chunks, options, async chunk => + + await Task.Run(() => Parallel.ForEach(chunks, options, async chunk => { - await threadingManager.WithLockAsync(chunk, async () => + await threadingManager.WithLockAsync(chunk, async () => { await processor(chunk).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false); }), cancellationToken).ConfigureAwait(false); } - + /// /// Processes chunks with thread safety. /// @@ -204,16 +204,16 @@ namespace AdvChkSys.Threading { var chunks = manager.GetAllChunks().ToArray(); var threadingManager = ChunkThreadingManager.Instance; - + var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism, CancellationToken = cancellationToken }; - - await Task.Run(() => Parallel.ForEach(chunks, options, async chunk => + + await Task.Run(() => Parallel.ForEach(chunks, options, async chunk => { - await threadingManager.WithLockAsync(chunk, async () => + await threadingManager.WithLockAsync(chunk, async () => { await processor(chunk).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false); diff --git a/src/AdvChkSys/Threading/LimitedConcurrencyTaskScheduler.cs b/src/AdvChkSys/Threading/LimitedConcurrencyTaskScheduler.cs index 3e375f3..d1e2316 100644 --- a/src/AdvChkSys/Threading/LimitedConcurrencyTaskScheduler.cs +++ b/src/AdvChkSys/Threading/LimitedConcurrencyTaskScheduler.cs @@ -16,7 +16,7 @@ namespace AdvChkSys.Threading private static bool _currentThreadIsProcessingItems; // The list of tasks to be executed - private readonly LinkedList _tasks = new LinkedList(); + private readonly LinkedList _tasks = new(); // The maximum concurrency level allowed by this scheduler. private int _maximumConcurrencyLevel; diff --git a/src/AdvChkSys/Util/LRUCache.cs b/src/AdvChkSys/Util/LRUCache.cs index b84a117..8b99478 100644 --- a/src/AdvChkSys/Util/LRUCache.cs +++ b/src/AdvChkSys/Util/LRUCache.cs @@ -115,8 +115,8 @@ namespace AdvChkSys.Util { lock (_lock) { - foreach (var node in _lruList) - yield return node.value; + foreach (var (key, value) in _lruList) + yield return value; } } }