XML comments added Builds cleanly.

oddly a ignore in the gitignore went missing.
restored.
This commit is contained in:
Stan44 2025-05-11 21:50:25 -05:00
parent 8a6f9144c4
commit 0ae815cf2c
6 changed files with 266 additions and 182 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
# Ignore Build
.vscode/
/docfx_project
/src/AdvChkSys/bin
/src/AdvChkSys/obj

View File

@ -14,21 +14,36 @@ namespace AdvChkSys.Dependencies
/// </summary>
public enum DependencyType
{
/// <summary>
/// Indicates a neighboring chunk relationship.
/// </summary>
Neighbor,
/// <summary>
/// Indicates a reference relationship between chunks.
/// </summary>
Reference,
/// <summary>
/// Indicates an update relationship between chunks.
/// </summary>
Update,
/// <summary>
/// Indicates a custom dependency relationship.
/// </summary>
Custom
}
// Dictionary to track dependencies: source chunk -> (target chunk, dependency type)
private readonly Dictionary<IChunk, HashSet<(IChunk Target, DependencyType Type)>> _dependencies = new();
// Dictionary to track dependents: target chunk -> (source chunk, dependency type)
private readonly Dictionary<IChunk, HashSet<(IChunk Source, DependencyType Type)>> _dependents = new();
// Lock object for thread safety
private readonly object _lock = new();
/// <summary>
/// Registers a dependency between two chunks.
/// </summary>
@ -36,7 +51,7 @@ namespace AdvChkSys.Dependencies
{
if (source == null || target == null)
return;
lock (_lock)
{
// Add to dependencies
@ -46,7 +61,7 @@ namespace AdvChkSys.Dependencies
_dependencies[source] = targetSet;
}
targetSet.Add((target, type));
// Add to dependents
if (!_dependents.TryGetValue(target, out var sourceSet))
{
@ -56,7 +71,7 @@ namespace AdvChkSys.Dependencies
sourceSet.Add((source, type));
}
}
/// <summary>
/// Removes a dependency between two chunks.
/// </summary>
@ -64,7 +79,7 @@ namespace AdvChkSys.Dependencies
{
if (source == null || target == null)
return;
lock (_lock)
{
// Remove from dependencies
@ -76,7 +91,7 @@ namespace AdvChkSys.Dependencies
_dependencies.Remove(source);
}
}
// Remove from dependents
if (_dependents.TryGetValue(target, out var sourceSet))
{
@ -88,7 +103,7 @@ namespace AdvChkSys.Dependencies
}
}
}
/// <summary>
/// Gets all chunks that depend on the specified chunk.
/// </summary>
@ -96,14 +111,14 @@ namespace AdvChkSys.Dependencies
{
if (chunk == null)
return new List<IChunk>();
lock (_lock)
{
if (!_dependents.TryGetValue(chunk, out var sourceSet))
{
return new List<IChunk>();
}
var result = new List<IChunk>(sourceSet.Count);
foreach (var (source, _) in sourceSet)
{
@ -112,7 +127,7 @@ namespace AdvChkSys.Dependencies
return result;
}
}
/// <summary>
/// Gets all chunks that the specified chunk depends on.
/// </summary>
@ -120,14 +135,14 @@ namespace AdvChkSys.Dependencies
{
if (chunk == null)
return new List<IChunk>();
lock (_lock)
{
if (!_dependencies.TryGetValue(chunk, out var targetSet))
{
return new List<IChunk>();
}
var result = new List<IChunk>(targetSet.Count);
foreach (var (target, _) in targetSet)
{
@ -136,7 +151,7 @@ namespace AdvChkSys.Dependencies
return result;
}
}
/// <summary>
/// Gets all chunks that depend on the specified chunk with their dependency types.
/// </summary>
@ -144,14 +159,14 @@ namespace AdvChkSys.Dependencies
{
if (chunk == null)
return new List<(IChunk, DependencyType)>();
lock (_lock)
{
if (!_dependents.TryGetValue(chunk, out var sourceDependents))
{
return new List<(IChunk, DependencyType)>();
}
var result = new List<(IChunk, DependencyType)>(sourceDependents.Count);
foreach (var (source, type) in sourceDependents)
{
@ -160,7 +175,7 @@ namespace AdvChkSys.Dependencies
return result;
}
}
/// <summary>
/// Gets all chunks that the specified chunk depends on with their dependency types.
/// </summary>
@ -168,14 +183,14 @@ namespace AdvChkSys.Dependencies
{
if (chunk == null)
return new List<(IChunk, DependencyType)>();
lock (_lock)
{
if (!_dependencies.TryGetValue(chunk, out var targetSet))
{
return new List<(IChunk, DependencyType)>();
}
var result = new List<(IChunk, DependencyType)>(targetSet.Count);
foreach (var (target, type) in targetSet)
{
@ -184,7 +199,7 @@ namespace AdvChkSys.Dependencies
return result;
}
}
/// <summary>
/// Checks if a dependency exists between source and target chunks.
/// </summary>
@ -192,14 +207,14 @@ namespace AdvChkSys.Dependencies
{
if (source == null || target == null)
return false;
lock (_lock)
{
if (!_dependencies.TryGetValue(source, out var targetSet))
{
return false;
}
foreach (var (t, _) in targetSet)
{
if (t.Equals(target))
@ -207,11 +222,11 @@ namespace AdvChkSys.Dependencies
return true;
}
}
return false;
}
}
/// <summary>
/// Gets the dependency type between source and target chunks, or null if no dependency exists.
/// </summary>
@ -219,14 +234,14 @@ namespace AdvChkSys.Dependencies
{
if (source == null || target == null)
return null;
lock (_lock)
{
if (!_dependencies.TryGetValue(source, out var targetSet))
{
return null;
}
foreach (var (t, type) in targetSet)
{
if (t.Equals(target))
@ -234,11 +249,11 @@ namespace AdvChkSys.Dependencies
return type;
}
}
return null;
}
}
/// <summary>
/// Clears all dependencies for a specific chunk.
/// </summary>
@ -246,7 +261,7 @@ namespace AdvChkSys.Dependencies
{
if (chunk == null)
return;
lock (_lock)
{
// Remove all dependencies where this chunk is the source
@ -265,7 +280,7 @@ namespace AdvChkSys.Dependencies
}
_dependencies.Remove(chunk);
}
// Remove all dependencies where this chunk is the target
if (_dependents.TryGetValue(chunk, out var sourceSets))
{
@ -284,7 +299,7 @@ namespace AdvChkSys.Dependencies
}
}
}
/// <summary>
/// Gets all chunks that have any dependencies.
/// </summary>
@ -295,7 +310,7 @@ namespace AdvChkSys.Dependencies
return new HashSet<IChunk>(_dependencies.Keys);
}
}
/// <summary>
/// Gets all chunks that have any dependents.
/// </summary>

View File

@ -18,13 +18,32 @@ namespace AdvChkSys.Loading
/// </summary>
public enum Priority
{
/// <summary>
/// Immediate priority - process as soon as possible
/// </summary>
Immediate,
/// <summary>
/// High priority - process after immediate requests
/// </summary>
High,
/// <summary>
/// Normal priority - standard processing order
/// </summary>
Normal,
/// <summary>
/// Low priority - process after normal requests
/// </summary>
Low,
/// <summary>
/// Background priority - process when system is idle
/// </summary>
Background
}
/// <summary>
/// Represents a chunk loading request with priority.
/// </summary>
@ -34,47 +53,47 @@ namespace AdvChkSys.Loading
/// The X coordinate of the chunk.
/// </summary>
public int X { get; }
/// <summary>
/// The Y coordinate of the chunk.
/// </summary>
public int Y { get; }
/// <summary>
/// The Z coordinate of the chunk (optional, for 3D chunks).
/// </summary>
public int Z { get; }
/// <summary>
/// The priority of this loading request.
/// </summary>
public Priority LoadPriority { get; }
/// <summary>
/// The width of the chunk.
/// </summary>
public int Width { get; }
/// <summary>
/// The height of the chunk.
/// </summary>
public int Height { get; }
/// <summary>
/// The depth of the chunk (for 3D chunks).
/// </summary>
public int Depth { get; }
/// <summary>
/// Timestamp when the request was created.
/// </summary>
public DateTime Timestamp { get; }
/// <summary>
/// Unique identifier for this request.
/// </summary>
public Guid RequestId { get; }
/// <summary>
/// Creates a new 2D chunk loading request.
/// </summary>
@ -90,7 +109,7 @@ namespace AdvChkSys.Loading
Timestamp = DateTime.UtcNow;
RequestId = Guid.NewGuid();
}
/// <summary>
/// Creates a new 3D chunk loading request.
/// </summary>
@ -107,39 +126,39 @@ namespace AdvChkSys.Loading
RequestId = Guid.NewGuid();
}
}
// Priority queues for each priority level
private readonly Dictionary<Priority, Queue<ChunkLoadRequest>> _requestQueues = new();
// Lookup for fast cancellation and status checks
private readonly Dictionary<Guid, ChunkLoadRequest> _requestLookup = new();
// Active tasks to prevent duplicate loading
private readonly Dictionary<(int X, int Y, int Z), Task> _activeTasks = new();
// Synchronization
private readonly SemaphoreSlim _queueSemaphore = new(1, 1);
private readonly SemaphoreSlim _processSemaphore;
private readonly CancellationTokenSource _cancellationSource = new();
// Processing state
private bool _isProcessing;
private Task? _processingTask;
// Configuration
private readonly int _maxConcurrentLoads;
private readonly TimeSpan _requestTimeout;
/// <summary>
/// Event raised when a chunk load request is completed.
/// </summary>
public event EventHandler<ChunkLoadRequest>? RequestCompleted;
/// <summary>
/// Event raised when a chunk load request fails.
/// </summary>
public event EventHandler<(ChunkLoadRequest Request, Exception Exception)>? RequestFailed;
/// <summary>
/// Initializes a new instance of the ChunkLoadingPriority class.
/// </summary>
@ -150,14 +169,14 @@ namespace AdvChkSys.Loading
_maxConcurrentLoads = maxConcurrentLoads;
_requestTimeout = TimeSpan.FromSeconds(requestTimeoutSeconds);
_processSemaphore = new SemaphoreSlim(maxConcurrentLoads, maxConcurrentLoads);
// Initialize queues for each priority level
foreach (Priority priority in Enum.GetValues(typeof(Priority)))
{
_requestQueues[priority] = new Queue<ChunkLoadRequest>();
}
}
/// <summary>
/// Enqueues a chunk load request with the specified priority.
/// </summary>
@ -168,7 +187,7 @@ namespace AdvChkSys.Loading
EnqueueRequest(request);
return request;
}
/// <summary>
/// Enqueues a 3D chunk load request with the specified priority.
/// </summary>
@ -179,7 +198,7 @@ namespace AdvChkSys.Loading
EnqueueRequest(request);
return request;
}
/// <summary>
/// Enqueues a pre-created chunk load request.
/// </summary>
@ -194,11 +213,11 @@ namespace AdvChkSys.Loading
{
return; // Already loading this chunk
}
// Add to appropriate queue
_requestQueues[request.LoadPriority].Enqueue(request);
_requestLookup[request.RequestId] = request;
// Start processing if not already running
EnsureProcessingStarted();
}
@ -206,14 +225,14 @@ namespace AdvChkSys.Loading
{
_queueSemaphore.Release();
}
// For immediate priority, wait for processing to start
if (request.LoadPriority == Priority.Immediate)
{
TriggerImmediateProcessing();
}
}
/// <summary>
/// Cancels a pending chunk load request.
/// </summary>
@ -237,7 +256,7 @@ namespace AdvChkSys.Loading
_queueSemaphore.Release();
}
}
/// <summary>
/// Cancels all pending chunk load requests.
/// </summary>
@ -257,7 +276,7 @@ namespace AdvChkSys.Loading
_queueSemaphore.Release();
}
}
/// <summary>
/// Starts the processing of chunk load requests if not already running.
/// </summary>
@ -265,11 +284,11 @@ namespace AdvChkSys.Loading
{
if (_isProcessing)
return;
_isProcessing = true;
_processingTask = Task.Run(ProcessRequestsAsync);
}
/// <summary>
/// Triggers immediate processing of high-priority requests.
/// </summary>
@ -278,7 +297,7 @@ namespace AdvChkSys.Loading
// This is a hint to the processor to check for immediate requests now
// We don't need to do anything special as the processor checks immediate first
}
/// <summary>
/// Main processing loop for chunk load requests.
/// </summary>
@ -287,13 +306,13 @@ namespace AdvChkSys.Loading
while (!_cancellationSource.IsCancellationRequested)
{
ChunkLoadRequest? request = null;
// Get the next request from the highest priority queue
await _queueSemaphore.WaitAsync();
try
{
request = DequeueNextRequest();
if (request == null)
{
// No requests to process, pause briefly
@ -302,7 +321,7 @@ namespace AdvChkSys.Loading
await Task.Delay(50);
continue;
}
// Mark this chunk as being processed
var key = (request.X, request.Y, request.Z);
if (_activeTasks.ContainsKey(key))
@ -310,7 +329,7 @@ namespace AdvChkSys.Loading
// Already processing this chunk, skip
continue;
}
// Create a task for this request but don't start it yet
var loadTask = ProcessRequestAsync(request);
_activeTasks[key] = loadTask;
@ -322,10 +341,10 @@ namespace AdvChkSys.Loading
_queueSemaphore.Release();
}
}
// Wait for a processing slot
await _processSemaphore.WaitAsync();
// Start the task and continue without waiting
_ = Task.Run(async () =>
{
@ -336,7 +355,7 @@ namespace AdvChkSys.Loading
finally
{
_processSemaphore.Release();
// Remove from active tasks
await _queueSemaphore.WaitAsync();
try
@ -351,7 +370,7 @@ namespace AdvChkSys.Loading
});
}
}
/// <summary>
/// Dequeues the next request from the highest priority queue.
/// </summary>
@ -361,30 +380,30 @@ namespace AdvChkSys.Loading
foreach (Priority priority in Enum.GetValues(typeof(Priority)))
{
var queue = _requestQueues[priority];
while (queue.Count > 0)
{
var request = queue.Dequeue();
// Skip if the request has been canceled
if (!_requestLookup.ContainsKey(request.RequestId))
continue;
// Skip if the request has timed out
if (DateTime.UtcNow - request.Timestamp > _requestTimeout)
{
_requestLookup.Remove(request.RequestId);
continue;
}
// Valid request found
return request;
}
}
return null; // No valid requests found
}
/// <summary>
/// Processes a single chunk load request.
/// </summary>
@ -394,7 +413,7 @@ namespace AdvChkSys.Loading
{
// This would be integrated with your chunk manager
// For now, we'll just simulate the loading with a delay
// Simulate different loading times based on priority
int delayMs = request.LoadPriority switch
{
@ -405,9 +424,9 @@ namespace AdvChkSys.Loading
Priority.Background => 1000,
_ => 200
};
await Task.Delay(delayMs);
// Notify completion
RequestCompleted?.Invoke(this, request);
}
@ -430,7 +449,7 @@ namespace AdvChkSys.Loading
}
}
}
/// <summary>
/// Gets the number of pending requests for each priority level.
/// </summary>
@ -451,7 +470,7 @@ namespace AdvChkSys.Loading
_queueSemaphore.Release();
}
}
/// <summary>
/// Gets the total number of pending requests.
/// </summary>
@ -472,14 +491,14 @@ namespace AdvChkSys.Loading
_queueSemaphore.Release();
}
}
/// <summary>
/// Stops processing and releases resources.
/// </summary>
public async Task ShutdownAsync()
{
_cancellationSource.Cancel();
if (_processingTask != null)
{
try
@ -495,12 +514,12 @@ namespace AdvChkSys.Loading
// Ignore other exceptions during shutdown
}
}
_queueSemaphore.Dispose();
_processSemaphore.Dispose();
_cancellationSource.Dispose();
}
/// <summary>
/// Creates a ChunkLoadingPriority instance with default settings.
/// </summary>
@ -508,7 +527,7 @@ namespace AdvChkSys.Loading
{
return new ChunkLoadingPriority();
}
/// <summary>
/// Creates a ChunkLoadingPriority instance optimized for high throughput.
/// </summary>
@ -518,7 +537,7 @@ namespace AdvChkSys.Loading
maxConcurrentLoads: Environment.ProcessorCount * 2,
requestTimeoutSeconds: 60);
}
/// <summary>
/// Creates a ChunkLoadingPriority instance optimized for low latency.
/// </summary>
@ -528,7 +547,7 @@ namespace AdvChkSys.Loading
maxConcurrentLoads: Math.Max(2, Environment.ProcessorCount / 2),
requestTimeoutSeconds: 15);
}
/// <summary>
/// Integrates with a chunk manager to process load requests.
/// </summary>
@ -539,7 +558,7 @@ namespace AdvChkSys.Loading
// Replace the ProcessRequestAsync method with one that uses the chunk manager
// This is a simplified example - in a real implementation, you'd need to handle
// both 2D and 3D chunk managers and properly cast the interface
RequestCompleted += (sender, request) =>
{
// The chunk has been loaded, you might want to do something with it
@ -547,7 +566,7 @@ namespace AdvChkSys.Loading
// Additional processing if needed
};
}
/// <summary>
/// Gets the estimated time until a request with the given priority would be processed.
/// </summary>
@ -558,7 +577,7 @@ namespace AdvChkSys.Loading
try
{
int totalHigherPriorityRequests = 0;
// Count requests with higher or equal priority
foreach (Priority p in Enum.GetValues(typeof(Priority)))
{
@ -567,13 +586,13 @@ namespace AdvChkSys.Loading
totalHigherPriorityRequests += _requestQueues[p].Count;
}
}
// If no requests or no active tasks, return 0
if (totalHigherPriorityRequests == 0 || _activeTasks.Count == 0)
{
return 0;
}
// Estimate based on current processing rate
// This is a very simple estimate and could be improved with actual metrics
int averageProcessingTimeMs = priority switch
@ -585,10 +604,10 @@ namespace AdvChkSys.Loading
Priority.Background => 1000,
_ => 200
};
// Calculate how many batches of concurrent requests we'll need
int batches = (int)Math.Ceiling(totalHigherPriorityRequests / (double)_maxConcurrentLoads);
return batches * averageProcessingTimeMs;
}
finally
@ -596,7 +615,7 @@ namespace AdvChkSys.Loading
_queueSemaphore.Release();
}
}
/// <summary>
/// Adjusts the priority of an existing request.
/// </summary>
@ -605,7 +624,7 @@ namespace AdvChkSys.Loading
{
// Note: This is not an efficient operation as we don't directly modify the queue
// Instead, we mark the request for priority change when it's processed
_queueSemaphore.Wait();
try
{
@ -616,26 +635,26 @@ namespace AdvChkSys.Loading
{
// Remove from lookup (it will be skipped when dequeued)
_requestLookup.Remove(requestId);
// Create a new request with the same parameters but higher priority
var newRequest = request.Depth > 1
? new ChunkLoadRequest(request.X, request.Y, request.Z, request.Width, request.Height, request.Depth, newPriority)
: new ChunkLoadRequest(request.X, request.Y, request.Width, request.Height, newPriority);
// Add the new request
_requestQueues[newPriority].Enqueue(newRequest);
_requestLookup[newRequest.RequestId] = newRequest;
// If upgrading to immediate, trigger processing
if (newPriority == Priority.Immediate)
{
TriggerImmediateProcessing();
}
}
return true;
}
return false;
}
finally
@ -643,7 +662,7 @@ namespace AdvChkSys.Loading
_queueSemaphore.Release();
}
}
/// <summary>
/// Gets statistics about the current state of the chunk loading system.
/// </summary>
@ -659,13 +678,13 @@ namespace AdvChkSys.Loading
["IsProcessing"] = _isProcessing,
["MaxConcurrentLoads"] = _maxConcurrentLoads
};
// Add queue sizes for each priority
foreach (Priority priority in Enum.GetValues(typeof(Priority)))
{
stats[$"Queue_{priority}"] = _requestQueues[priority].Count;
}
return stats;
}
finally
@ -674,4 +693,4 @@ namespace AdvChkSys.Loading
}
}
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -14,17 +13,17 @@ namespace AdvChkSys.Threading
{
private static ChunkThreadingManager? _instance;
private static readonly object _instanceLock = new();
// Thread safety utilities
private readonly ChunkThreadSafetyManager _threadSafety;
private readonly ChunkAsyncLock _asyncLock;
// Operation queue
private readonly ChunkOperationQueue _operationQueue;
// Cancellation for shutdown
private readonly CancellationTokenSource _shutdownCts = new();
/// <summary>
/// Gets the singleton instance of the ChunkThreadingManager.
/// </summary>
@ -42,7 +41,7 @@ namespace AdvChkSys.Threading
return _instance;
}
}
/// <summary>
/// Initializes a new instance of the ChunkThreadingManager class.
/// </summary>
@ -50,29 +49,29 @@ namespace AdvChkSys.Threading
{
_threadSafety = new ChunkThreadSafetyManager(
ChunkThreadingConfiguration.LockCleanupIntervalMinutes);
_asyncLock = new ChunkAsyncLock(
ChunkThreadingConfiguration.LockCleanupIntervalMinutes);
_operationQueue = new ChunkOperationQueue(
ChunkThreadingConfiguration.DefaultMaxDegreeOfParallelism);
}
/// <summary>
/// Gets the thread safety manager for synchronous locking.
/// </summary>
public ChunkThreadSafetyManager ThreadSafety => _threadSafety;
/// <summary>
/// Gets the async lock manager for asynchronous locking.
/// </summary>
public ChunkAsyncLock AsyncLock => _asyncLock;
/// <summary>
/// Gets the operation queue for sequential chunk operations.
/// </summary>
public ChunkOperationQueue OperationQueue => _operationQueue;
/// <summary>
/// Acquires an exclusive lock on a chunk.
/// </summary>
@ -82,7 +81,7 @@ namespace AdvChkSys.Threading
{
return _threadSafety.AcquireLock(chunk);
}
/// <summary>
/// Acquires a read lock on a chunk.
/// </summary>
@ -92,7 +91,7 @@ namespace AdvChkSys.Threading
{
return _threadSafety.AcquireReadLock(chunk);
}
/// <summary>
/// Acquires a write lock on a chunk.
/// </summary>
@ -102,7 +101,7 @@ namespace AdvChkSys.Threading
{
return _threadSafety.AcquireWriteLock(chunk);
}
/// <summary>
/// Acquires a lock on a chunk asynchronously.
/// </summary>
@ -113,7 +112,7 @@ namespace AdvChkSys.Threading
{
return _asyncLock.LockAsync(chunk, cancellationToken);
}
/// <summary>
/// Enqueues an operation to be performed on a chunk.
/// </summary>
@ -124,7 +123,7 @@ namespace AdvChkSys.Threading
{
return _operationQueue.EnqueueOperationAsync(chunk, operation);
}
/// <summary>
/// Runs an action with a lock on the chunk.
/// </summary>
@ -135,7 +134,7 @@ namespace AdvChkSys.Threading
using var lockObj = AcquireLock(chunk);
action();
}
/// <summary>
/// Runs a function with a lock on the chunk and returns the result.
/// </summary>
@ -148,7 +147,7 @@ namespace AdvChkSys.Threading
using var lockObj = AcquireLock(chunk);
return func();
}
/// <summary>
/// Runs an async action with a lock on the chunk.
/// </summary>
@ -160,7 +159,7 @@ namespace AdvChkSys.Threading
using var lockObj = await LockAsync(chunk, cancellationToken).ConfigureAwait(false);
await action().ConfigureAwait(false);
}
/// <summary>
/// Runs an async function with a lock on the chunk and returns the result.
/// </summary>
@ -174,7 +173,7 @@ namespace AdvChkSys.Threading
using var lockObj = await LockAsync(chunk, cancellationToken).ConfigureAwait(false);
return await func().ConfigureAwait(false);
}
/// <summary>
/// Runs an action on multiple chunks with proper locking to avoid deadlocks.
/// </summary>
@ -184,7 +183,7 @@ namespace AdvChkSys.Threading
{
// Sort chunks by ID to prevent deadlocks
var sortedChunks = SortChunksById(chunks);
// Acquire locks in order
var locks = new IDisposable[sortedChunks.Length];
try
@ -193,7 +192,7 @@ namespace AdvChkSys.Threading
{
locks[i] = AcquireLock(sortedChunks[i]);
}
// Execute the action
action();
}
@ -206,7 +205,7 @@ namespace AdvChkSys.Threading
}
}
}
/// <summary>
/// Runs an async action on multiple chunks with proper locking to avoid deadlocks.
/// </summary>
@ -217,7 +216,7 @@ namespace AdvChkSys.Threading
{
// Sort chunks by ID to prevent deadlocks
var sortedChunks = SortChunksById(chunks);
// Acquire locks in order
var locks = new IDisposable[sortedChunks.Length];
try
@ -226,7 +225,7 @@ namespace AdvChkSys.Threading
{
locks[i] = await LockAsync(sortedChunks[i], cancellationToken).ConfigureAwait(false);
}
// Execute the action
await action().ConfigureAwait(false);
}
@ -239,7 +238,7 @@ namespace AdvChkSys.Threading
}
}
}
/// <summary>
/// Sorts chunks by ID to prevent deadlocks when acquiring multiple locks.
/// </summary>
@ -248,24 +247,24 @@ namespace AdvChkSys.Threading
// Use System.Linq for OrderBy
return chunks.OrderBy(c => c.GetHashCode()).ToArray();
}
/// <summary>
/// Disposes all resources.
/// </summary>
public void Dispose()
{
_shutdownCts.Cancel();
// Shutdown operation queue
_operationQueue.ShutdownAsync().GetAwaiter().GetResult();
// Dispose thread safety managers
(_threadSafety as IDisposable)?.Dispose();
(_asyncLock as IDisposable)?.Dispose();
_shutdownCts.Dispose();
}
/// <summary>
/// Resets the singleton instance (for testing).
/// </summary>

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AdvChkSys.Threading
{
@ -15,19 +15,19 @@ namespace AdvChkSys.Threading
{
// Performance metrics
private readonly ConcurrentDictionary<string, PerformanceMetric> _metrics = new();
// Sampling timer
private readonly Timer _samplingTimer;
// Sampling interval
private readonly TimeSpan _samplingInterval;
// Maximum history to keep
private readonly int _maxHistory;
// Whether monitoring is enabled
private bool _isEnabled;
/// <summary>
/// Initializes a new instance of the ChunkThreadingPerformanceMonitor class.
/// </summary>
@ -39,7 +39,7 @@ namespace AdvChkSys.Threading
_maxHistory = maxHistory;
_samplingTimer = new Timer(SamplePerformance, null, Timeout.Infinite, Timeout.Infinite);
}
/// <summary>
/// Starts monitoring performance.
/// </summary>
@ -51,7 +51,7 @@ namespace AdvChkSys.Threading
_samplingTimer.Change(TimeSpan.Zero, _samplingInterval);
}
}
/// <summary>
/// Stops monitoring performance.
/// </summary>
@ -63,7 +63,7 @@ namespace AdvChkSys.Threading
_samplingTimer.Change(Timeout.Infinite, Timeout.Infinite);
}
}
/// <summary>
/// Samples performance metrics.
/// </summary>
@ -71,28 +71,28 @@ namespace AdvChkSys.Threading
{
if (!_isEnabled)
return;
// Sample thread pool information
ThreadPool.GetAvailableThreads(out int workerThreads, out int completionPortThreads);
ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads);
// Calculate thread pool usage
double workerThreadUsage = 1.0 - ((double)workerThreads / maxWorkerThreads);
double ioThreadUsage = 1.0 - ((double)completionPortThreads / maxCompletionPortThreads);
// Update metrics
UpdateMetric("ThreadPool.WorkerThreadUsage", workerThreadUsage);
UpdateMetric("ThreadPool.IOThreadUsage", ioThreadUsage);
// Sample operation queue information
var operationQueue = ChunkThreadingManager.Instance.OperationQueue;
UpdateMetric("OperationQueue.PendingOperations", operationQueue.PendingOperationCount);
UpdateMetric("OperationQueue.ActiveOperations", operationQueue.ActiveOperationCount);
// Sample diagnostics information
UpdateMetric("Diagnostics.ActiveOperations", ChunkThreadingDiagnostics.GetActiveOperationCount());
UpdateMetric("Diagnostics.LockContentions", ChunkThreadingDiagnostics.GetTotalLockContentionCount());
// Sample process information
var process = Process.GetCurrentProcess();
// Fix: Use process.TotalProcessorTime and calculate uptime manually
@ -101,7 +101,7 @@ namespace AdvChkSys.Threading
UpdateMetric("Process.Memory", process.WorkingSet64 / 1024.0 / 1024.0); // MB
UpdateMetric("Process.Threads", process.Threads.Count);
}
/// <summary>
/// Updates a performance metric.
/// </summary>
@ -112,36 +112,36 @@ namespace AdvChkSys.Threading
metric = new PerformanceMetric(_maxHistory);
_metrics[name] = metric;
}
metric.AddSample(value);
}
/// <summary>
/// Gets a performance report.
/// </summary>
public Dictionary<string, PerformanceReport> GetPerformanceReport()
{
var report = new Dictionary<string, PerformanceReport>();
foreach (var kvp in _metrics)
{
report[kvp.Key] = kvp.Value.GetReport();
}
return report;
}
/// <summary>
/// Gets a formatted performance report.
/// </summary>
public string GetFormattedReport()
{
var report = new System.Text.StringBuilder();
report.AppendLine("=== Chunk Threading Performance Report ===");
report.AppendLine($"Generated: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} UTC");
report.AppendLine();
var metrics = _metrics.OrderBy(m => m.Key).ToList();
foreach (var kvp in metrics)
{
@ -153,10 +153,10 @@ namespace AdvChkSys.Threading
report.AppendLine($" Max: {metricReport.Max:F3}");
report.AppendLine();
}
return report.ToString();
}
/// <summary>
/// Disposes resources.
/// </summary>
@ -165,7 +165,7 @@ namespace AdvChkSys.Threading
Stop();
_samplingTimer.Dispose();
}
/// <summary>
/// Represents a performance metric with history.
/// </summary>
@ -173,26 +173,26 @@ namespace AdvChkSys.Threading
{
private readonly Queue<double> _samples;
private readonly int _maxSamples;
public PerformanceMetric(int maxSamples)
{
_maxSamples = maxSamples;
_samples = new Queue<double>(maxSamples);
}
public void AddSample(double value)
{
lock (_samples)
{
_samples.Enqueue(value);
while (_samples.Count > _maxSamples)
{
_samples.Dequeue();
}
}
}
public PerformanceReport GetReport()
{
lock (_samples)
@ -201,7 +201,7 @@ namespace AdvChkSys.Threading
{
return new PerformanceReport(0, 0, 0, 0, Array.Empty<double>());
}
var samplesArray = _samples.ToArray();
return new PerformanceReport(
samplesArray.Last(),
@ -213,7 +213,7 @@ namespace AdvChkSys.Threading
}
}
}
/// <summary>
/// Represents a performance report.
/// </summary>
@ -223,27 +223,35 @@ namespace AdvChkSys.Threading
/// The current value.
/// </summary>
public double Current { get; }
/// <summary>
/// The average value.
/// </summary>
public double Average { get; }
/// <summary>
/// The minimum value.
/// </summary>
public double Min { get; }
/// <summary>
/// The maximum value.
/// </summary>
public double Max { get; }
/// <summary>
/// The history of values.
/// </summary>
public double[] History { get; }
/// <summary>
///
/// </summary>
/// <param name="current"></param>
/// <param name="average"></param>
/// <param name="min"></param>
/// <param name="max"></param>
/// <param name="history"></param>
public PerformanceReport(double current, double average, double min, double max, double[] history)
{
Current = current;

View File

@ -35,8 +35,10 @@ namespace AdvChkSys.Util
private static ulong GetAvailableMemoryWindows()
{
var memStatus = new MEMORYSTATUSEX();
memStatus.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
var memStatus = new MEMORYSTATUSEX
{
dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX))
};
if (GlobalMemoryStatusEx(ref memStatus))
{
return memStatus.ullAvailPhys;
@ -81,21 +83,61 @@ namespace AdvChkSys.Util
return 0;
}
/// <summary>
/// Structure containing memory status information for Windows systems.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct MEMORYSTATUSEX
{
/// <summary>
/// The size of the structure, in bytes. You must set this member before calling GlobalMemoryStatusEx.
/// </summary>
public uint dwLength;
/// <summary>
/// A number between 0 and 100 that specifies the approximate percentage of physical memory
/// that is in use (0 indicates no memory use and 100 indicates full memory use).
/// </summary>
public uint dwMemoryLoad;
/// <summary>
/// The total size of physical memory, in bytes.
/// </summary>
public ulong ullTotalPhys;
/// <summary>
/// The amount of physical memory currently available, in bytes.
/// This is the amount of physical memory that can be immediately reused without having to write its contents to disk first.
/// </summary>
public ulong ullAvailPhys;
/// <summary>
/// The current committed memory limit for the system or the current process, whichever is smaller, in bytes.
/// </summary>
public ulong ullTotalPageFile;
/// <summary>
/// The maximum amount of memory the current process can commit, in bytes.
/// This value is equal to or smaller than the system-wide available commit value.
/// </summary>
public ulong ullAvailPageFile;
/// <summary>
/// The size of the user-mode portion of the virtual address space of the calling process, in bytes.
/// </summary>
public ulong ullTotalVirtual;
/// <summary>
/// The amount of unreserved and uncommitted memory currently in the user-mode portion
/// of the virtual address space of the calling process, in bytes.
/// </summary>
public ulong ullAvailVirtual;
/// <summary>
/// Reserved. This value is always 0.
/// </summary>
public ulong ullAvailExtendedVirtual;
}
/// <summary>
/// Retrieves information about the system's current usage of both physical and virtual memory.
/// </summary>
/// <param name="lpBuffer">A pointer to a <see cref="MEMORYSTATUSEX"/> structure that receives the memory status information.</param>
/// <returns>If the function succeeds, the return value is <c>true</c>. If the function fails, the return value is <c>false</c>.</returns>
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
}
}