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