// Copyright 2018 RED Software, LLC. All Rights Reserved.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace IgniteEngine.Networking
{
///
/// The callback type for a network message handler.
///
/// The message being handled.
/// The connection that sent the message.
public delegate void NetworkMessageHandlerMethod(NetworkMessage message, NetworkConnection connection);
///
/// 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 MAX_TASK_RUNTIME = 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;
}
Debug.Log($"[{connection}] Invoking handler: {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(MAX_TASK_RUNTIME * 1000, token))
{
cancelSource.Cancel();
}
}
catch (AggregateException ex)
{
foreach (var e in ex.Flatten().InnerExceptions)
{
Debug.LogException($"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);
Debug.Log($"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);
}
}
}