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); } } }