fixed code formatter.
minor bug fixes to a few systems as well.
This commit is contained in:
parent
657e19f48f
commit
01d2cbf4b3
@ -27,19 +27,19 @@ namespace AdvChkSys
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new WorldConstraints object.
|
/// Creates a new WorldConstraints object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static WorldConstraints CreateDefaultConstraints() => new WorldConstraints();
|
public static WorldConstraints CreateDefaultConstraints() => new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new 2D chunk manager with optional constraints.
|
/// Creates a new 2D chunk manager with optional constraints.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ChunkManager2D<byte> Create2DManager(WorldConstraints? constraints = null) =>
|
public static ChunkManager2D<byte> Create2DManager(WorldConstraints? constraints = null) =>
|
||||||
new ChunkManager2D<byte>(constraints);
|
new(constraints);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new 3D chunk manager with optional constraints.
|
/// Creates a new 3D chunk manager with optional constraints.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ChunkManager3D<byte> Create3DManager(WorldConstraints? constraints = null) =>
|
public static ChunkManager3D<byte> Create3DManager(WorldConstraints? constraints = null) =>
|
||||||
new ChunkManager3D<byte>(constraints);
|
new(constraints);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a detailed report of the current memory usage by the chunk system.
|
/// 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.
|
/// Logs the current memory usage to the provided logging action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logAction">Action that will receive the log messages</param>
|
/// <param name="logAction">Action that will receive the log messages</param>
|
||||||
public static void LogMemoryUsage(Action<string> logAction) =>
|
public static void LogMemoryUsage(Action<string> logAction) =>
|
||||||
MemoryUsageReporter.LogMemoryUsage(logAction);
|
MemoryUsageReporter.LogMemoryUsage(logAction);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -62,7 +62,7 @@ namespace AdvChkSys
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a batch of chunk operations in parallel.
|
/// Runs a batch of chunk operations in parallel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Task RunParallelChunkOperationsAsync(IEnumerable<Action> operations,
|
public static Task RunParallelChunkOperationsAsync(IEnumerable<Action> operations,
|
||||||
int? maxDegreeOfParallelism = null,
|
int? maxDegreeOfParallelism = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@ -73,13 +73,13 @@ namespace AdvChkSys
|
|||||||
/// Creates a spatial index for 2D chunks.
|
/// Creates a spatial index for 2D chunks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SpatialChunkIndex<Chunk2D<T>> CreateSpatialIndex2D<T>() =>
|
public static SpatialChunkIndex<Chunk2D<T>> CreateSpatialIndex2D<T>() =>
|
||||||
new SpatialChunkIndex<Chunk2D<T>>();
|
new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a spatial index for 3D chunks.
|
/// Creates a spatial index for 3D chunks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SpatialChunkIndex<Chunk3D<T>> CreateSpatialIndex3D<T>() =>
|
public static SpatialChunkIndex<Chunk3D<T>> CreateSpatialIndex3D<T>() =>
|
||||||
new SpatialChunkIndex<Chunk3D<T>>();
|
new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures the threading system for high throughput.
|
/// Configures the threading system for high throughput.
|
||||||
@ -155,7 +155,7 @@ namespace AdvChkSys
|
|||||||
manager2D.UnloadChunk(1, 1);
|
manager2D.UnloadChunk(1, 1);
|
||||||
manager3D.UnloadChunk(1, 1, 1);
|
manager3D.UnloadChunk(1, 1, 1);
|
||||||
|
|
||||||
return resourceTracking2D && resourceTracking3D && constraintsWork &&
|
return resourceTracking2D && resourceTracking3D && constraintsWork &&
|
||||||
spatialIndexWorks && threadingWorks;
|
spatialIndexWorks && threadingWorks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,13 +91,13 @@ namespace AdvChkSys.Chunk
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (localX < 0 || localX >= Width || localY < 0 || localY >= Height)
|
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];
|
return _isAllAir ? default! : _data![localX, localY];
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (localX < 0 || localX >= Width || localY < 0 || localY >= Height)
|
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)
|
if (_isAllAir)
|
||||||
throw new InvalidOperationException("Cannot set cell in all-air chunk.");
|
throw new InvalidOperationException("Cannot set cell in all-air chunk.");
|
||||||
_data![localX, localY] = value;
|
_data![localX, localY] = value;
|
||||||
|
|||||||
@ -6,210 +6,210 @@ using AdvChkSys.Interfaces;
|
|||||||
|
|
||||||
namespace AdvChkSys.Chunk
|
namespace AdvChkSys.Chunk
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a 3D chunk of data in the world.
|
||||||
|
/// Perspective-agnostic and supports arbitrary data types and metadata.
|
||||||
|
/// </summary>
|
||||||
|
public class Chunk3D<T> : IChunk, IDisposable
|
||||||
|
{
|
||||||
|
// Array pool for chunk data arrays
|
||||||
|
private static readonly ConcurrentBag<T[,,]> _arrayPool = new();
|
||||||
|
|
||||||
|
// Flyweight: one all-air instance per size
|
||||||
|
private static readonly Dictionary<(int, int, int), Chunk3D<T>> _allAirChunks = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a 3D chunk of data in the world.
|
/// Returns a singleton all-air chunk for the given size and position.
|
||||||
/// Perspective-agnostic and supports arbitrary data types and metadata.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Chunk3D<T> : IChunk, IDisposable
|
public static Chunk3D<T> AllAir(int x, int y, int z, int width, int height, int depth)
|
||||||
{
|
{
|
||||||
// Array pool for chunk data arrays
|
var key = (width, height, depth);
|
||||||
private static readonly ConcurrentBag<T[,,]> _arrayPool = new();
|
if (!_allAirChunks.TryGetValue(key, out var chunk))
|
||||||
|
|
||||||
// Flyweight: one all-air instance per size
|
|
||||||
private static readonly Dictionary<(int, int, int), Chunk3D<T>> _allAirChunks = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a singleton all-air chunk for the given size and position.
|
|
||||||
/// </summary>
|
|
||||||
public static Chunk3D<T> AllAir(int x, int y, int z, int width, int height, int depth)
|
|
||||||
{
|
{
|
||||||
var key = (width, height, depth);
|
chunk = new Chunk3D<T>(x, y, z, width, height, depth, isAllAir: true);
|
||||||
if (!_allAirChunks.TryGetValue(key, out var chunk))
|
_allAirChunks[key] = chunk;
|
||||||
{
|
|
||||||
chunk = new Chunk3D<T>(x, y, z, width, height, depth, isAllAir: true);
|
|
||||||
_allAirChunks[key] = chunk;
|
|
||||||
}
|
|
||||||
chunk.SetPosition(x, y, z); // Use explicit method for position update
|
|
||||||
return chunk;
|
|
||||||
}
|
}
|
||||||
|
chunk.SetPosition(x, y, z); // Use explicit method for position update
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chunk's X position in chunk coordinates.
|
/// The chunk's X position in chunk coordinates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int X { get; private set; }
|
public int X { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The chunk's Y position in chunk coordinates.
|
|
||||||
/// </summary>
|
|
||||||
public int Y { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The chunk's Z position in chunk coordinates.
|
|
||||||
/// </summary>
|
|
||||||
public int Z { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The width of the chunk in cells.
|
/// The chunk's Y position in chunk coordinates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Width { get; }
|
public int Y { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The height of the chunk in cells.
|
|
||||||
/// </summary>
|
|
||||||
public int Height { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The depth of the chunk in cells.
|
|
||||||
/// </summary>
|
|
||||||
public int Depth { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chunk's data array.
|
/// The chunk's Z position in chunk coordinates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly T[,,]? _data;
|
public int Z { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Metadata dictionary for arbitrary chunk information (e.g., biome, tags).
|
/// The width of the chunk in cells.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, object> Metadata { get; }
|
public int Width { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if this chunk is the all-air singleton.
|
/// The height of the chunk in cells.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsAllAir { get; private set; } = false;
|
public int Height { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tracks whether the chunk has been disposed.
|
|
||||||
/// </summary>
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new 3D chunk at the specified position with the given dimensions.
|
/// The depth of the chunk in cells.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="x">X coordinate in chunk space</param>
|
public int Depth { get; }
|
||||||
/// <param name="y">Y coordinate in chunk space</param>
|
|
||||||
/// <param name="z">Z coordinate in chunk space</param>
|
|
||||||
/// <param name="width">Width of the chunk in cells</param>
|
|
||||||
/// <param name="height">Height of the chunk in cells</param>
|
|
||||||
/// <param name="depth">Depth of the chunk in cells</param>
|
|
||||||
public Chunk3D(int x, int y, int z, int width, int height, int depth)
|
|
||||||
: this(x, y, z, width, height, depth, false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new 3D chunk at the specified position with the given dimensions.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="x">X coordinate in chunk space</param>
|
|
||||||
/// <param name="y">Y coordinate in chunk space</param>
|
|
||||||
/// <param name="z">Z coordinate in chunk space</param>
|
|
||||||
/// <param name="width">Width of the chunk in cells</param>
|
|
||||||
/// <param name="height">Height of the chunk in cells</param>
|
|
||||||
/// <param name="depth">Depth of the chunk in cells</param>
|
|
||||||
/// <param name="isAllAir">Whether this is an all-air chunk</param>
|
|
||||||
public Chunk3D(int x, int y, int z, int width, int height, int depth, bool isAllAir = false)
|
|
||||||
{
|
|
||||||
X = x;
|
|
||||||
Y = y;
|
|
||||||
Z = z;
|
|
||||||
Width = width;
|
|
||||||
Height = height;
|
|
||||||
Depth = depth;
|
|
||||||
IsAllAir = isAllAir;
|
|
||||||
_data = isAllAir ? null : RentArray(width, height, depth);
|
|
||||||
Metadata = new Dictionary<string, object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Explicitly sets the chunk's position. Used internally for all-air singleton.
|
|
||||||
/// </summary>
|
|
||||||
internal void SetPosition(int x, int y, int z)
|
|
||||||
{
|
|
||||||
X = x;
|
|
||||||
Y = y;
|
|
||||||
Z = z;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the value at the given local chunk coordinates.
|
/// The chunk's data array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T this[int localX, int localY, int localZ]
|
private readonly T[,,]? _data;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (IsAllAir)
|
|
||||||
return default!;
|
|
||||||
return _data![localX, localY, localZ];
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (IsAllAir)
|
|
||||||
throw new InvalidOperationException("Cannot modify an all-air chunk.");
|
|
||||||
_data![localX, localY, localZ] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fills the chunk with a specified value.
|
/// Metadata dictionary for arbitrary chunk information (e.g., biome, tags).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Fill(T value)
|
public Dictionary<string, object> Metadata { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this chunk is the all-air singleton.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAllAir { get; private set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks whether the chunk has been disposed.
|
||||||
|
/// </summary>
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new 3D chunk at the specified position with the given dimensions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">X coordinate in chunk space</param>
|
||||||
|
/// <param name="y">Y coordinate in chunk space</param>
|
||||||
|
/// <param name="z">Z coordinate in chunk space</param>
|
||||||
|
/// <param name="width">Width of the chunk in cells</param>
|
||||||
|
/// <param name="height">Height of the chunk in cells</param>
|
||||||
|
/// <param name="depth">Depth of the chunk in cells</param>
|
||||||
|
public Chunk3D(int x, int y, int z, int width, int height, int depth)
|
||||||
|
: this(x, y, z, width, height, depth, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new 3D chunk at the specified position with the given dimensions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">X coordinate in chunk space</param>
|
||||||
|
/// <param name="y">Y coordinate in chunk space</param>
|
||||||
|
/// <param name="z">Z coordinate in chunk space</param>
|
||||||
|
/// <param name="width">Width of the chunk in cells</param>
|
||||||
|
/// <param name="height">Height of the chunk in cells</param>
|
||||||
|
/// <param name="depth">Depth of the chunk in cells</param>
|
||||||
|
/// <param name="isAllAir">Whether this is an all-air chunk</param>
|
||||||
|
public Chunk3D(int x, int y, int z, int width, int height, int depth, bool isAllAir = false)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Z = z;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
Depth = depth;
|
||||||
|
IsAllAir = isAllAir;
|
||||||
|
_data = isAllAir ? null : RentArray(width, height, depth);
|
||||||
|
Metadata = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Explicitly sets the chunk's position. Used internally for all-air singleton.
|
||||||
|
/// </summary>
|
||||||
|
internal void SetPosition(int x, int y, int z)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the value at the given local chunk coordinates.
|
||||||
|
/// </summary>
|
||||||
|
public T this[int localX, int localY, int localZ]
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
if (IsAllAir)
|
if (IsAllAir)
|
||||||
throw new InvalidOperationException("Cannot fill an all-air chunk.");
|
return default!;
|
||||||
|
return _data![localX, localY, localZ];
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
set
|
||||||
/// <summary>
|
|
||||||
/// Rents an array from the pool or creates a new one.
|
|
||||||
/// </summary>
|
|
||||||
private static T[,,] RentArray(int width, int height, int depth)
|
|
||||||
{
|
{
|
||||||
if (_arrayPool.TryTake(out var arr) &&
|
if (IsAllAir)
|
||||||
arr.GetLength(0) == width &&
|
throw new InvalidOperationException("Cannot modify an all-air chunk.");
|
||||||
arr.GetLength(1) == height &&
|
_data![localX, localY, localZ] = value;
|
||||||
arr.GetLength(2) == depth)
|
|
||||||
return arr;
|
|
||||||
return new T[width, height, depth];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an array to the pool.
|
|
||||||
/// </summary>
|
|
||||||
internal static void ReturnArray(T[,,] arr)
|
|
||||||
{
|
|
||||||
_arrayPool.Add(arr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the underlying data array (for pooling).
|
|
||||||
/// </summary>
|
|
||||||
internal T[,,]? GetDataArray() => _data;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases the data array back to the pool.
|
|
||||||
/// </summary>
|
|
||||||
internal void ReleaseDataArray()
|
|
||||||
{
|
|
||||||
if (_data != null && !IsAllAir)
|
|
||||||
{
|
|
||||||
ReturnArray(_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes the chunk and returns its resources to the pool.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (!_disposed)
|
|
||||||
{
|
|
||||||
ReleaseDataArray();
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fills the chunk with a specified value.
|
||||||
|
/// </summary>
|
||||||
|
public void Fill(T value)
|
||||||
|
{
|
||||||
|
if (IsAllAir)
|
||||||
|
throw new InvalidOperationException("Cannot fill an all-air chunk.");
|
||||||
|
|
||||||
|
for (int x = 0; x < Width; x++)
|
||||||
|
for (int y = 0; y < Height; y++)
|
||||||
|
for (int z = 0; z < Depth; z++)
|
||||||
|
_data![x, y, z] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rents an array from the pool or creates a new one.
|
||||||
|
/// </summary>
|
||||||
|
private static T[,,] RentArray(int width, int height, int depth)
|
||||||
|
{
|
||||||
|
if (_arrayPool.TryTake(out var arr) &&
|
||||||
|
arr.GetLength(0) == width &&
|
||||||
|
arr.GetLength(1) == height &&
|
||||||
|
arr.GetLength(2) == depth)
|
||||||
|
return arr;
|
||||||
|
return new T[width, height, depth];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an array to the pool.
|
||||||
|
/// </summary>
|
||||||
|
internal static void ReturnArray(T[,,] arr)
|
||||||
|
{
|
||||||
|
_arrayPool.Add(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the underlying data array (for pooling).
|
||||||
|
/// </summary>
|
||||||
|
internal T[,,]? GetDataArray() => _data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases the data array back to the pool.
|
||||||
|
/// </summary>
|
||||||
|
internal void ReleaseDataArray()
|
||||||
|
{
|
||||||
|
if (_data != null && !IsAllAir)
|
||||||
|
{
|
||||||
|
ReturnArray(_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes the chunk and returns its resources to the pool.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
ReleaseDataArray();
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,67 +1,67 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace AdvChkSys.Constraints
|
namespace AdvChkSys.Constraints
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents constraints and limits for the chunk world.
|
|
||||||
/// Can be used to restrict world size, chunk counts, and other resource limits.
|
|
||||||
/// </summary>
|
|
||||||
public class WorldConstraints
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? MinChunkX { get; set; }
|
public class WorldConstraints
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If set, the maximum allowed chunk X coordinate (inclusive).
|
|
||||||
/// </summary>
|
|
||||||
public int? MaxChunkX { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If set, the minimum allowed chunk Y coordinate (inclusive).
|
|
||||||
/// </summary>
|
|
||||||
public int? MinChunkY { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If set, the maximum allowed chunk Y coordinate (inclusive).
|
|
||||||
/// </summary>
|
|
||||||
public int? MaxChunkY { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If set, the minimum allowed chunk Z coordinate (inclusive).
|
|
||||||
/// </summary>
|
|
||||||
public int? MinChunkZ { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If set, the maximum allowed chunk Z coordinate (inclusive).
|
|
||||||
/// </summary>
|
|
||||||
public int? MaxChunkZ { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If set, the maximum number of chunks allowed to be loaded in memory at once.
|
|
||||||
/// </summary>
|
|
||||||
public int? MaxLoadedChunks { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the given chunk coordinates are within the allowed world bounds.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsWithinBounds(int chunkX, int chunkY)
|
|
||||||
{
|
{
|
||||||
if (MinChunkX.HasValue && chunkX < MinChunkX.Value) return false;
|
/// <summary>
|
||||||
if (MaxChunkX.HasValue && chunkX > MaxChunkX.Value) return false;
|
/// If set, the minimum allowed chunk X coordinate (inclusive).
|
||||||
if (MinChunkY.HasValue && chunkY < MinChunkY.Value) return false;
|
/// </summary>
|
||||||
if (MaxChunkY.HasValue && chunkY > MaxChunkY.Value) return false;
|
public int? MinChunkX { get; set; }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the current number of loaded chunks is within the allowed limit.
|
/// If set, the maximum allowed chunk X coordinate (inclusive).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsWithinChunkLimit(int loadedChunkCount)
|
public int? MaxChunkX { get; set; }
|
||||||
{
|
|
||||||
if (MaxLoadedChunks.HasValue && loadedChunkCount > MaxLoadedChunks.Value) return false;
|
/// <summary>
|
||||||
return true;
|
/// If set, the minimum allowed chunk Y coordinate (inclusive).
|
||||||
|
/// </summary>
|
||||||
|
public int? MinChunkY { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set, the maximum allowed chunk Y coordinate (inclusive).
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxChunkY { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set, the minimum allowed chunk Z coordinate (inclusive).
|
||||||
|
/// </summary>
|
||||||
|
public int? MinChunkZ { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set, the maximum allowed chunk Z coordinate (inclusive).
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxChunkZ { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set, the maximum number of chunks allowed to be loaded in memory at once.
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxLoadedChunks { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given chunk coordinates are within the allowed world bounds.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the current number of loaded chunks is within the allowed limit.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsWithinChunkLimit(int loadedChunkCount)
|
||||||
|
{
|
||||||
|
if (MaxLoadedChunks.HasValue && loadedChunkCount > MaxLoadedChunks.Value) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ namespace AdvChkSys.Diagnostics
|
|||||||
|
|
||||||
// Calculate estimated chunk memory usage
|
// Calculate estimated chunk memory usage
|
||||||
report.EstimatedChunkMemoryBytes = EstimateChunkMemoryUsage(report.ActiveChunkCount);
|
report.EstimatedChunkMemoryBytes = EstimateChunkMemoryUsage(report.ActiveChunkCount);
|
||||||
|
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,14 +51,16 @@ namespace AdvChkSys.Diagnostics
|
|||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
var memStatus = new MemoryHelper.MEMORYSTATUSEX();
|
var memStatus = new MemoryHelper.MEMORYSTATUSEX
|
||||||
memStatus.dwLength = (uint)Marshal.SizeOf(typeof(MemoryHelper.MEMORYSTATUSEX));
|
{
|
||||||
|
dwLength = (uint)Marshal.SizeOf(typeof(MemoryHelper.MEMORYSTATUSEX))
|
||||||
|
};
|
||||||
if (MemoryHelper.GlobalMemoryStatusEx(ref memStatus))
|
if (MemoryHelper.GlobalMemoryStatusEx(ref memStatus))
|
||||||
{
|
{
|
||||||
return memStatus.ullTotalPhys;
|
return memStatus.ullTotalPhys;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For non-Windows platforms or if Windows call fails, use a fallback
|
// For non-Windows platforms or if Windows call fails, use a fallback
|
||||||
return 8UL * 1024 * 1024 * 1024; // Assume 8GB as fallback
|
return 8UL * 1024 * 1024 * 1024; // Assume 8GB as fallback
|
||||||
}
|
}
|
||||||
@ -70,7 +72,7 @@ namespace AdvChkSys.Diagnostics
|
|||||||
public static void LogMemoryUsage(Action<string> logAction)
|
public static void LogMemoryUsage(Action<string> logAction)
|
||||||
{
|
{
|
||||||
var report = GetMemoryUsage();
|
var report = GetMemoryUsage();
|
||||||
|
|
||||||
logAction($"--- AdvChkSys Memory Report ({report.Timestamp:yyyy-MM-dd HH:mm:ss}) ---");
|
logAction($"--- AdvChkSys Memory Report ({report.Timestamp:yyyy-MM-dd HH:mm:ss}) ---");
|
||||||
logAction($"Active Chunks: {report.ActiveChunkCount:N0}");
|
logAction($"Active Chunks: {report.ActiveChunkCount:N0}");
|
||||||
logAction($"Estimated Chunk Memory: {FormatByteSize(report.EstimatedChunkMemoryBytes)}");
|
logAction($"Estimated Chunk Memory: {FormatByteSize(report.EstimatedChunkMemoryBytes)}");
|
||||||
@ -88,7 +90,7 @@ namespace AdvChkSys.Diagnostics
|
|||||||
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
|
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
|
||||||
double formattedSize = bytes;
|
double formattedSize = bytes;
|
||||||
int order = 0;
|
int order = 0;
|
||||||
|
|
||||||
while (formattedSize >= 1024 && order < sizes.Length - 1)
|
while (formattedSize >= 1024 && order < sizes.Length - 1)
|
||||||
{
|
{
|
||||||
order++;
|
order++;
|
||||||
@ -132,9 +134,9 @@ namespace AdvChkSys.Diagnostics
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Percentage of total system memory used by chunks.
|
/// Percentage of total system memory used by chunks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double MemoryUsagePercentage =>
|
public double MemoryUsagePercentage =>
|
||||||
TotalSystemMemoryBytes > 0
|
TotalSystemMemoryBytes > 0
|
||||||
? (double)EstimatedChunkMemoryBytes / TotalSystemMemoryBytes * 100
|
? (double)EstimatedChunkMemoryBytes / TotalSystemMemoryBytes * 100
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,123 +4,123 @@ using System.Threading;
|
|||||||
|
|
||||||
namespace AdvChkSys.Events
|
namespace AdvChkSys.Events
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Provides events for chunk lifecycle operations such as loading, unloading, saving, and more.
|
|
||||||
/// Thread-safe event subscription and invocation.
|
|
||||||
/// </summary>
|
|
||||||
public static class ChunkEvents
|
|
||||||
{
|
|
||||||
// Backing fields for thread-safe event handling
|
|
||||||
private static Action<Interfaces.IChunk>? _chunkLoaded;
|
|
||||||
private static Action<Interfaces.IChunk>? _chunkUnloaded;
|
|
||||||
private static Action<Interfaces.IChunk>? _chunkLoading;
|
|
||||||
private static Action<Interfaces.IChunk>? _chunkUnloading;
|
|
||||||
private static Action<Interfaces.IChunk>? _chunkSaving;
|
|
||||||
private static Action<Interfaces.IChunk>? _chunkSaved;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static event Action<Interfaces.IChunk> ChunkLoaded
|
public static class ChunkEvents
|
||||||
{
|
{
|
||||||
add => AddHandler(ref _chunkLoaded, value);
|
// Backing fields for thread-safe event handling
|
||||||
remove => RemoveHandler(ref _chunkLoaded, value);
|
private static Action<Interfaces.IChunk>? _chunkLoaded;
|
||||||
}
|
private static Action<Interfaces.IChunk>? _chunkUnloaded;
|
||||||
|
private static Action<Interfaces.IChunk>? _chunkLoading;
|
||||||
|
private static Action<Interfaces.IChunk>? _chunkUnloading;
|
||||||
|
private static Action<Interfaces.IChunk>? _chunkSaving;
|
||||||
|
private static Action<Interfaces.IChunk>? _chunkSaved;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when a chunk is about to be loaded into memory.
|
/// Occurs when a chunk has been loaded into memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static event Action<Interfaces.IChunk> ChunkLoading
|
public static event Action<Interfaces.IChunk> ChunkLoaded
|
||||||
{
|
|
||||||
add => AddHandler(ref _chunkLoading, value);
|
|
||||||
remove => RemoveHandler(ref _chunkLoading, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a chunk is about to be unloaded from memory.
|
|
||||||
/// </summary>
|
|
||||||
public static event Action<Interfaces.IChunk> ChunkUnloading
|
|
||||||
{
|
|
||||||
add => AddHandler(ref _chunkUnloading, value);
|
|
||||||
remove => RemoveHandler(ref _chunkUnloading, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a chunk has been unloaded from memory.
|
|
||||||
/// </summary>
|
|
||||||
public static event Action<Interfaces.IChunk> ChunkUnloaded
|
|
||||||
{
|
|
||||||
add => AddHandler(ref _chunkUnloaded, value);
|
|
||||||
remove => RemoveHandler(ref _chunkUnloaded, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a chunk is about to be saved.
|
|
||||||
/// </summary>
|
|
||||||
public static event Action<Interfaces.IChunk> ChunkSaving
|
|
||||||
{
|
|
||||||
add => AddHandler(ref _chunkSaving, value);
|
|
||||||
remove => RemoveHandler(ref _chunkSaving, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a chunk has been saved.
|
|
||||||
/// </summary>
|
|
||||||
public static event Action<Interfaces.IChunk> ChunkSaved
|
|
||||||
{
|
|
||||||
add => AddHandler(ref _chunkSaved, value);
|
|
||||||
remove => RemoveHandler(ref _chunkSaved, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raises the ChunkLoading event.
|
|
||||||
/// </summary>
|
|
||||||
public static void OnChunkLoading(Interfaces.IChunk chunk) => _chunkLoading?.Invoke(chunk);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raises the ChunkLoaded event.
|
|
||||||
/// </summary>
|
|
||||||
public static void OnChunkLoaded(Interfaces.IChunk chunk) => _chunkLoaded?.Invoke(chunk);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raises the ChunkUnloading event.
|
|
||||||
/// </summary>
|
|
||||||
public static void OnChunkUnloading(Interfaces.IChunk chunk) => _chunkUnloading?.Invoke(chunk);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raises the ChunkUnloaded event.
|
|
||||||
/// </summary>
|
|
||||||
public static void OnChunkUnloaded(Interfaces.IChunk chunk) => _chunkUnloaded?.Invoke(chunk);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raises the ChunkSaving event.
|
|
||||||
/// </summary>
|
|
||||||
public static void OnChunkSaving(Interfaces.IChunk chunk) => _chunkSaving?.Invoke(chunk);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raises the ChunkSaved event.
|
|
||||||
/// </summary>
|
|
||||||
public static void OnChunkSaved(Interfaces.IChunk chunk) => _chunkSaved?.Invoke(chunk);
|
|
||||||
|
|
||||||
// Thread-safe add/remove for event handlers
|
|
||||||
private static void AddHandler(ref Action<Interfaces.IChunk>? field, Action<Interfaces.IChunk> handler)
|
|
||||||
{
|
|
||||||
Action<Interfaces.IChunk>? prevHandler, newHandler;
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
prevHandler = field;
|
add => AddHandler(ref _chunkLoaded, value);
|
||||||
newHandler = (Action<Interfaces.IChunk>?)Delegate.Combine(prevHandler, handler);
|
remove => RemoveHandler(ref _chunkLoaded, value);
|
||||||
} while (Interlocked.CompareExchange(ref field, newHandler, prevHandler) != prevHandler);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void RemoveHandler(ref Action<Interfaces.IChunk>? field, Action<Interfaces.IChunk> handler)
|
/// <summary>
|
||||||
{
|
/// Occurs when a chunk is about to be loaded into memory.
|
||||||
Action<Interfaces.IChunk>? prevHandler, newHandler;
|
/// </summary>
|
||||||
do
|
public static event Action<Interfaces.IChunk> ChunkLoading
|
||||||
{
|
{
|
||||||
prevHandler = field;
|
add => AddHandler(ref _chunkLoading, value);
|
||||||
newHandler = (Action<Interfaces.IChunk>?)Delegate.Remove(prevHandler, handler);
|
remove => RemoveHandler(ref _chunkLoading, value);
|
||||||
} while (Interlocked.CompareExchange(ref field, newHandler, prevHandler) != prevHandler);
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a chunk is about to be unloaded from memory.
|
||||||
|
/// </summary>
|
||||||
|
public static event Action<Interfaces.IChunk> ChunkUnloading
|
||||||
|
{
|
||||||
|
add => AddHandler(ref _chunkUnloading, value);
|
||||||
|
remove => RemoveHandler(ref _chunkUnloading, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a chunk has been unloaded from memory.
|
||||||
|
/// </summary>
|
||||||
|
public static event Action<Interfaces.IChunk> ChunkUnloaded
|
||||||
|
{
|
||||||
|
add => AddHandler(ref _chunkUnloaded, value);
|
||||||
|
remove => RemoveHandler(ref _chunkUnloaded, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a chunk is about to be saved.
|
||||||
|
/// </summary>
|
||||||
|
public static event Action<Interfaces.IChunk> ChunkSaving
|
||||||
|
{
|
||||||
|
add => AddHandler(ref _chunkSaving, value);
|
||||||
|
remove => RemoveHandler(ref _chunkSaving, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a chunk has been saved.
|
||||||
|
/// </summary>
|
||||||
|
public static event Action<Interfaces.IChunk> ChunkSaved
|
||||||
|
{
|
||||||
|
add => AddHandler(ref _chunkSaved, value);
|
||||||
|
remove => RemoveHandler(ref _chunkSaved, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the ChunkLoading event.
|
||||||
|
/// </summary>
|
||||||
|
public static void OnChunkLoading(Interfaces.IChunk chunk) => _chunkLoading?.Invoke(chunk);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the ChunkLoaded event.
|
||||||
|
/// </summary>
|
||||||
|
public static void OnChunkLoaded(Interfaces.IChunk chunk) => _chunkLoaded?.Invoke(chunk);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the ChunkUnloading event.
|
||||||
|
/// </summary>
|
||||||
|
public static void OnChunkUnloading(Interfaces.IChunk chunk) => _chunkUnloading?.Invoke(chunk);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the ChunkUnloaded event.
|
||||||
|
/// </summary>
|
||||||
|
public static void OnChunkUnloaded(Interfaces.IChunk chunk) => _chunkUnloaded?.Invoke(chunk);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the ChunkSaving event.
|
||||||
|
/// </summary>
|
||||||
|
public static void OnChunkSaving(Interfaces.IChunk chunk) => _chunkSaving?.Invoke(chunk);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the ChunkSaved event.
|
||||||
|
/// </summary>
|
||||||
|
public static void OnChunkSaved(Interfaces.IChunk chunk) => _chunkSaved?.Invoke(chunk);
|
||||||
|
|
||||||
|
// Thread-safe add/remove for event handlers
|
||||||
|
private static void AddHandler(ref Action<Interfaces.IChunk>? field, Action<Interfaces.IChunk> handler)
|
||||||
|
{
|
||||||
|
Action<Interfaces.IChunk>? prevHandler, newHandler;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
prevHandler = field;
|
||||||
|
newHandler = (Action<Interfaces.IChunk>?)Delegate.Combine(prevHandler, handler);
|
||||||
|
} while (Interlocked.CompareExchange(ref field, newHandler, prevHandler) != prevHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveHandler(ref Action<Interfaces.IChunk>? field, Action<Interfaces.IChunk> handler)
|
||||||
|
{
|
||||||
|
Action<Interfaces.IChunk>? prevHandler, newHandler;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
prevHandler = field;
|
||||||
|
newHandler = (Action<Interfaces.IChunk>?)Delegate.Remove(prevHandler, handler);
|
||||||
|
} while (Interlocked.CompareExchange(ref field, newHandler, prevHandler) != prevHandler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -12,7 +12,7 @@ namespace AdvChkSys.Interfaces
|
|||||||
/// The chunk's X position in chunk-space coordinates.
|
/// The chunk's X position in chunk-space coordinates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int X { get; }
|
int X { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chunk's Y position in chunk-space coordinates.
|
/// The chunk's Y position in chunk-space coordinates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -2,37 +2,37 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace AdvChkSys.Interfaces
|
namespace AdvChkSys.Interfaces
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for managing chunks in memory.
|
|
||||||
/// Provides methods for loading, unloading, and accessing chunks.
|
|
||||||
/// </summary>
|
|
||||||
public interface IChunkManager
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the chunk at (x, y) is loaded.
|
/// Interface for managing chunks in memory.
|
||||||
|
/// Provides methods for loading, unloading, and accessing chunks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsChunkLoaded(int x, int y);
|
public interface IChunkManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the chunk at (x, y) is loaded.
|
||||||
|
/// </summary>
|
||||||
|
bool IsChunkLoaded(int x, int y);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the chunk at (x, y) if loaded, or null if not.
|
/// Gets the chunk at (x, y) if loaded, or null if not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IChunk? GetChunk(int x, int y);
|
IChunk? GetChunk(int x, int y);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads or creates a chunk at (x, y) with the given size.
|
/// Loads or creates a chunk at (x, y) with the given size.
|
||||||
/// Returns the loaded or newly created chunk.
|
/// Returns the loaded or newly created chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IChunk LoadOrCreateChunk(int x, int y, int width, int height);
|
IChunk LoadOrCreateChunk(int x, int y, int width, int height);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unloads (removes) the chunk at (x, y) if loaded.
|
/// Unloads (removes) the chunk at (x, y) if loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool UnloadChunk(int x, int y);
|
bool UnloadChunk(int x, int y);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enumerates all loaded chunks.
|
/// Enumerates all loaded chunks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IEnumerable<IChunk> GetAllChunks();
|
IEnumerable<IChunk> GetAllChunks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,23 +20,23 @@ namespace AdvChkSys.Manager
|
|||||||
private readonly WorldConstraints? _constraints;
|
private readonly WorldConstraints? _constraints;
|
||||||
private readonly int _capacity;
|
private readonly int _capacity;
|
||||||
private readonly Dictionary<(int, int), Task<Chunk2D<T>>> _loadingChunks = new();
|
private readonly Dictionary<(int, int), Task<Chunk2D<T>>> _loadingChunks = new();
|
||||||
private readonly object _lock = new object();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for custom air check logic
|
/// Delegate for custom air check logic
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Func<int, int, int, int, bool>? _airCheckDelegate;
|
private Func<int, int, int, int, bool>? _airCheckDelegate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// World generator for determining empty regions
|
/// World generator for determining empty regions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IWorldGenerator? _worldGenerator;
|
private IWorldGenerator? _worldGenerator;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Data provider for determining empty regions
|
/// Data provider for determining empty regions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IDataProvider? _dataProvider;
|
private IDataProvider? _dataProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Height map for determining sky chunks
|
/// Height map for determining sky chunks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -155,7 +155,7 @@ namespace AdvChkSys.Manager
|
|||||||
{
|
{
|
||||||
var key = (x, y);
|
var key = (x, y);
|
||||||
Task<Chunk2D<T>>? task = null;
|
Task<Chunk2D<T>>? task = null;
|
||||||
|
|
||||||
// First check if we already have a task
|
// First check if we already have a task
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
@ -173,7 +173,7 @@ namespace AdvChkSys.Manager
|
|||||||
|
|
||||||
// Otherwise, create a new task outside the lock
|
// Otherwise, create a new task outside the lock
|
||||||
task = Task.Run(() => LoadOrCreateChunk(x, y, width, height));
|
task = Task.Run(() => LoadOrCreateChunk(x, y, width, height));
|
||||||
|
|
||||||
// Register the task
|
// Register the task
|
||||||
lock (_lock)
|
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);
|
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);
|
bool IChunkManager.UnloadChunk(int x, int y) => UnloadChunk(x, y);
|
||||||
IEnumerable<IChunk> IChunkManager.GetAllChunks() => _chunks.Values;
|
IEnumerable<IChunk> IChunkManager.GetAllChunks() => _chunks.Values;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a custom delegate for determining if a region should be all air.
|
/// Sets a custom delegate for determining if a region should be all air.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -252,7 +252,7 @@ namespace AdvChkSys.Manager
|
|||||||
{
|
{
|
||||||
_airCheckDelegate = airCheckDelegate;
|
_airCheckDelegate = airCheckDelegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a world generator for determining empty regions.
|
/// Sets a world generator for determining empty regions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -260,7 +260,7 @@ namespace AdvChkSys.Manager
|
|||||||
{
|
{
|
||||||
_worldGenerator = worldGenerator;
|
_worldGenerator = worldGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a data provider for determining empty regions.
|
/// Sets a data provider for determining empty regions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -268,7 +268,7 @@ namespace AdvChkSys.Manager
|
|||||||
{
|
{
|
||||||
_dataProvider = dataProvider;
|
_dataProvider = dataProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a height map for determining sky chunks.
|
/// Sets a height map for determining sky chunks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -277,7 +277,7 @@ namespace AdvChkSys.Manager
|
|||||||
_heightMap = heightMap;
|
_heightMap = heightMap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for world generators that can determine if regions are empty.
|
/// Interface for world generators that can determine if regions are empty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -288,7 +288,7 @@ namespace AdvChkSys.Manager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsRegionEmpty(int x, int y, int width, int height);
|
bool IsRegionEmpty(int x, int y, int width, int height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for data providers that can determine if regions are empty.
|
/// Interface for data providers that can determine if regions are empty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -299,7 +299,7 @@ namespace AdvChkSys.Manager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsEmptyRegion(int x, int y, int width, int height);
|
bool IsEmptyRegion(int x, int y, int width, int height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for height maps that can determine the maximum height at a position.
|
/// Interface for height maps that can determine the maximum height at a position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -21,7 +21,7 @@ namespace AdvChkSys.Manager
|
|||||||
private readonly LRUCache<(int, int, int), Chunk3D<T>> _chunks;
|
private readonly LRUCache<(int, int, int), Chunk3D<T>> _chunks;
|
||||||
private readonly WorldConstraints? _constraints;
|
private readonly WorldConstraints? _constraints;
|
||||||
private readonly int _capacity;
|
private readonly int _capacity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for custom air check logic
|
/// Delegate for custom air check logic
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -67,7 +67,7 @@ namespace AdvChkSys.Manager
|
|||||||
_chunks.TryGet((x, y, z), out var chunk);
|
_chunks.TryGet((x, y, z), out var chunk);
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if a region should be represented as an all-air chunk.
|
/// Determines if a region should be represented as an all-air chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -139,7 +139,7 @@ namespace AdvChkSys.Manager
|
|||||||
arr = arrField.GetValue(chunk) as T[,,];
|
arr = arrField.GetValue(chunk) as T[,,];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chunk.IsAllAir && arr != null)
|
if (!chunk.IsAllAir && arr != null)
|
||||||
{
|
{
|
||||||
Chunk3D<T>.ReturnArray(arr);
|
Chunk3D<T>.ReturnArray(arr);
|
||||||
@ -175,7 +175,7 @@ namespace AdvChkSys.Manager
|
|||||||
LoadOrCreateChunk(x, y, 0, width, height, 1);
|
LoadOrCreateChunk(x, y, 0, width, height, 1);
|
||||||
bool IChunkManager.UnloadChunk(int x, int y) => UnloadChunk(x, y, 0);
|
bool IChunkManager.UnloadChunk(int x, int y) => UnloadChunk(x, y, 0);
|
||||||
IEnumerable<IChunk> IChunkManager.GetAllChunks() => _chunks.Values;
|
IEnumerable<IChunk> IChunkManager.GetAllChunks() => _chunks.Values;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a custom delegate for determining if a region should be all air.
|
/// Sets a custom delegate for determining if a region should be all air.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -208,7 +208,7 @@ namespace AdvChkSys.Manager
|
|||||||
_heightMap = heightMap;
|
_heightMap = heightMap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for 3D world generators that can determine if regions are empty.
|
/// Interface for 3D world generators that can determine if regions are empty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -6,67 +6,67 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace AdvChkSys.Resources
|
namespace AdvChkSys.Resources
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Manages allocation and release of chunk resources in memory.
|
|
||||||
/// Can be extended to track resource usage, pooling, or implement custom memory strategies.
|
|
||||||
/// </summary>
|
|
||||||
public static class ChunkResourceManager
|
|
||||||
{
|
|
||||||
// Example: Track allocated chunks (for diagnostics, pooling, or resource limits)
|
|
||||||
private static readonly ConcurrentDictionary<IChunk, DateTime> _allocatedChunks = new ConcurrentDictionary<IChunk, DateTime>();
|
|
||||||
private static int _allocatedChunkCount;
|
|
||||||
private static readonly object _lock = new object();
|
|
||||||
private static readonly HashSet<IChunk> _activeChunks = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when a chunk is allocated/loaded into memory.
|
/// Manages allocation and release of chunk resources in memory.
|
||||||
/// Tracks the chunk and can be extended for pooling or resource limits.
|
/// Can be extended to track resource usage, pooling, or implement custom memory strategies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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<IChunk, DateTime> _allocatedChunks = new();
|
||||||
|
private static int _allocatedChunkCount;
|
||||||
|
private static readonly object _lock = new();
|
||||||
|
private static readonly HashSet<IChunk> _activeChunks = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a chunk is allocated/loaded into memory.
|
||||||
|
/// Tracks the chunk and can be extended for pooling or resource limits.
|
||||||
|
/// </summary>
|
||||||
|
public static void AllocateChunk(IChunk chunk)
|
||||||
{
|
{
|
||||||
_allocatedChunkCount++;
|
lock (_lock)
|
||||||
_activeChunks.Add(chunk);
|
{
|
||||||
|
_allocatedChunkCount++;
|
||||||
|
_activeChunks.Add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a chunk is released/unloaded from memory.
|
||||||
|
/// Removes the chunk from tracking and can be extended for pooling or cleanup.
|
||||||
|
/// </summary>
|
||||||
|
public static void ReleaseChunk(IChunk chunk)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_allocatedChunkCount--;
|
||||||
|
_activeChunks.Remove(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current number of allocated chunks.
|
||||||
|
/// </summary>
|
||||||
|
public static int AllocatedChunkCount => _allocatedChunks.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all tracked chunks (for diagnostics or shutdown).
|
||||||
|
/// </summary>
|
||||||
|
public static void Clear()
|
||||||
|
{
|
||||||
|
_allocatedChunks.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current count of active chunks being tracked by the resource manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The number of active chunks</returns>
|
||||||
|
public static int GetActiveChunkCount()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return _activeChunks.Count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when a chunk is released/unloaded from memory.
|
|
||||||
/// Removes the chunk from tracking and can be extended for pooling or cleanup.
|
|
||||||
/// </summary>
|
|
||||||
public static void ReleaseChunk(IChunk chunk)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_allocatedChunkCount--;
|
|
||||||
_activeChunks.Remove(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current number of allocated chunks.
|
|
||||||
/// </summary>
|
|
||||||
public static int AllocatedChunkCount => _allocatedChunks.Count;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears all tracked chunks (for diagnostics or shutdown).
|
|
||||||
/// </summary>
|
|
||||||
public static void Clear()
|
|
||||||
{
|
|
||||||
_allocatedChunks.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current count of active chunks being tracked by the resource manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The number of active chunks</returns>
|
|
||||||
public static int GetActiveChunkCount()
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
return _activeChunks.Count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -15,19 +15,19 @@ namespace AdvChkSys.Spatial
|
|||||||
{
|
{
|
||||||
return new Chunk3DAdapter<T>(chunk);
|
return new Chunk3DAdapter<T>(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adapter to make Chunk3D implement IChunk3D.
|
/// Adapter to make Chunk3D implement IChunk3D.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private class Chunk3DAdapter<T> : IChunk3D
|
private class Chunk3DAdapter<T> : IChunk3D
|
||||||
{
|
{
|
||||||
private readonly Chunk3D<T> _chunk;
|
private readonly Chunk3D<T> _chunk;
|
||||||
|
|
||||||
public Chunk3DAdapter(Chunk3D<T> chunk)
|
public Chunk3DAdapter(Chunk3D<T> chunk)
|
||||||
{
|
{
|
||||||
_chunk = chunk;
|
_chunk = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int X => _chunk.X;
|
public int X => _chunk.X;
|
||||||
public int Y => _chunk.Y;
|
public int Y => _chunk.Y;
|
||||||
public int Z => _chunk.Z;
|
public int Z => _chunk.Z;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -13,16 +13,16 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Lock objects for each chunk
|
// Lock objects for each chunk
|
||||||
private readonly ConcurrentDictionary<IChunk, SemaphoreSlim> _locks = new();
|
private readonly ConcurrentDictionary<IChunk, SemaphoreSlim> _locks = new();
|
||||||
|
|
||||||
// Timer for cleanup
|
// Timer for cleanup
|
||||||
private readonly Timer _cleanupTimer;
|
private readonly Timer _cleanupTimer;
|
||||||
|
|
||||||
// Last access time for each lock
|
// Last access time for each lock
|
||||||
private readonly ConcurrentDictionary<IChunk, DateTime> _lastAccessTime = new();
|
private readonly ConcurrentDictionary<IChunk, DateTime> _lastAccessTime = new();
|
||||||
|
|
||||||
// Cleanup interval in minutes
|
// Cleanup interval in minutes
|
||||||
private readonly int _cleanupIntervalMinutes;
|
private readonly int _cleanupIntervalMinutes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the ChunkAsyncLock class.
|
/// Initializes a new instance of the ChunkAsyncLock class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -30,11 +30,11 @@ namespace AdvChkSys.Threading
|
|||||||
public ChunkAsyncLock(int cleanupIntervalMinutes = 10)
|
public ChunkAsyncLock(int cleanupIntervalMinutes = 10)
|
||||||
{
|
{
|
||||||
_cleanupIntervalMinutes = cleanupIntervalMinutes;
|
_cleanupIntervalMinutes = cleanupIntervalMinutes;
|
||||||
_cleanupTimer = new Timer(CleanupUnusedLocks, null,
|
_cleanupTimer = new Timer(CleanupUnusedLocks, null,
|
||||||
TimeSpan.FromMinutes(cleanupIntervalMinutes),
|
TimeSpan.FromMinutes(cleanupIntervalMinutes),
|
||||||
TimeSpan.FromMinutes(cleanupIntervalMinutes));
|
TimeSpan.FromMinutes(cleanupIntervalMinutes));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Acquires a lock on a chunk asynchronously.
|
/// Acquires a lock on a chunk asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -45,7 +45,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var semaphore = _locks.GetOrAdd(chunk, _ => new SemaphoreSlim(1, 1));
|
var semaphore = _locks.GetOrAdd(chunk, _ => new SemaphoreSlim(1, 1));
|
||||||
_lastAccessTime[chunk] = DateTime.UtcNow;
|
_lastAccessTime[chunk] = DateTime.UtcNow;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
@ -60,13 +60,13 @@ namespace AdvChkSys.Threading
|
|||||||
_lastAccessTime.TryRemove(chunk, out _);
|
_lastAccessTime.TryRemove(chunk, out _);
|
||||||
semaphore.Dispose();
|
semaphore.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LockReleaser(semaphore, chunk, this);
|
return new LockReleaser(semaphore, chunk, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to acquire a lock on a chunk asynchronously with a timeout.
|
/// Tries to acquire a lock on a chunk asynchronously with a timeout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -75,24 +75,24 @@ namespace AdvChkSys.Threading
|
|||||||
/// <param name="cancellationToken">Cancellation token</param>
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
/// <returns>A tuple with a boolean indicating success and the lock releaser if successful</returns>
|
/// <returns>A tuple with a boolean indicating success and the lock releaser if successful</returns>
|
||||||
public async Task<(bool Success, IDisposable? LockReleaser)> TryLockAsync(
|
public async Task<(bool Success, IDisposable? LockReleaser)> TryLockAsync(
|
||||||
IChunk chunk,
|
IChunk chunk,
|
||||||
TimeSpan timeout,
|
TimeSpan timeout,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (chunk == null)
|
if (chunk == null)
|
||||||
throw new ArgumentNullException(nameof(chunk));
|
throw new ArgumentNullException(nameof(chunk));
|
||||||
|
|
||||||
var semaphore = _locks.GetOrAdd(chunk, _ => new SemaphoreSlim(1, 1));
|
var semaphore = _locks.GetOrAdd(chunk, _ => new SemaphoreSlim(1, 1));
|
||||||
_lastAccessTime[chunk] = DateTime.UtcNow;
|
_lastAccessTime[chunk] = DateTime.UtcNow;
|
||||||
|
|
||||||
if (await semaphore.WaitAsync(timeout, cancellationToken).ConfigureAwait(false))
|
if (await semaphore.WaitAsync(timeout, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
return (true, new LockReleaser(semaphore, chunk, this));
|
return (true, new LockReleaser(semaphore, chunk, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
return (false, null);
|
return (false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cleans up unused locks.
|
/// Cleans up unused locks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -100,10 +100,10 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var threshold = now.AddMinutes(-_cleanupIntervalMinutes);
|
var threshold = now.AddMinutes(-_cleanupIntervalMinutes);
|
||||||
|
|
||||||
foreach (var chunk in _lastAccessTime.Keys)
|
foreach (var chunk in _lastAccessTime.Keys)
|
||||||
{
|
{
|
||||||
if (_lastAccessTime.TryGetValue(chunk, out var lastAccess) &&
|
if (_lastAccessTime.TryGetValue(chunk, out var lastAccess) &&
|
||||||
lastAccess < threshold &&
|
lastAccess < threshold &&
|
||||||
_locks.TryGetValue(chunk, out var semaphore))
|
_locks.TryGetValue(chunk, out var semaphore))
|
||||||
{
|
{
|
||||||
@ -119,23 +119,23 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes resources.
|
/// Disposes resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cleanupTimer.Dispose();
|
_cleanupTimer.Dispose();
|
||||||
|
|
||||||
foreach (var semaphore in _locks.Values)
|
foreach (var semaphore in _locks.Values)
|
||||||
{
|
{
|
||||||
semaphore.Dispose();
|
semaphore.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_locks.Clear();
|
_locks.Clear();
|
||||||
_lastAccessTime.Clear();
|
_lastAccessTime.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases a lock when disposed.
|
/// Releases a lock when disposed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -145,14 +145,14 @@ namespace AdvChkSys.Threading
|
|||||||
private readonly IChunk _chunk;
|
private readonly IChunk _chunk;
|
||||||
private readonly ChunkAsyncLock _parent;
|
private readonly ChunkAsyncLock _parent;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public LockReleaser(SemaphoreSlim semaphore, IChunk chunk, ChunkAsyncLock parent)
|
public LockReleaser(SemaphoreSlim semaphore, IChunk chunk, ChunkAsyncLock parent)
|
||||||
{
|
{
|
||||||
_semaphore = semaphore;
|
_semaphore = semaphore;
|
||||||
_chunk = chunk;
|
_chunk = chunk;
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
|
|||||||
@ -14,16 +14,16 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Queue of pending operations for each chunk
|
// Queue of pending operations for each chunk
|
||||||
private readonly ConcurrentDictionary<IChunk, Queue<(Func<Task> Operation, TaskCompletionSource<object?> Completion)>> _pendingOperations = new();
|
private readonly ConcurrentDictionary<IChunk, Queue<(Func<Task> Operation, TaskCompletionSource<object?> Completion)>> _pendingOperations = new();
|
||||||
|
|
||||||
// Currently active operations
|
// Currently active operations
|
||||||
private readonly ConcurrentDictionary<IChunk, Task> _activeOperations = new();
|
private readonly ConcurrentDictionary<IChunk, Task> _activeOperations = new();
|
||||||
|
|
||||||
// Semaphore to limit concurrent operations
|
// Semaphore to limit concurrent operations
|
||||||
private readonly SemaphoreSlim _semaphore;
|
private readonly SemaphoreSlim _semaphore;
|
||||||
|
|
||||||
// Cancellation for shutdown
|
// Cancellation for shutdown
|
||||||
private readonly CancellationTokenSource _shutdownCts = new();
|
private readonly CancellationTokenSource _shutdownCts = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the ChunkOperationQueue class.
|
/// Initializes a new instance of the ChunkOperationQueue class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -32,7 +32,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
_semaphore = new SemaphoreSlim(maxConcurrentOperations, maxConcurrentOperations);
|
_semaphore = new SemaphoreSlim(maxConcurrentOperations, maxConcurrentOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enqueues an operation to be performed on a chunk.
|
/// Enqueues an operation to be performed on a chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -43,31 +43,31 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
if (chunk == null)
|
if (chunk == null)
|
||||||
throw new ArgumentNullException(nameof(chunk));
|
throw new ArgumentNullException(nameof(chunk));
|
||||||
|
|
||||||
if (operation == null)
|
if (operation == null)
|
||||||
throw new ArgumentNullException(nameof(operation));
|
throw new ArgumentNullException(nameof(operation));
|
||||||
|
|
||||||
// Create a completion source for this operation
|
// Create a completion source for this operation
|
||||||
var completion = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var completion = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
// Get or create the queue for this chunk
|
// Get or create the queue for this chunk
|
||||||
var queue = _pendingOperations.GetOrAdd(chunk, _ => new Queue<(Func<Task>, TaskCompletionSource<object?>)>());
|
var queue = _pendingOperations.GetOrAdd(chunk, _ => new Queue<(Func<Task>, TaskCompletionSource<object?>)>());
|
||||||
|
|
||||||
// Add the operation to the queue
|
// Add the operation to the queue
|
||||||
lock (queue)
|
lock (queue)
|
||||||
{
|
{
|
||||||
queue.Enqueue((operation, completion));
|
queue.Enqueue((operation, completion));
|
||||||
|
|
||||||
// If this is the only operation, start processing
|
// If this is the only operation, start processing
|
||||||
if (queue.Count == 1 && !_activeOperations.ContainsKey(chunk))
|
if (queue.Count == 1 && !_activeOperations.ContainsKey(chunk))
|
||||||
{
|
{
|
||||||
StartProcessingChunkOperations(chunk);
|
StartProcessingChunkOperations(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return completion.Task;
|
return completion.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts processing operations for a chunk.
|
/// Starts processing operations for a chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -75,7 +75,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Wait for a semaphore slot
|
// Wait for a semaphore slot
|
||||||
await _semaphore.WaitAsync(_shutdownCts.Token).ConfigureAwait(false);
|
await _semaphore.WaitAsync(_shutdownCts.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Process operations until the queue is empty
|
// Process operations until the queue is empty
|
||||||
@ -83,9 +83,9 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Get the next operation
|
// Get the next operation
|
||||||
(Func<Task> operation, TaskCompletionSource<object?> completion) nextOperation;
|
(Func<Task> operation, TaskCompletionSource<object?> completion) nextOperation;
|
||||||
|
|
||||||
var queue = _pendingOperations.GetOrAdd(chunk, _ => new Queue<(Func<Task>, TaskCompletionSource<object?>)>());
|
var queue = _pendingOperations.GetOrAdd(chunk, _ => new Queue<(Func<Task>, TaskCompletionSource<object?>)>());
|
||||||
|
|
||||||
lock (queue)
|
lock (queue)
|
||||||
{
|
{
|
||||||
if (queue.Count == 0)
|
if (queue.Count == 0)
|
||||||
@ -94,17 +94,17 @@ namespace AdvChkSys.Threading
|
|||||||
_activeOperations.TryRemove(chunk, out _);
|
_activeOperations.TryRemove(chunk, out _);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextOperation = queue.Peek();
|
nextOperation = queue.Peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the operation
|
// Execute the operation
|
||||||
var task = ExecuteOperationAsync(chunk, nextOperation.operation, nextOperation.completion);
|
var task = ExecuteOperationAsync(chunk, nextOperation.operation, nextOperation.completion);
|
||||||
_activeOperations[chunk] = task;
|
_activeOperations[chunk] = task;
|
||||||
|
|
||||||
// Wait for completion
|
// Wait for completion
|
||||||
await task.ConfigureAwait(false);
|
await task.ConfigureAwait(false);
|
||||||
|
|
||||||
// Remove the completed operation
|
// Remove the completed operation
|
||||||
lock (queue)
|
lock (queue)
|
||||||
{
|
{
|
||||||
@ -132,12 +132,12 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Track the operation for diagnostics
|
// Track the operation for diagnostics
|
||||||
var operationId = ChunkThreadingDiagnostics.TrackOperationStart("ChunkOperation", chunk);
|
var operationId = ChunkThreadingDiagnostics.TrackOperationStart("ChunkOperation", chunk);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Execute the operation
|
// Execute the operation
|
||||||
await operation().ConfigureAwait(false);
|
await operation().ConfigureAwait(false);
|
||||||
|
|
||||||
// Complete the task
|
// Complete the task
|
||||||
completion.TrySetResult(null);
|
completion.TrySetResult(null);
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Set the exception
|
// Set the exception
|
||||||
completion.TrySetException(ex);
|
completion.TrySetException(ex);
|
||||||
|
|
||||||
// Log the error
|
// Log the error
|
||||||
ChunkThreadingDiagnostics.LogEvent("OperationError", $"Error in chunk operation: {ex.Message}");
|
ChunkThreadingDiagnostics.LogEvent("OperationError", $"Error in chunk operation: {ex.Message}");
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// This should never happen, but just in case
|
// This should never happen, but just in case
|
||||||
completion.TrySetException(ex);
|
completion.TrySetException(ex);
|
||||||
|
|
||||||
// Log the error
|
// Log the error
|
||||||
ChunkThreadingDiagnostics.LogEvent("CriticalError", $"Critical error in operation execution: {ex.Message}");
|
ChunkThreadingDiagnostics.LogEvent("CriticalError", $"Critical error in operation execution: {ex.Message}");
|
||||||
}
|
}
|
||||||
@ -177,7 +177,7 @@ namespace AdvChkSys.Threading
|
|||||||
return queue.Count;
|
return queue.Count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,18 +217,18 @@ namespace AdvChkSys.Threading
|
|||||||
lock (queue)
|
lock (queue)
|
||||||
{
|
{
|
||||||
int count = queue.Count;
|
int count = queue.Count;
|
||||||
|
|
||||||
// Cancel all pending operations
|
// Cancel all pending operations
|
||||||
while (queue.Count > 0)
|
while (queue.Count > 0)
|
||||||
{
|
{
|
||||||
var operation = queue.Dequeue();
|
var (Operation, Completion) = queue.Dequeue();
|
||||||
operation.Completion.TrySetCanceled();
|
Completion.TrySetCanceled();
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,12 +239,12 @@ namespace AdvChkSys.Threading
|
|||||||
public int CancelAllOperations()
|
public int CancelAllOperations()
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
foreach (var chunk in _pendingOperations.Keys)
|
foreach (var chunk in _pendingOperations.Keys)
|
||||||
{
|
{
|
||||||
count += CancelOperations(chunk);
|
count += CancelOperations(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,17 +255,17 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Cancel all operations
|
// Cancel all operations
|
||||||
_shutdownCts.Cancel();
|
_shutdownCts.Cancel();
|
||||||
|
|
||||||
// Cancel all pending operations
|
// Cancel all pending operations
|
||||||
CancelAllOperations();
|
CancelAllOperations();
|
||||||
|
|
||||||
// Wait for active operations to complete
|
// Wait for active operations to complete
|
||||||
var tasks = new List<Task>(_activeOperations.Values);
|
var tasks = new List<Task>(_activeOperations.Values);
|
||||||
if (tasks.Count > 0)
|
if (tasks.Count > 0)
|
||||||
{
|
{
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispose resources
|
// Dispose resources
|
||||||
_shutdownCts.Dispose();
|
_shutdownCts.Dispose();
|
||||||
_semaphore.Dispose();
|
_semaphore.Dispose();
|
||||||
|
|||||||
@ -30,7 +30,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Build dependency graph
|
// Build dependency graph
|
||||||
var dependencyGraph = BuildDependencyGraph(chunks, getDependencies);
|
var dependencyGraph = BuildDependencyGraph(chunks, getDependencies);
|
||||||
|
|
||||||
// Process in dependency order
|
// Process in dependency order
|
||||||
await ProcessDependencyGraphAsync(
|
await ProcessDependencyGraphAsync(
|
||||||
dependencyGraph,
|
dependencyGraph,
|
||||||
@ -38,7 +38,7 @@ namespace AdvChkSys.Threading
|
|||||||
maxDegreeOfParallelism,
|
maxDegreeOfParallelism,
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a dependency graph for chunks.
|
/// Builds a dependency graph for chunks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -47,13 +47,13 @@ namespace AdvChkSys.Threading
|
|||||||
Func<IChunk, IEnumerable<IChunk>> getDependencies)
|
Func<IChunk, IEnumerable<IChunk>> getDependencies)
|
||||||
{
|
{
|
||||||
var graph = new DependencyGraph();
|
var graph = new DependencyGraph();
|
||||||
|
|
||||||
// Add all chunks to the graph
|
// Add all chunks to the graph
|
||||||
foreach (var chunk in chunks)
|
foreach (var chunk in chunks)
|
||||||
{
|
{
|
||||||
graph.AddNode(chunk);
|
graph.AddNode(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add dependencies
|
// Add dependencies
|
||||||
foreach (var chunk in chunks)
|
foreach (var chunk in chunks)
|
||||||
{
|
{
|
||||||
@ -63,10 +63,10 @@ namespace AdvChkSys.Threading
|
|||||||
graph.AddDependency(chunk, dependency);
|
graph.AddDependency(chunk, dependency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes a dependency graph in parallel.
|
/// Processes a dependency graph in parallel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -79,28 +79,28 @@ namespace AdvChkSys.Threading
|
|||||||
// Set up semaphore for parallelism control
|
// Set up semaphore for parallelism control
|
||||||
var semaphore = new SemaphoreSlim(
|
var semaphore = new SemaphoreSlim(
|
||||||
maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism);
|
maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism);
|
||||||
|
|
||||||
// Track completed chunks
|
// Track completed chunks
|
||||||
var completed = new ConcurrentDictionary<IChunk, bool>();
|
var completed = new ConcurrentDictionary<IChunk, bool>();
|
||||||
|
|
||||||
// Get initial set of chunks with no dependencies
|
// Get initial set of chunks with no dependencies
|
||||||
var readyChunks = new ConcurrentQueue<IChunk>(graph.GetNodesWithNoDependencies());
|
var readyChunks = new ConcurrentQueue<IChunk>(graph.GetNodesWithNoDependencies());
|
||||||
|
|
||||||
// Track active tasks
|
// Track active tasks
|
||||||
var activeTasks = new ConcurrentDictionary<IChunk, Task>();
|
var activeTasks = new ConcurrentDictionary<IChunk, Task>();
|
||||||
|
|
||||||
// Process until all chunks are completed
|
// Process until all chunks are completed
|
||||||
while (!readyChunks.IsEmpty || activeTasks.Count > 0)
|
while (!readyChunks.IsEmpty || activeTasks.Count > 0)
|
||||||
{
|
{
|
||||||
// Check for cancellation
|
// Check for cancellation
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// Start processing ready chunks
|
// Start processing ready chunks
|
||||||
while (readyChunks.TryDequeue(out var chunk))
|
while (readyChunks.TryDequeue(out var chunk))
|
||||||
{
|
{
|
||||||
// Wait for a semaphore slot
|
// Wait for a semaphore slot
|
||||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// Start processing this chunk
|
// Start processing this chunk
|
||||||
var task = ProcessChunkAsync(
|
var task = ProcessChunkAsync(
|
||||||
chunk,
|
chunk,
|
||||||
@ -110,27 +110,27 @@ namespace AdvChkSys.Threading
|
|||||||
readyChunks,
|
readyChunks,
|
||||||
semaphore,
|
semaphore,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
activeTasks[chunk] = task;
|
activeTasks[chunk] = task;
|
||||||
|
|
||||||
// When the task completes, remove it from active tasks
|
// When the task completes, remove it from active tasks
|
||||||
_ = task.ContinueWith(_ =>
|
_ = task.ContinueWith(_ =>
|
||||||
{
|
{
|
||||||
activeTasks.TryRemove(chunk, out _);
|
activeTasks.TryRemove(chunk, out _);
|
||||||
}, TaskContinuationOptions.ExecuteSynchronously);
|
}, TaskContinuationOptions.ExecuteSynchronously);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no ready chunks but active tasks, wait for one to complete
|
// If no ready chunks but active tasks, wait for one to complete
|
||||||
if (readyChunks.IsEmpty && activeTasks.Count > 0)
|
if (readyChunks.IsEmpty && activeTasks.Count > 0)
|
||||||
{
|
{
|
||||||
await Task.WhenAny(activeTasks.Values).ConfigureAwait(false);
|
await Task.WhenAny(activeTasks.Values).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
semaphore.Dispose();
|
semaphore.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes a single chunk and updates the ready queue.
|
/// Processes a single chunk and updates the ready queue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -147,10 +147,10 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Process the chunk
|
// Process the chunk
|
||||||
await processor(chunk).ConfigureAwait(false);
|
await processor(chunk).ConfigureAwait(false);
|
||||||
|
|
||||||
// Mark as completed
|
// Mark as completed
|
||||||
completed[chunk] = true;
|
completed[chunk] = true;
|
||||||
|
|
||||||
// Find dependents that are now ready
|
// Find dependents that are now ready
|
||||||
var dependents = graph.GetDependents(chunk);
|
var dependents = graph.GetDependents(chunk);
|
||||||
foreach (var dependent in dependents)
|
foreach (var dependent in dependents)
|
||||||
@ -170,7 +170,7 @@ namespace AdvChkSys.Threading
|
|||||||
semaphore.Release();
|
semaphore.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a dependency graph for chunks.
|
/// Represents a dependency graph for chunks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -178,10 +178,10 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Map of chunk to its dependencies
|
// Map of chunk to its dependencies
|
||||||
private readonly Dictionary<IChunk, HashSet<IChunk>> _dependencies = new();
|
private readonly Dictionary<IChunk, HashSet<IChunk>> _dependencies = new();
|
||||||
|
|
||||||
// Map of chunk to chunks that depend on it
|
// Map of chunk to chunks that depend on it
|
||||||
private readonly Dictionary<IChunk, HashSet<IChunk>> _dependents = new();
|
private readonly Dictionary<IChunk, HashSet<IChunk>> _dependents = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a node to the graph.
|
/// Adds a node to the graph.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -191,13 +191,13 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
_dependencies[chunk] = new HashSet<IChunk>();
|
_dependencies[chunk] = new HashSet<IChunk>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_dependents.ContainsKey(chunk))
|
if (!_dependents.ContainsKey(chunk))
|
||||||
{
|
{
|
||||||
_dependents[chunk] = new HashSet<IChunk>();
|
_dependents[chunk] = new HashSet<IChunk>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a dependency between two nodes.
|
/// Adds a dependency between two nodes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -206,14 +206,14 @@ namespace AdvChkSys.Threading
|
|||||||
// Add nodes if they don't exist
|
// Add nodes if they don't exist
|
||||||
AddNode(dependent);
|
AddNode(dependent);
|
||||||
AddNode(dependency);
|
AddNode(dependency);
|
||||||
|
|
||||||
// Add dependency
|
// Add dependency
|
||||||
_dependencies[dependent].Add(dependency);
|
_dependencies[dependent].Add(dependency);
|
||||||
|
|
||||||
// Add dependent
|
// Add dependent
|
||||||
_dependents[dependency].Add(dependent);
|
_dependents[dependency].Add(dependent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all nodes with no dependencies.
|
/// Gets all nodes with no dependencies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -221,7 +221,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
return _dependencies.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key);
|
return _dependencies.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all dependencies of a node.
|
/// Gets all dependencies of a node.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -231,10 +231,10 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Enumerable.Empty<IChunk>();
|
return Enumerable.Empty<IChunk>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all dependents of a node.
|
/// Gets all dependents of a node.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -244,7 +244,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
return dependents;
|
return dependents;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Enumerable.Empty<IChunk>();
|
return Enumerable.Empty<IChunk>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,18 +92,18 @@ namespace AdvChkSys.Threading
|
|||||||
using var timeoutCts = new CancellationTokenSource(timeout);
|
using var timeoutCts = new CancellationTokenSource(timeout);
|
||||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||||
timeoutCts.Token, cancellationToken);
|
timeoutCts.Token, cancellationToken);
|
||||||
|
|
||||||
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, linkedCts.Token))
|
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, linkedCts.Token))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (completedTask == task)
|
if (completedTask == task)
|
||||||
{
|
{
|
||||||
return await task.ConfigureAwait(false);
|
return await task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TimeoutException($"The operation timed out after {timeout.TotalMilliseconds}ms");
|
throw new TimeoutException($"The operation timed out after {timeout.TotalMilliseconds}ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a task with a timeout.
|
/// Runs a task with a timeout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -119,19 +119,19 @@ namespace AdvChkSys.Threading
|
|||||||
using var timeoutCts = new CancellationTokenSource(timeout);
|
using var timeoutCts = new CancellationTokenSource(timeout);
|
||||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||||
timeoutCts.Token, cancellationToken);
|
timeoutCts.Token, cancellationToken);
|
||||||
|
|
||||||
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, linkedCts.Token))
|
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, linkedCts.Token))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (completedTask == task)
|
if (completedTask == task)
|
||||||
{
|
{
|
||||||
await task.ConfigureAwait(false);
|
await task.ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TimeoutException($"The operation timed out after {timeout.TotalMilliseconds}ms");
|
throw new TimeoutException($"The operation timed out after {timeout.TotalMilliseconds}ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a task with a fallback value if it times out.
|
/// Runs a task with a fallback value if it times out.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -156,25 +156,25 @@ namespace AdvChkSys.Threading
|
|||||||
return fallbackValue;
|
return fallbackValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs multiple actions in parallel with a limit on the degree of parallelism.
|
/// Runs multiple actions in parallel with a limit on the degree of parallelism.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Task RunBatchParallelAsync(IEnumerable<Action> actions, int? maxDegreeOfParallelism = null,
|
public static Task RunBatchParallelAsync(IEnumerable<Action> actions, int? maxDegreeOfParallelism = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return Task.Run(() =>
|
return Task.Run(() =>
|
||||||
{
|
{
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism,
|
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism,
|
||||||
CancellationToken = cancellationToken
|
CancellationToken = cancellationToken
|
||||||
};
|
};
|
||||||
|
|
||||||
Parallel.ForEach(actions, options, action => action());
|
Parallel.ForEach(actions, options, action => action());
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a batch of functions in parallel and returns the results.
|
/// Runs a batch of functions in parallel and returns the results.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -190,24 +190,24 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var funcs = functions.ToArray();
|
var funcs = functions.ToArray();
|
||||||
var results = new T[funcs.Length];
|
var results = new T[funcs.Length];
|
||||||
|
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism,
|
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism,
|
||||||
CancellationToken = cancellationToken
|
CancellationToken = cancellationToken
|
||||||
};
|
};
|
||||||
|
|
||||||
Parallel.For(0, funcs.Length, options, i =>
|
Parallel.For(0, funcs.Length, options, i =>
|
||||||
{
|
{
|
||||||
results[i] = funcs[i]();
|
results[i] = funcs[i]();
|
||||||
});
|
});
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a batch of async functions in parallel.
|
/// Runs a batch of async functions in parallel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -220,24 +220,24 @@ namespace AdvChkSys.Threading
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var funcs = functions.ToArray();
|
var funcs = functions.ToArray();
|
||||||
|
|
||||||
if (funcs.Length == 0)
|
if (funcs.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Use SemaphoreSlim to limit concurrency
|
// Use SemaphoreSlim to limit concurrency
|
||||||
using var semaphore = new SemaphoreSlim(
|
using var semaphore = new SemaphoreSlim(
|
||||||
maxDegreeOfParallelism ?? MaxDegreeOfParallelism);
|
maxDegreeOfParallelism ?? MaxDegreeOfParallelism);
|
||||||
|
|
||||||
// Create tasks for all functions
|
// Create tasks for all functions
|
||||||
var tasks = new List<Task>(funcs.Length);
|
var tasks = new List<Task>(funcs.Length);
|
||||||
|
|
||||||
foreach (var func in funcs)
|
foreach (var func in funcs)
|
||||||
{
|
{
|
||||||
// Wait for a slot in the semaphore
|
// Wait for a slot in the semaphore
|
||||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// Create a task that releases the semaphore when done
|
// Create a task that releases the semaphore when done
|
||||||
var task = Task.Run(async () =>
|
var task = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -248,14 +248,14 @@ namespace AdvChkSys.Threading
|
|||||||
semaphore.Release();
|
semaphore.Release();
|
||||||
}
|
}
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
tasks.Add(task);
|
tasks.Add(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all tasks to complete
|
// Wait for all tasks to complete
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a batch of async functions in parallel and returns the results.
|
/// Runs a batch of async functions in parallel and returns the results.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -270,27 +270,27 @@ namespace AdvChkSys.Threading
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var funcs = functions.ToArray();
|
var funcs = functions.ToArray();
|
||||||
|
|
||||||
if (funcs.Length == 0)
|
if (funcs.Length == 0)
|
||||||
return Array.Empty<T>();
|
return Array.Empty<T>();
|
||||||
|
|
||||||
// Use SemaphoreSlim to limit concurrency
|
// Use SemaphoreSlim to limit concurrency
|
||||||
using var semaphore = new SemaphoreSlim(
|
using var semaphore = new SemaphoreSlim(
|
||||||
maxDegreeOfParallelism ?? MaxDegreeOfParallelism);
|
maxDegreeOfParallelism ?? MaxDegreeOfParallelism);
|
||||||
|
|
||||||
// Create tasks for all functions
|
// Create tasks for all functions
|
||||||
var tasks = new Task<T>[funcs.Length];
|
var tasks = new Task<T>[funcs.Length];
|
||||||
|
|
||||||
for (int i = 0; i < funcs.Length; i++)
|
for (int i = 0; i < funcs.Length; i++)
|
||||||
{
|
{
|
||||||
var func = funcs[i];
|
var func = funcs[i];
|
||||||
var index = i;
|
var index = i;
|
||||||
|
|
||||||
// Wait for a slot in the semaphore
|
// Wait for a slot in the semaphore
|
||||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// Create a task that releases the semaphore when done
|
// Create a task that releases the semaphore when done
|
||||||
tasks[index] = Task.Run(async () =>
|
tasks[index] = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -302,7 +302,7 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all tasks to complete
|
// Wait for all tasks to complete
|
||||||
return await Task.WhenAll(tasks).ConfigureAwait(false);
|
return await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ namespace AdvChkSys.Threading
|
|||||||
/// Maximum degree of parallelism for chunk operations.
|
/// Maximum degree of parallelism for chunk operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static int MaxDegreeOfParallelism { get; set; } = Environment.ProcessorCount;
|
public static int MaxDegreeOfParallelism { get; set; } = Environment.ProcessorCount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a batch of actions in parallel.
|
/// Runs a batch of actions in parallel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -29,7 +29,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
if (actions == null || actions.Length == 0)
|
if (actions == null || actions.Length == 0)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
return Task.Run(() =>
|
return Task.Run(() =>
|
||||||
{
|
{
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
@ -37,11 +37,11 @@ namespace AdvChkSys.Threading
|
|||||||
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism,
|
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? MaxDegreeOfParallelism,
|
||||||
CancellationToken = cancellationToken
|
CancellationToken = cancellationToken
|
||||||
};
|
};
|
||||||
|
|
||||||
Parallel.ForEach(actions, options, action => action());
|
Parallel.ForEach(actions, options, action => action());
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a cancellation token with a timeout.
|
/// Creates a cancellation token with a timeout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -54,7 +54,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
if (timeout == TimeSpan.MaxValue)
|
if (timeout == TimeSpan.MaxValue)
|
||||||
return cancellationToken;
|
return cancellationToken;
|
||||||
|
|
||||||
var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
source.CancelAfter(timeout);
|
source.CancelAfter(timeout);
|
||||||
return source.Token;
|
return source.Token;
|
||||||
|
|||||||
@ -12,16 +12,16 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Lock objects for each chunk
|
// Lock objects for each chunk
|
||||||
private readonly ConcurrentDictionary<IChunk, ChunkLock> _locks = new();
|
private readonly ConcurrentDictionary<IChunk, ChunkLock> _locks = new();
|
||||||
|
|
||||||
// Timer for cleanup
|
// Timer for cleanup
|
||||||
private readonly Timer _cleanupTimer;
|
private readonly Timer _cleanupTimer;
|
||||||
|
|
||||||
// Last access time for each lock
|
// Last access time for each lock
|
||||||
private readonly ConcurrentDictionary<IChunk, DateTime> _lastAccessTime = new();
|
private readonly ConcurrentDictionary<IChunk, DateTime> _lastAccessTime = new();
|
||||||
|
|
||||||
// Cleanup interval in minutes
|
// Cleanup interval in minutes
|
||||||
private readonly int _cleanupIntervalMinutes;
|
private readonly int _cleanupIntervalMinutes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the ChunkThreadSafetyManager class.
|
/// Initializes a new instance of the ChunkThreadSafetyManager class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -29,11 +29,11 @@ namespace AdvChkSys.Threading
|
|||||||
public ChunkThreadSafetyManager(int cleanupIntervalMinutes = 10)
|
public ChunkThreadSafetyManager(int cleanupIntervalMinutes = 10)
|
||||||
{
|
{
|
||||||
_cleanupIntervalMinutes = cleanupIntervalMinutes;
|
_cleanupIntervalMinutes = cleanupIntervalMinutes;
|
||||||
_cleanupTimer = new Timer(CleanupUnusedLocks, null,
|
_cleanupTimer = new Timer(CleanupUnusedLocks, null,
|
||||||
TimeSpan.FromMinutes(cleanupIntervalMinutes),
|
TimeSpan.FromMinutes(cleanupIntervalMinutes),
|
||||||
TimeSpan.FromMinutes(cleanupIntervalMinutes));
|
TimeSpan.FromMinutes(cleanupIntervalMinutes));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Acquires an exclusive lock on a chunk.
|
/// Acquires an exclusive lock on a chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -43,12 +43,12 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
||||||
_lastAccessTime[chunk] = DateTime.UtcNow;
|
_lastAccessTime[chunk] = DateTime.UtcNow;
|
||||||
|
|
||||||
lockObj.EnterWriteLock();
|
lockObj.EnterWriteLock();
|
||||||
|
|
||||||
return new LockReleaser(lockObj, chunk, this, LockType.Write);
|
return new LockReleaser(lockObj, chunk, this, LockType.Write);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Acquires a read lock on a chunk.
|
/// Acquires a read lock on a chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -58,12 +58,12 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
||||||
_lastAccessTime[chunk] = DateTime.UtcNow;
|
_lastAccessTime[chunk] = DateTime.UtcNow;
|
||||||
|
|
||||||
lockObj.EnterReadLock();
|
lockObj.EnterReadLock();
|
||||||
|
|
||||||
return new LockReleaser(lockObj, chunk, this, LockType.Read);
|
return new LockReleaser(lockObj, chunk, this, LockType.Read);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Acquires a write lock on a chunk.
|
/// Acquires a write lock on a chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -73,12 +73,12 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
||||||
_lastAccessTime[chunk] = DateTime.UtcNow;
|
_lastAccessTime[chunk] = DateTime.UtcNow;
|
||||||
|
|
||||||
lockObj.EnterWriteLock();
|
lockObj.EnterWriteLock();
|
||||||
|
|
||||||
return new LockReleaser(lockObj, chunk, this, LockType.Write);
|
return new LockReleaser(lockObj, chunk, this, LockType.Write);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to acquire a read lock on a chunk.
|
/// Tries to acquire a read lock on a chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -89,18 +89,18 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
||||||
_lastAccessTime[chunk] = DateTime.UtcNow;
|
_lastAccessTime[chunk] = DateTime.UtcNow;
|
||||||
|
|
||||||
if (lockObj.TryEnterReadLock(timeout))
|
if (lockObj.TryEnterReadLock(timeout))
|
||||||
{
|
{
|
||||||
return new LockReleaser(lockObj, chunk, this, LockType.Read);
|
return new LockReleaser(lockObj, chunk, this, LockType.Read);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track contention
|
// Track contention
|
||||||
ChunkThreadingDiagnostics.TrackLockContention(chunk);
|
ChunkThreadingDiagnostics.TrackLockContention(chunk);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to acquire a write lock on a chunk.
|
/// Tries to acquire a write lock on a chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -111,18 +111,18 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
var lockObj = _locks.GetOrAdd(chunk, _ => new ChunkLock());
|
||||||
_lastAccessTime[chunk] = DateTime.UtcNow;
|
_lastAccessTime[chunk] = DateTime.UtcNow;
|
||||||
|
|
||||||
if (lockObj.TryEnterWriteLock(timeout))
|
if (lockObj.TryEnterWriteLock(timeout))
|
||||||
{
|
{
|
||||||
return new LockReleaser(lockObj, chunk, this, LockType.Write);
|
return new LockReleaser(lockObj, chunk, this, LockType.Write);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track contention
|
// Track contention
|
||||||
ChunkThreadingDiagnostics.TrackLockContention(chunk);
|
ChunkThreadingDiagnostics.TrackLockContention(chunk);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cleans up unused locks.
|
/// Cleans up unused locks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -130,10 +130,10 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var threshold = now.AddMinutes(-_cleanupIntervalMinutes);
|
var threshold = now.AddMinutes(-_cleanupIntervalMinutes);
|
||||||
|
|
||||||
foreach (var chunk in _lastAccessTime.Keys)
|
foreach (var chunk in _lastAccessTime.Keys)
|
||||||
{
|
{
|
||||||
if (_lastAccessTime.TryGetValue(chunk, out var lastAccess) &&
|
if (_lastAccessTime.TryGetValue(chunk, out var lastAccess) &&
|
||||||
lastAccess < threshold &&
|
lastAccess < threshold &&
|
||||||
_locks.TryGetValue(chunk, out var lockObj))
|
_locks.TryGetValue(chunk, out var lockObj))
|
||||||
{
|
{
|
||||||
@ -149,23 +149,23 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes resources.
|
/// Disposes resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cleanupTimer.Dispose();
|
_cleanupTimer.Dispose();
|
||||||
|
|
||||||
foreach (var lockObj in _locks.Values)
|
foreach (var lockObj in _locks.Values)
|
||||||
{
|
{
|
||||||
lockObj.Dispose();
|
lockObj.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_locks.Clear();
|
_locks.Clear();
|
||||||
_lastAccessTime.Clear();
|
_lastAccessTime.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Type of lock.
|
/// Type of lock.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -174,28 +174,28 @@ namespace AdvChkSys.Threading
|
|||||||
Read,
|
Read,
|
||||||
Write
|
Write
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wrapper around ReaderWriterLockSlim.
|
/// Wrapper around ReaderWriterLockSlim.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private class ChunkLock : IDisposable
|
private class ChunkLock : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion);
|
private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion);
|
||||||
|
|
||||||
public void EnterReadLock() => _lock.EnterReadLock();
|
public void EnterReadLock() => _lock.EnterReadLock();
|
||||||
public void ExitReadLock() => _lock.ExitReadLock();
|
public void ExitReadLock() => _lock.ExitReadLock();
|
||||||
public void EnterWriteLock() => _lock.EnterWriteLock();
|
public void EnterWriteLock() => _lock.EnterWriteLock();
|
||||||
public void ExitWriteLock() => _lock.ExitWriteLock();
|
public void ExitWriteLock() => _lock.ExitWriteLock();
|
||||||
|
|
||||||
public bool TryEnterReadLock(TimeSpan timeout) => _lock.TryEnterReadLock(timeout);
|
public bool TryEnterReadLock(TimeSpan timeout) => _lock.TryEnterReadLock(timeout);
|
||||||
public bool TryEnterWriteLock(TimeSpan timeout) => _lock.TryEnterWriteLock(timeout);
|
public bool TryEnterWriteLock(TimeSpan timeout) => _lock.TryEnterWriteLock(timeout);
|
||||||
|
|
||||||
public bool IsReadLockHeld => _lock.IsReadLockHeld;
|
public bool IsReadLockHeld => _lock.IsReadLockHeld;
|
||||||
public bool IsWriteLockHeld => _lock.IsWriteLockHeld;
|
public bool IsWriteLockHeld => _lock.IsWriteLockHeld;
|
||||||
|
|
||||||
public void Dispose() => _lock.Dispose();
|
public void Dispose() => _lock.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases a lock when disposed.
|
/// Releases a lock when disposed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -206,7 +206,7 @@ namespace AdvChkSys.Threading
|
|||||||
private readonly ChunkThreadSafetyManager _parent;
|
private readonly ChunkThreadSafetyManager _parent;
|
||||||
private readonly LockType _lockType;
|
private readonly LockType _lockType;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public LockReleaser(ChunkLock lockObj, IChunk chunk, ChunkThreadSafetyManager parent, LockType lockType)
|
public LockReleaser(ChunkLock lockObj, IChunk chunk, ChunkThreadSafetyManager parent, LockType lockType)
|
||||||
{
|
{
|
||||||
_lock = lockObj;
|
_lock = lockObj;
|
||||||
@ -214,7 +214,7 @@ namespace AdvChkSys.Threading
|
|||||||
_parent = parent;
|
_parent = parent;
|
||||||
_lockType = lockType;
|
_lockType = lockType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
@ -227,7 +227,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
_lock.ExitWriteLock();
|
_lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
_parent._lastAccessTime[_chunk] = DateTime.UtcNow;
|
_parent._lastAccessTime[_chunk] = DateTime.UtcNow;
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace AdvChkSys.Threading
|
|||||||
private static int _defaultMaxDegreeOfParallelism = Environment.ProcessorCount;
|
private static int _defaultMaxDegreeOfParallelism = Environment.ProcessorCount;
|
||||||
private static int _chunkOperationTimeout = 30000; // 30 seconds
|
private static int _chunkOperationTimeout = 30000; // 30 seconds
|
||||||
private static int _lockCleanupInterval = 10; // 10 minutes
|
private static int _lockCleanupInterval = 10; // 10 minutes
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the default maximum degree of parallelism for chunk operations.
|
/// Gets or sets the default maximum degree of parallelism for chunk operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -26,7 +26,7 @@ namespace AdvChkSys.Threading
|
|||||||
ChunkTaskScheduler.MaxDegreeOfParallelism = value;
|
ChunkTaskScheduler.MaxDegreeOfParallelism = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the timeout in milliseconds for chunk operations.
|
/// Gets or sets the timeout in milliseconds for chunk operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -40,7 +40,7 @@ namespace AdvChkSys.Threading
|
|||||||
_chunkOperationTimeout = value;
|
_chunkOperationTimeout = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the interval in minutes for cleaning up unused locks.
|
/// Gets or sets the interval in minutes for cleaning up unused locks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -54,17 +54,17 @@ namespace AdvChkSys.Threading
|
|||||||
_lockCleanupInterval = value;
|
_lockCleanupInterval = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a cancellation token with the default timeout.
|
/// Creates a cancellation token with the default timeout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static CancellationToken CreateTimeoutToken(CancellationToken cancellationToken = default)
|
public static CancellationToken CreateTimeoutToken(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return ChunkTaskScheduler.CreateTimeoutToken(
|
return ChunkTaskScheduler.CreateTimeoutToken(
|
||||||
TimeSpan.FromMilliseconds(_chunkOperationTimeout),
|
TimeSpan.FromMilliseconds(_chunkOperationTimeout),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures the system for high throughput (more parallelism, longer timeouts).
|
/// Configures the system for high throughput (more parallelism, longer timeouts).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -74,7 +74,7 @@ namespace AdvChkSys.Threading
|
|||||||
ChunkOperationTimeoutMs = 60000; // 1 minute
|
ChunkOperationTimeoutMs = 60000; // 1 minute
|
||||||
LockCleanupIntervalMinutes = 30;
|
LockCleanupIntervalMinutes = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures the system for low latency (less parallelism, shorter timeouts).
|
/// Configures the system for low latency (less parallelism, shorter timeouts).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -84,7 +84,7 @@ namespace AdvChkSys.Threading
|
|||||||
ChunkOperationTimeoutMs = 15000; // 15 seconds
|
ChunkOperationTimeoutMs = 15000; // 15 seconds
|
||||||
LockCleanupIntervalMinutes = 5;
|
LockCleanupIntervalMinutes = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures the system for memory efficiency (less parallelism, more aggressive cleanup).
|
/// Configures the system for memory efficiency (less parallelism, more aggressive cleanup).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -16,19 +16,19 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Track operation durations
|
// Track operation durations
|
||||||
private static readonly ConcurrentDictionary<string, List<long>> _operationDurations = new();
|
private static readonly ConcurrentDictionary<string, List<long>> _operationDurations = new();
|
||||||
|
|
||||||
// Track lock contention
|
// Track lock contention
|
||||||
private static readonly ConcurrentDictionary<IChunk, int> _lockContentionCount = new();
|
private static readonly ConcurrentDictionary<IChunk, int> _lockContentionCount = new();
|
||||||
|
|
||||||
// Track active operations
|
// Track active operations
|
||||||
private static readonly ConcurrentDictionary<Guid, (string Operation, DateTime StartTime, IChunk Chunk)> _activeOperations = new();
|
private static readonly ConcurrentDictionary<Guid, (string Operation, DateTime StartTime, IChunk Chunk)> _activeOperations = new();
|
||||||
|
|
||||||
// Lock for thread safety
|
// Lock for thread safety
|
||||||
private static readonly object _lock = new();
|
private static readonly object _lock = new();
|
||||||
|
|
||||||
// Stopwatch for timing
|
// Stopwatch for timing
|
||||||
private static readonly Stopwatch _stopwatch = Stopwatch.StartNew();
|
private static readonly Stopwatch _stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracks the duration of an operation.
|
/// Tracks the duration of an operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -38,7 +38,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var operationId = Guid.NewGuid();
|
var operationId = Guid.NewGuid();
|
||||||
var startTime = _stopwatch.ElapsedMilliseconds;
|
var startTime = _stopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
action();
|
action();
|
||||||
@ -46,7 +46,7 @@ namespace AdvChkSys.Threading
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
var duration = _stopwatch.ElapsedMilliseconds - startTime;
|
var duration = _stopwatch.ElapsedMilliseconds - startTime;
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (!_operationDurations.TryGetValue(operationName, out var durations))
|
if (!_operationDurations.TryGetValue(operationName, out var durations))
|
||||||
@ -54,9 +54,9 @@ namespace AdvChkSys.Threading
|
|||||||
durations = new List<long>();
|
durations = new List<long>();
|
||||||
_operationDurations[operationName] = durations;
|
_operationDurations[operationName] = durations;
|
||||||
}
|
}
|
||||||
|
|
||||||
durations.Add(duration);
|
durations.Add(duration);
|
||||||
|
|
||||||
// Keep only the last 1000 durations
|
// Keep only the last 1000 durations
|
||||||
if (durations.Count > 1000)
|
if (durations.Count > 1000)
|
||||||
{
|
{
|
||||||
@ -65,7 +65,7 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracks the start of an operation on a chunk.
|
/// Tracks the start of an operation on a chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -78,7 +78,7 @@ namespace AdvChkSys.Threading
|
|||||||
_activeOperations[operationId] = (operationName, DateTime.UtcNow, chunk);
|
_activeOperations[operationId] = (operationName, DateTime.UtcNow, chunk);
|
||||||
return operationId;
|
return operationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracks the end of an operation.
|
/// Tracks the end of an operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -88,7 +88,7 @@ namespace AdvChkSys.Threading
|
|||||||
if (_activeOperations.TryRemove(operationId, out var info))
|
if (_activeOperations.TryRemove(operationId, out var info))
|
||||||
{
|
{
|
||||||
var duration = (DateTime.UtcNow - info.StartTime).TotalMilliseconds;
|
var duration = (DateTime.UtcNow - info.StartTime).TotalMilliseconds;
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (!_operationDurations.TryGetValue(info.Operation, out var durations))
|
if (!_operationDurations.TryGetValue(info.Operation, out var durations))
|
||||||
@ -96,9 +96,9 @@ namespace AdvChkSys.Threading
|
|||||||
durations = new List<long>();
|
durations = new List<long>();
|
||||||
_operationDurations[info.Operation] = durations;
|
_operationDurations[info.Operation] = durations;
|
||||||
}
|
}
|
||||||
|
|
||||||
durations.Add((long)duration);
|
durations.Add((long)duration);
|
||||||
|
|
||||||
// Keep only the last 1000 durations
|
// Keep only the last 1000 durations
|
||||||
if (durations.Count > 1000)
|
if (durations.Count > 1000)
|
||||||
{
|
{
|
||||||
@ -107,7 +107,7 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracks lock contention on a chunk.
|
/// Tracks lock contention on a chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -116,14 +116,14 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
_lockContentionCount.AddOrUpdate(chunk, 1, (_, count) => count + 1);
|
_lockContentionCount.AddOrUpdate(chunk, 1, (_, count) => count + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets statistics about operation durations.
|
/// Gets statistics about operation durations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Dictionary<string, (long Min, long Max, double Average, int Count)> GetOperationStatistics()
|
public static Dictionary<string, (long Min, long Max, double Average, int Count)> GetOperationStatistics()
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, (long Min, long Max, double Average, int Count)>();
|
var result = new Dictionary<string, (long Min, long Max, double Average, int Count)>();
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var kvp in _operationDurations)
|
foreach (var kvp in _operationDurations)
|
||||||
@ -140,10 +140,10 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the chunks with the most lock contention.
|
/// Gets the chunks with the most lock contention.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -156,7 +156,7 @@ namespace AdvChkSys.Threading
|
|||||||
.Select(kvp => (kvp.Key, kvp.Value))
|
.Select(kvp => (kvp.Key, kvp.Value))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets information about currently active operations.
|
/// Gets information about currently active operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -172,7 +172,7 @@ namespace AdvChkSys.Threading
|
|||||||
.OrderByDescending(x => x.Item2)
|
.OrderByDescending(x => x.Item2)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total number of tracked operations.
|
/// Gets the total number of tracked operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -183,7 +183,7 @@ namespace AdvChkSys.Threading
|
|||||||
return _operationDurations.Values.Sum(list => list.Count);
|
return _operationDurations.Values.Sum(list => list.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of active operations.
|
/// Gets the number of active operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -191,7 +191,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
return _activeOperations.Count;
|
return _activeOperations.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total number of lock contentions.
|
/// Gets the total number of lock contentions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -199,7 +199,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
return _lockContentionCount.Values.Sum();
|
return _lockContentionCount.Values.Sum();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears all diagnostic data.
|
/// Clears all diagnostic data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -212,18 +212,18 @@ namespace AdvChkSys.Threading
|
|||||||
// Don't clear active operations as they're still in progress
|
// Don't clear active operations as they're still in progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a comprehensive diagnostic report.
|
/// Gets a comprehensive diagnostic report.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string GenerateDiagnosticReport()
|
public static string GenerateDiagnosticReport()
|
||||||
{
|
{
|
||||||
var report = new System.Text.StringBuilder();
|
var report = new System.Text.StringBuilder();
|
||||||
|
|
||||||
report.AppendLine("=== Chunk Threading Diagnostic Report ===");
|
report.AppendLine("=== Chunk Threading Diagnostic Report ===");
|
||||||
report.AppendLine($"Generated: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
|
report.AppendLine($"Generated: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
|
||||||
report.AppendLine();
|
report.AppendLine();
|
||||||
|
|
||||||
// Operation statistics
|
// Operation statistics
|
||||||
report.AppendLine("== Operation Statistics ==");
|
report.AppendLine("== Operation Statistics ==");
|
||||||
var stats = GetOperationStatistics();
|
var stats = GetOperationStatistics();
|
||||||
@ -236,7 +236,7 @@ namespace AdvChkSys.Threading
|
|||||||
report.AppendLine($" Avg: {kvp.Value.Average:F2}ms");
|
report.AppendLine($" Avg: {kvp.Value.Average:F2}ms");
|
||||||
}
|
}
|
||||||
report.AppendLine();
|
report.AppendLine();
|
||||||
|
|
||||||
// Lock contention
|
// Lock contention
|
||||||
report.AppendLine("== Lock Contention ==");
|
report.AppendLine("== Lock Contention ==");
|
||||||
var contentions = GetTopContentionChunks(10);
|
var contentions = GetTopContentionChunks(10);
|
||||||
@ -246,7 +246,7 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
report.AppendLine($"Total contentions: {GetTotalLockContentionCount()}");
|
report.AppendLine($"Total contentions: {GetTotalLockContentionCount()}");
|
||||||
report.AppendLine();
|
report.AppendLine();
|
||||||
|
|
||||||
// Active operations
|
// Active operations
|
||||||
report.AppendLine("== Active Operations ==");
|
report.AppendLine("== Active Operations ==");
|
||||||
var activeOps = GetActiveOperations();
|
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($"{operation} on Chunk ({chunk.X}, {chunk.Y}): {duration.TotalMilliseconds:F2}ms");
|
||||||
}
|
}
|
||||||
report.AppendLine($"Total active operations: {GetActiveOperationCount()}");
|
report.AppendLine($"Total active operations: {GetActiveOperationCount()}");
|
||||||
|
|
||||||
return report.ToString();
|
return report.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logs a diagnostic event.
|
/// Logs a diagnostic event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -76,7 +76,7 @@ namespace AdvChkSys.Threading
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var chunks = new List<Chunk.Chunk2D<T>>();
|
var chunks = new List<Chunk.Chunk2D<T>>();
|
||||||
|
|
||||||
// Collect chunks in the region
|
// Collect chunks in the region
|
||||||
for (int x = minX; x <= maxX; x++)
|
for (int x = minX; x <= maxX; x++)
|
||||||
{
|
{
|
||||||
@ -89,7 +89,7 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process in parallel
|
// Process in parallel
|
||||||
await ChunkTaskScheduler.RunBatchParallelAsync(
|
await ChunkTaskScheduler.RunBatchParallelAsync(
|
||||||
chunks.Select(c => new Action(() => action(c))).ToArray(),
|
chunks.Select(c => new Action(() => action(c))).ToArray(),
|
||||||
@ -119,7 +119,7 @@ namespace AdvChkSys.Threading
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var chunks = new List<Chunk.Chunk3D<T>>();
|
var chunks = new List<Chunk.Chunk3D<T>>();
|
||||||
|
|
||||||
// Collect chunks in the region
|
// Collect chunks in the region
|
||||||
for (int x = minX; x <= maxX; x++)
|
for (int x = minX; x <= maxX; x++)
|
||||||
{
|
{
|
||||||
@ -135,7 +135,7 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process in parallel
|
// Process in parallel
|
||||||
await ChunkTaskScheduler.RunBatchParallelAsync(
|
await ChunkTaskScheduler.RunBatchParallelAsync(
|
||||||
chunks.Select(c => new Action(() => action(c))).ToArray(),
|
chunks.Select(c => new Action(() => action(c))).ToArray(),
|
||||||
@ -165,7 +165,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Create a loading priority system if not already integrated
|
// Create a loading priority system if not already integrated
|
||||||
var loadingPriority = new ChunkLoadingPriority();
|
var loadingPriority = new ChunkLoadingPriority();
|
||||||
|
|
||||||
// Enqueue all chunks in the region
|
// Enqueue all chunks in the region
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
for (int x = minX; x <= maxX; x++)
|
for (int x = minX; x <= maxX; x++)
|
||||||
@ -174,17 +174,17 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Skip if already loaded
|
// Skip if already loaded
|
||||||
if (manager.IsChunkLoaded(x, y))
|
if (manager.IsChunkLoaded(x, y))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Enqueue the request
|
// Enqueue the request
|
||||||
var request = loadingPriority.EnqueueRequest(x, y, width, height, priority);
|
var request = loadingPriority.EnqueueRequest(x, y, width, height, priority);
|
||||||
|
|
||||||
// Create a task that completes when the chunk is loaded
|
// Create a task that completes when the chunk is loaded
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
void Handler(object? sender, ChunkLoadingPriority.ChunkLoadRequest completedRequest)
|
void Handler(object? sender, ChunkLoadingPriority.ChunkLoadRequest completedRequest)
|
||||||
{
|
{
|
||||||
if (completedRequest.RequestId == request.RequestId)
|
if (completedRequest.RequestId == request.RequestId)
|
||||||
@ -193,24 +193,24 @@ namespace AdvChkSys.Threading
|
|||||||
tcs.TrySetResult(true);
|
tcs.TrySetResult(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingPriority.RequestCompleted += Handler;
|
loadingPriority.RequestCompleted += Handler;
|
||||||
|
|
||||||
// Add timeout to prevent indefinite waiting
|
// Add timeout to prevent indefinite waiting
|
||||||
_ = Task.Delay(30000, cancellationToken).ContinueWith(t =>
|
_ = Task.Delay(30000, cancellationToken).ContinueWith(t =>
|
||||||
{
|
{
|
||||||
loadingPriority.RequestCompleted -= Handler;
|
loadingPriority.RequestCompleted -= Handler;
|
||||||
if (!t.IsCanceled)
|
if (!t.IsCanceled)
|
||||||
tcs.TrySetResult(false);
|
tcs.TrySetResult(false);
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
tasks.Add(tcs.Task);
|
tasks.Add(tcs.Task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all chunks to be loaded
|
// Wait for all chunks to be loaded
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
await loadingPriority.ShutdownAsync().ConfigureAwait(false);
|
await loadingPriority.ShutdownAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -240,7 +240,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Create a loading priority system if not already integrated
|
// Create a loading priority system if not already integrated
|
||||||
var loadingPriority = new ChunkLoadingPriority();
|
var loadingPriority = new ChunkLoadingPriority();
|
||||||
|
|
||||||
// Enqueue all chunks in the region
|
// Enqueue all chunks in the region
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
for (int x = minX; x <= maxX; x++)
|
for (int x = minX; x <= maxX; x++)
|
||||||
@ -251,17 +251,17 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Skip if already loaded
|
// Skip if already loaded
|
||||||
if (manager.IsChunkLoaded(x, y, z))
|
if (manager.IsChunkLoaded(x, y, z))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Enqueue the request
|
// Enqueue the request
|
||||||
var request = loadingPriority.EnqueueRequest(x, y, z, width, height, depth, priority);
|
var request = loadingPriority.EnqueueRequest(x, y, z, width, height, depth, priority);
|
||||||
|
|
||||||
// Create a task that completes when the chunk is loaded
|
// Create a task that completes when the chunk is loaded
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
void Handler(object? sender, ChunkLoadingPriority.ChunkLoadRequest completedRequest)
|
void Handler(object? sender, ChunkLoadingPriority.ChunkLoadRequest completedRequest)
|
||||||
{
|
{
|
||||||
if (completedRequest.RequestId == request.RequestId)
|
if (completedRequest.RequestId == request.RequestId)
|
||||||
@ -270,25 +270,25 @@ namespace AdvChkSys.Threading
|
|||||||
tcs.TrySetResult(true);
|
tcs.TrySetResult(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingPriority.RequestCompleted += Handler;
|
loadingPriority.RequestCompleted += Handler;
|
||||||
|
|
||||||
// Add timeout to prevent indefinite waiting
|
// Add timeout to prevent indefinite waiting
|
||||||
_ = Task.Delay(30000, cancellationToken).ContinueWith(t =>
|
_ = Task.Delay(30000, cancellationToken).ContinueWith(t =>
|
||||||
{
|
{
|
||||||
loadingPriority.RequestCompleted -= Handler;
|
loadingPriority.RequestCompleted -= Handler;
|
||||||
if (!t.IsCanceled)
|
if (!t.IsCanceled)
|
||||||
tcs.TrySetResult(false);
|
tcs.TrySetResult(false);
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
tasks.Add(tcs.Task);
|
tasks.Add(tcs.Task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all chunks to be loaded
|
// Wait for all chunks to be loaded
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
await loadingPriority.ShutdownAsync().ConfigureAwait(false);
|
await loadingPriority.ShutdownAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -312,21 +312,21 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Get all loaded chunks
|
// Get all loaded chunks
|
||||||
var chunks = manager.GetAllChunks().ToArray();
|
var chunks = manager.GetAllChunks().ToArray();
|
||||||
|
|
||||||
// Filter chunks outside the region
|
// 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();
|
c.X < minX || c.X > maxX || c.Y < minY || c.Y > maxY).ToArray();
|
||||||
|
|
||||||
// Unload in parallel
|
// Unload in parallel
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? Environment.ProcessorCount,
|
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? Environment.ProcessorCount,
|
||||||
CancellationToken = cancellationToken
|
CancellationToken = cancellationToken
|
||||||
};
|
};
|
||||||
|
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
Parallel.ForEach(chunksToUnload, options, chunk =>
|
Parallel.ForEach(chunksToUnload, options, chunk =>
|
||||||
{
|
{
|
||||||
manager.UnloadChunk(chunk.X, chunk.Y);
|
manager.UnloadChunk(chunk.X, chunk.Y);
|
||||||
});
|
});
|
||||||
@ -354,23 +354,23 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Get all loaded chunks
|
// Get all loaded chunks
|
||||||
var chunks = manager.GetAllChunks().ToArray();
|
var chunks = manager.GetAllChunks().ToArray();
|
||||||
|
|
||||||
// Filter chunks outside the region
|
// Filter chunks outside the region
|
||||||
var chunksToUnload = chunks.Where(c =>
|
var chunksToUnload = chunks.Where(c =>
|
||||||
c.X < minX || c.X > maxX ||
|
c.X < minX || c.X > maxX ||
|
||||||
c.Y < minY || c.Y > maxY ||
|
c.Y < minY || c.Y > maxY ||
|
||||||
c.Z < minZ || c.Z > maxZ).ToArray();
|
c.Z < minZ || c.Z > maxZ).ToArray();
|
||||||
|
|
||||||
// Unload in parallel
|
// Unload in parallel
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? Environment.ProcessorCount,
|
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? Environment.ProcessorCount,
|
||||||
CancellationToken = cancellationToken
|
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);
|
manager.UnloadChunk(chunk.X, chunk.Y, chunk.Z);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -35,21 +35,21 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Generate spiral coordinates
|
// Generate spiral coordinates
|
||||||
var coordinates = GenerateSpiralCoordinates(centerX, centerY, radius).ToArray();
|
var coordinates = GenerateSpiralCoordinates(centerX, centerY, radius).ToArray();
|
||||||
|
|
||||||
// Process in batches to maintain the spiral ordering while still using parallelism
|
// Process in batches to maintain the spiral ordering while still using parallelism
|
||||||
int batchSize = Math.Max(1, (int)Math.Sqrt(coordinates.Length));
|
int batchSize = Math.Max(1, (int)Math.Sqrt(coordinates.Length));
|
||||||
int batchCount = (coordinates.Length + batchSize - 1) / batchSize;
|
int batchCount = (coordinates.Length + batchSize - 1) / batchSize;
|
||||||
|
|
||||||
for (int i = 0; i < batchCount; i++)
|
for (int i = 0; i < batchCount; i++)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var batchCoords = coordinates
|
var batchCoords = coordinates
|
||||||
.Skip(i * batchSize)
|
.Skip(i * batchSize)
|
||||||
.Take(batchSize)
|
.Take(batchSize)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
foreach (var (x, y) in batchCoords)
|
foreach (var (x, y) in batchCoords)
|
||||||
{
|
{
|
||||||
@ -59,14 +59,14 @@ namespace AdvChkSys.Threading
|
|||||||
tasks.Add(processor(chunk));
|
tasks.Add(processor(chunk));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tasks.Count > 0)
|
if (tasks.Count > 0)
|
||||||
{
|
{
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates coordinates in a spiral pattern from the center outward.
|
/// Generates coordinates in a spiral pattern from the center outward.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -74,7 +74,7 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
// Start with the center
|
// Start with the center
|
||||||
yield return (centerX, centerY);
|
yield return (centerX, centerY);
|
||||||
|
|
||||||
// Spiral outward
|
// Spiral outward
|
||||||
for (int layer = 1; layer <= radius; layer++)
|
for (int layer = 1; layer <= radius; layer++)
|
||||||
{
|
{
|
||||||
@ -83,19 +83,19 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
yield return (x, centerY - layer);
|
yield return (x, centerY - layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right edge (moving down)
|
// Right edge (moving down)
|
||||||
for (int y = centerY - layer + 1; y <= centerY + layer; y++)
|
for (int y = centerY - layer + 1; y <= centerY + layer; y++)
|
||||||
{
|
{
|
||||||
yield return (centerX + layer, y);
|
yield return (centerX + layer, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom edge (moving left)
|
// Bottom edge (moving left)
|
||||||
for (int x = centerX + layer - 1; x >= centerX - layer; x--)
|
for (int x = centerX + layer - 1; x >= centerX - layer; x--)
|
||||||
{
|
{
|
||||||
yield return (x, centerY + layer);
|
yield return (x, centerY + layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Left edge (moving up)
|
// Left edge (moving up)
|
||||||
for (int y = centerY + layer - 1; y >= centerY - layer; y--)
|
for (int y = centerY + layer - 1; y >= centerY - layer; y--)
|
||||||
{
|
{
|
||||||
@ -103,7 +103,7 @@ namespace AdvChkSys.Threading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes chunks with dependency awareness.
|
/// Processes chunks with dependency awareness.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -121,7 +121,7 @@ namespace AdvChkSys.Threading
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var chunks = manager.GetAllChunks().ToArray();
|
var chunks = manager.GetAllChunks().ToArray();
|
||||||
|
|
||||||
await ChunkParallelProcessor.ProcessChunksWithDependenciesAsync(
|
await ChunkParallelProcessor.ProcessChunksWithDependenciesAsync(
|
||||||
chunks,
|
chunks,
|
||||||
chunk => processor((Chunk2D<T>)chunk),
|
chunk => processor((Chunk2D<T>)chunk),
|
||||||
@ -129,7 +129,7 @@ namespace AdvChkSys.Threading
|
|||||||
maxDegreeOfParallelism,
|
maxDegreeOfParallelism,
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes chunks with dependency awareness.
|
/// Processes chunks with dependency awareness.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -147,7 +147,7 @@ namespace AdvChkSys.Threading
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var chunks = manager.GetAllChunks().ToArray();
|
var chunks = manager.GetAllChunks().ToArray();
|
||||||
|
|
||||||
await ChunkParallelProcessor.ProcessChunksWithDependenciesAsync(
|
await ChunkParallelProcessor.ProcessChunksWithDependenciesAsync(
|
||||||
chunks,
|
chunks,
|
||||||
chunk => processor((Chunk3D<T>)chunk),
|
chunk => processor((Chunk3D<T>)chunk),
|
||||||
@ -155,7 +155,7 @@ namespace AdvChkSys.Threading
|
|||||||
maxDegreeOfParallelism,
|
maxDegreeOfParallelism,
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes chunks with thread safety.
|
/// Processes chunks with thread safety.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -172,22 +172,22 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var chunks = manager.GetAllChunks().ToArray();
|
var chunks = manager.GetAllChunks().ToArray();
|
||||||
var threadingManager = ChunkThreadingManager.Instance;
|
var threadingManager = ChunkThreadingManager.Instance;
|
||||||
|
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism,
|
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism,
|
||||||
CancellationToken = cancellationToken
|
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);
|
await processor(chunk).ConfigureAwait(false);
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
}), cancellationToken).ConfigureAwait(false);
|
}), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes chunks with thread safety.
|
/// Processes chunks with thread safety.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -204,16 +204,16 @@ namespace AdvChkSys.Threading
|
|||||||
{
|
{
|
||||||
var chunks = manager.GetAllChunks().ToArray();
|
var chunks = manager.GetAllChunks().ToArray();
|
||||||
var threadingManager = ChunkThreadingManager.Instance;
|
var threadingManager = ChunkThreadingManager.Instance;
|
||||||
|
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism,
|
MaxDegreeOfParallelism = maxDegreeOfParallelism ?? ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism,
|
||||||
CancellationToken = cancellationToken
|
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);
|
await processor(chunk).ConfigureAwait(false);
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|||||||
@ -16,7 +16,7 @@ namespace AdvChkSys.Threading
|
|||||||
private static bool _currentThreadIsProcessingItems;
|
private static bool _currentThreadIsProcessingItems;
|
||||||
|
|
||||||
// The list of tasks to be executed
|
// The list of tasks to be executed
|
||||||
private readonly LinkedList<Task> _tasks = new LinkedList<Task>();
|
private readonly LinkedList<Task> _tasks = new();
|
||||||
|
|
||||||
// The maximum concurrency level allowed by this scheduler.
|
// The maximum concurrency level allowed by this scheduler.
|
||||||
private int _maximumConcurrencyLevel;
|
private int _maximumConcurrencyLevel;
|
||||||
|
|||||||
@ -115,8 +115,8 @@ namespace AdvChkSys.Util
|
|||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var node in _lruList)
|
foreach (var (key, value) in _lruList)
|
||||||
yield return node.value;
|
yield return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user