// This file is provided under The MIT License as part of RiptideNetworking. // Copyright (c) Tom Weiland // For additional information please see the included LICENSE.md file or view it on GitHub: // https://github.com/RiptideNetworking/Riptide/blob/main/LICENSE.md using Riptide.Transports; using Riptide.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Riptide { /// The reason the connection attempt was rejected. public enum RejectReason : byte { /// No response was received from the server (because the client has no internet connection, the server is offline, no server is listening on the target endpoint, etc.). NoConnection, /// The client is already connected. AlreadyConnected, /// The server is full. ServerFull, /// The connection attempt was rejected. Rejected, /// The connection attempt was rejected and custom data may have been included with the rejection message. Custom } /// The reason for a disconnection. public enum DisconnectReason : byte { /// No connection was ever established. NeverConnected, /// The connection attempt was rejected by the server. ConnectionRejected, /// The active transport detected a problem with the connection. TransportError, /// The connection timed out. /// /// This also acts as the fallback reason—if a client disconnects and the message containing the real reason is lost /// in transmission, it can't be resent as the connection will have already been closed. As a result, the other end will time /// out the connection after a short period of time and this will be used as the reason. /// TimedOut, /// The client was forcibly disconnected by the server. Kicked, /// The server shut down. ServerStopped, /// The disconnection was initiated by the client. Disconnected, /// The connection's loss and/or resend rates exceeded the maximum acceptable thresholds, or a reliably sent message could not be delivered. PoorConnection } /// Provides base functionality for and . public abstract class Peer { /// The name to use when logging messages via . public readonly string LogName; /// Sets the relevant connections' s. public abstract int TimeoutTime { set; } /// The interval (in milliseconds) at which to send and expect heartbeats to be received. /// Changes to this value will only take effect after the next heartbeat is executed. public int HeartbeatInterval { get; set; } = 1000; /// The number of currently active and instances. internal static int ActiveCount { get; private set; } /// The time (in milliseconds) for which to wait before giving up on a connection attempt. internal int ConnectTimeoutTime { get; set; } = 10000; /// The current time. internal long CurrentTime { get; private set; } /// Whether or not the peer should use the built-in message handler system. protected bool useMessageHandlers; /// The default time (in milliseconds) after which to disconnect if no heartbeats are received. protected int defaultTimeout = 5000; /// A stopwatch used to track how much time has passed. private readonly System.Diagnostics.Stopwatch time = new System.Diagnostics.Stopwatch(); /// Received messages which need to be handled. private readonly Queue messagesToHandle = new Queue(); /// A queue of events to execute, ordered by how soon they need to be executed. private readonly PriorityQueue eventQueue = new PriorityQueue(); /// Initializes the peer. /// The name to use when logging messages via . public Peer(string logName) { LogName = logName; } /// Retrieves methods marked with . /// An array containing message handler methods. protected MethodInfo[] FindMessageHandlers() { return new MethodInfo[0]; /*string thisAssemblyName = Assembly.GetExecutingAssembly().GetName().FullName; return AppDomain.CurrentDomain.GetAssemblies() .Where(a => a .GetReferencedAssemblies() .Any(n => n.FullName == thisAssemblyName)) // Get only assemblies that reference this assembly .SelectMany(a => a.GetTypes()) .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) // Include instance methods in the search so we can show the developer an error instead of silently not adding instance methods to the dictionary .Where(m => m.GetCustomAttributes(typeof(MessageHandlerAttribute), false).Length > 0) .ToArray();*/ } /// Builds a dictionary of message IDs and their corresponding message handler methods. /// The ID of the group of message handler methods to include in the dictionary. protected abstract void CreateMessageHandlersDictionary(byte messageHandlerGroupId); /// Starts tracking how much time has passed. protected void StartTime() { CurrentTime = 0; time.Restart(); } /// Stops tracking how much time has passed. protected void StopTime() { CurrentTime = 0; time.Reset(); eventQueue.Clear(); } /// Beats the heart. internal abstract void Heartbeat(); /// Handles any received messages and invokes any delayed events which need to be invoked. public virtual void Update() { CurrentTime = time.ElapsedMilliseconds; while (eventQueue.Count > 0 && eventQueue.PeekPriority() <= CurrentTime) eventQueue.Dequeue().Invoke(); } /// Sets up a delayed event to be executed after the given time has passed. /// How long from now to execute the delayed event, in milliseconds. /// The delayed event to execute later. internal void ExecuteLater(long inMS, DelayedEvent delayedEvent) { eventQueue.Enqueue(delayedEvent, CurrentTime + inMS); } /// Handles all queued messages. protected void HandleMessages() { while (messagesToHandle.Count > 0) { MessageToHandle handle = messagesToHandle.Dequeue(); Handle(handle.Message, handle.Header, handle.FromConnection); } } /// Handles data received by the transport. protected void HandleData(object _, DataReceivedEventArgs e) { Message message = Message.Create().Init(e.DataBuffer[0], e.Amount, out MessageHeader header); if (message.SendMode == MessageSendMode.Notify) { if (e.Amount < Message.MinNotifyBytes) return; e.FromConnection.ProcessNotify(e.DataBuffer, e.Amount, message); } else if (message.SendMode == MessageSendMode.Unreliable) { if (e.Amount > Message.MinUnreliableBytes) Buffer.BlockCopy(e.DataBuffer, 1, message.Data, 1, e.Amount - 1); messagesToHandle.Enqueue(new MessageToHandle(message, header, e.FromConnection)); e.FromConnection.Metrics.ReceivedUnreliable(e.Amount); } else { if (e.Amount < Message.MinReliableBytes) return; e.FromConnection.Metrics.ReceivedReliable(e.Amount); if (e.FromConnection.ShouldHandle(Converter.UShortFromBits(e.DataBuffer, Message.HeaderBits))) { Buffer.BlockCopy(e.DataBuffer, 1, message.Data, 1, e.Amount - 1); messagesToHandle.Enqueue(new MessageToHandle(message, header, e.FromConnection)); } else e.FromConnection.Metrics.ReliableDiscarded++; } } /// Handles a message. /// The message to handle. /// The message's header type. /// The connection which the message was received on. protected abstract void Handle(Message message, MessageHeader header, Connection connection); /// Disconnects the connection in question. Necessary for connections to be able to initiate disconnections (like in the case of poor connection quality). /// The connection to disconnect. /// The reason why the connection is being disconnected. internal abstract void Disconnect(Connection connection, DisconnectReason reason); /// Increases . For use when a new or is started. protected static void IncreaseActiveCount() { ActiveCount++; } /// Decreases . For use when a or is stopped. protected static void DecreaseActiveCount() { ActiveCount--; if (ActiveCount < 0) ActiveCount = 0; } } /// Stores information about a message that needs to be handled. internal struct MessageToHandle { /// The message that needs to be handled. internal readonly Message Message; /// The message's header type. internal readonly MessageHeader Header; /// The connection on which the message was received. internal readonly Connection FromConnection; /// Handles initialization. /// The message that needs to be handled. /// The message's header type. /// The connection on which the message was received. public MessageToHandle(Message message, MessageHeader header, Connection fromConnection) { Message = message; Header = header; FromConnection = fromConnection; } } }