using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using DFEngine.Logging;
namespace DFEngine.Network
{
///
/// The callback type for a network message handler.
///
/// The message being handled.
/// The client that sent the message.
public delegate void NetworkMessageHandlerMethod(NetworkMessage message, NetworkConnection client);
///
/// Class to register handlers for network messages.
///
public class NetworkMessageHandler
{
///
/// The maximum time a task can run for before it is considered dead
/// (can be used for debugging any locking issues with certain areas of code).
///
public const int MaxTaskRuntime = 300; // In seconds, so 5 minutes.
///
/// Returns the number of handlers.
///
public static int Count => Handlers.Count;
///
/// All registered message handlers.
///
private static readonly Dictionary Handlers = new Dictionary();
///
/// Used for running asynchronous tasks, in this case executing packets.
///
private static readonly TaskFactory HandlerFactory = new TaskFactory(TaskCreationOptions.PreferFairness, TaskContinuationOptions.None);
///
/// Currently running tasks to keep track of what the current load is.
///
private static readonly ConcurrentDictionary RunningHandlers = new ConcurrentDictionary();
///
/// Invokes the message handler.
///
public static void Invoke(NetworkMessageHandlerMethod method, NetworkMessage message, NetworkConnection connection)
{
if (method == null || !connection.IsConnected)
{
return;
}
SocketLog.Write(SocketLogLevel.Debug, $"Calling handler for {message.Command}");
var stopwatch = new Stopwatch();
stopwatch.Start();
var cancelSource = new CancellationTokenSource();
var token = cancelSource.Token;
var task = HandlerFactory.StartNew(() =>
{
method.Invoke(message, connection);
token.ThrowIfCancellationRequested();
}, token);
RunningHandlers.TryAdd(task.Id, task);
try
{
if (!task.Wait(MaxTaskRuntime * 1000, token))
{
cancelSource.Cancel();
}
}
catch (AggregateException ex)
{
foreach (var e in ex.Flatten().InnerExceptions)
{
SocketLog.Write(SocketLogLevel.Exception, $"Unhandled exception: {e.Message} - {e.StackTrace}");
}
connection.Disconnect();
}
catch (OperationCanceledException)
{
connection.Disconnect();
}
finally
{
stopwatch.Stop();
RunningHandlers.TryRemove(task.Id, out var unused);
cancelSource.Dispose();
Object.Destroy(message);
SocketLog.Write(SocketLogLevel.Debug, $"Handler took {stopwatch.ElapsedMilliseconds}ms to complete.");
}
}
///
/// Registers a message handler to the collection of handlers.
///
/// The command being handled.
/// The method to invoke.
public static void Store(NetworkCommand command, NetworkMessageHandlerMethod method)
{
if (!Handlers.ContainsKey(command))
{
Handlers.Add(command, method);
}
}
///
/// Tries to get a message handler from the collection.
///
/// The message's command.
/// The handler method.
///
public static bool TryFetch(NetworkCommand command, out NetworkMessageHandlerMethod method)
{
return Handlers.TryGetValue(command, out method);
}
}
}