StationObscurum/Assets/FishNet/Runtime/Managing/Transporting/LatencySimulator.cs

377 lines
13 KiB
C#

using FishNet.Connection;
using FishNet.Transporting;
using FishNet.Utility.Performance;
using System;
using System.Collections.Generic;
using UnityEngine;
//Thanks to TiToMoskito originally creating this as a Transport.
//https://github.com/TiToMoskito/FishyLatency
namespace FishNet.Managing.Transporting
{
[System.Serializable]
public class LatencySimulator
{
#region Types.
/// <summary>
/// A message affected by latency.
/// </summary>
private struct Message
{
public readonly int ConnectionId;
public readonly byte[] Data;
public readonly int Length;
public readonly float SendTime;
public Message(int connectionId, ArraySegment<byte> segment, float latency)
{
this.ConnectionId = connectionId;
this.SendTime = (Time.unscaledTime + latency);
this.Length = segment.Count;
this.Data = ByteArrayPool.Retrieve(this.Length);
Buffer.BlockCopy(segment.Array, segment.Offset, this.Data, 0, this.Length);
}
public ArraySegment<byte> GetSegment()
{
return new ArraySegment<byte>(Data, 0, Length);
}
}
#endregion
#region Internal.
/// <summary>
/// True if latency can be simulated.
/// </summary>
internal bool CanSimulate => (GetEnabled() && (GetLatency() > 0 || GetPacketLost() > 0 || GetOutOfOrder() > 0));
#endregion
#region Serialized
[Header("Settings")]
/// <summary>
///
/// </summary>
[Tooltip("True if latency simulator is enabled.")]
[SerializeField]
private bool _enabled;
/// <summary>
/// Gets the enabled value of simulator.
/// </summary>
public bool GetEnabled() => _enabled;
/// <summary>
/// Sets the enabled value of simulator.
/// </summary>
/// <param name="value">New value.</param>
public void SetEnabled(bool value)
{
if (value == _enabled)
return;
_enabled = value;
Reset();
}
/// <summary>
///
/// </summary>
[Tooltip("True to add latency on clientHost as well.")]
[SerializeField]
private bool _simulateHost = true;
/// <summary>
/// Milliseconds to add between packets. When acting as host this value will be doubled. Added latency will be a minimum of tick rate.
/// </summary>
[Tooltip("Milliseconds to add between packets. When acting as host this value will be doubled. Added latency will be a minimum of tick rate.")]
[Range(0, 60000)]
[SerializeField]
private long _latency = 0;
/// <summary>
/// Gets the latency value.
/// </summary>
/// <returns></returns>
public long GetLatency() => _latency;
/// <summary>
/// Sets a new latency value.
/// </summary>
/// <param name="value">Latency as milliseconds.</param>
public void SetLatency(long value) => _latency = value;
[Header("Unreliable")]
/// <summary>
/// Percentage of unreliable packets which should arrive out of order.
/// </summary>
[Tooltip("Percentage of unreliable packets which should arrive out of order.")]
[Range(0f, 1f)]
[SerializeField]
private double _outOfOrder = 0;
/// <summary>
/// Out of order chance, 1f is a 100% chance to occur.
/// </summary>
/// <returns></returns>
public double GetOutOfOrder() => _outOfOrder;
/// <summary>
/// Sets out of order chance. 1f is a 100% chance to occur.
/// </summary>
/// <param name="value">New Value.</param>
public void SetOutOfOrder(double value) => _outOfOrder = value;
/// <summary>
/// Percentage of packets which should drop.
/// </summary>
[Tooltip("Percentage of packets which should drop.")]
[Range(0, 1)]
[SerializeField]
private double _packetLoss = 0;
/// <summary>
/// Gets packet loss chance. 1f is a 100% chance to occur.
/// </summary>
/// <returns></returns>
public double GetPacketLost() => _packetLoss;
/// <summary>
/// Sets packet loss chance. 1f is a 100% chance to occur.
/// </summary>
/// <param name="value">New Value.</param>
public void SetPacketLoss(double value) => _packetLoss = value;
#endregion
#region Private
/// <summary>
/// Transport to send data on.
/// </summary>
private Transport _transport;
/// <summary>
/// Reliable messages to the server.
/// </summary>
private List<Message> _toServerReliable = new List<Message>();
/// <summary>
/// Unreliable messages to the server.
/// </summary>
private List<Message> _toServerUnreliable = new List<Message>();
/// <summary>
/// Reliable messages to clients.
/// </summary>
private List<Message> _toClientReliable = new List<Message>();
/// <summary>
/// Unreliable messages to clients.
/// </summary>
private List<Message> _toClientUnreliable = new List<Message>();
/// <summary>
/// NetworkManager for this instance.
/// </summary>
private NetworkManager _networkManager;
/// <summary>
/// Used to generate chances of latency.
/// </summary>
private readonly System.Random _random = new System.Random();
#endregion
#region Initialization and Unity
public void Initialize(NetworkManager manager, Transport transport)
{
_networkManager = manager;
_transport = transport;
}
#endregion
/// <summary>
/// Stops both client and server.
/// </summary>
public void Reset()
{
bool enabled = GetEnabled();
if (_transport != null && enabled)
{
IterateAndStore(_toServerReliable);
IterateAndStore(_toServerUnreliable);
IterateAndStore(_toClientReliable);
IterateAndStore(_toClientUnreliable);
}
void IterateAndStore(List<Message> messages)
{
foreach (Message m in messages)
{
_transport.SendToServer((byte)Channel.Reliable, m.GetSegment());
ByteArrayPool.Store(m.Data);
}
}
_toServerReliable.Clear();
_toServerUnreliable.Clear();
_toClientReliable.Clear();
_toClientUnreliable.Clear();
}
/// <summary>
/// Removes pending or held packets for a connection.
/// </summary>
/// <param name="conn">Connection to remove pending packets for.</param>
public void RemovePendingForConnection(int connectionId)
{
RemoveFromCollection(_toServerUnreliable);
RemoveFromCollection(_toServerUnreliable);
RemoveFromCollection(_toClientReliable);
RemoveFromCollection(_toClientUnreliable);
void RemoveFromCollection(List<Message> c)
{
for (int i = 0; i < c.Count; i++)
{
if (c[i].ConnectionId == connectionId)
{
c.RemoveAt(i);
i--;
}
}
}
}
#region Simulation
/// <summary>
/// Returns long latency as a float.
/// </summary>
/// <param name="ms"></param>
/// <returns></returns>
private float GetLatencyAsFloat()
{
return (float)(_latency / 1000f);
}
/// <summary>
/// Adds a packet for simulation.
/// </summary>
public void AddOutgoing(byte channelId, ArraySegment<byte> segment, bool toServer = true, int connectionId = -1)
{
/* If to not simulate for host see if this packet
* should be sent normally. */
if (!_simulateHost && _networkManager != null && _networkManager.IsHost)
{
/* If going to the server and is host then
* it must be sent from clientHost. */
if (toServer)
{
_transport.SendToServer(channelId, segment);
return;
}
//Not to server, see if going to clientHost.
else
{
//If connId is the same as clientHost id.
if (_networkManager.ClientManager.Connection.ClientId == connectionId)
{
_transport.SendToClient(channelId, segment, connectionId);
return;
}
}
}
List<Message> collection;
Channel c = (Channel)channelId;
if (toServer)
collection = (c == Channel.Reliable) ? _toServerReliable : _toServerUnreliable;
else
collection = (c == Channel.Reliable) ? _toClientReliable : _toClientUnreliable;
float latency = GetLatencyAsFloat();
//If dropping check to add extra latency if reliable, or discard if not.
if (DropPacket())
{
if (c == Channel.Reliable)
{
latency += (latency * 0.3f); //add extra for resend.
}
//If not reliable then return the segment array to pool.
else
{
return;
}
}
Message msg = new Message(connectionId, segment, latency);
int count = collection.Count;
if (c == Channel.Unreliable && count > 0 && OutOfOrderPacket(c))
collection.Insert(count - 1, msg);
else
collection.Add(msg);
}
/// <summary>
/// Simulates pending outgoing packets.
/// </summary>
/// <param name="toServer">True if sending to the server.</param>
public void IterateOutgoing(bool toServer)
{
if (_transport == null)
{
Reset();
return;
}
if (toServer)
{
IterateCollection(_toServerReliable, Channel.Reliable);
IterateCollection(_toServerUnreliable, Channel.Unreliable);
}
else
{
IterateCollection(_toClientReliable, Channel.Reliable);
IterateCollection(_toClientUnreliable, Channel.Unreliable);
}
void IterateCollection(List<Message> collection, Channel channel)
{
byte cByte = (byte)channel;
float unscaledTime = Time.unscaledTime;
int count = collection.Count;
int iterations = 0;
for (int i = 0; i < count; i++)
{
Message msg = collection[i];
//Not enough time has passed.
if (unscaledTime < msg.SendTime)
break;
if (toServer)
_transport.SendToServer(cByte, msg.GetSegment());
else
_transport.SendToClient(cByte, msg.GetSegment(), msg.ConnectionId);
iterations++;
}
if (iterations > 0)
{
for (int i = 0; i < iterations; i++)
ByteArrayPool.Store(collection[i].Data);
collection.RemoveRange(0, iterations);
}
}
_transport.IterateOutgoing(toServer);
}
/// <summary>
/// Returns if a packet should drop.
/// </summary>
/// <returns></returns>
private bool DropPacket()
{
return (_packetLoss > 0d && (_random.NextDouble() < _packetLoss));
}
/// <summary>
/// Returns if a packet should be out of order.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
private bool OutOfOrderPacket(Channel c)
{
if (c == Channel.Reliable)
return false;
return (_outOfOrder > 0d && (_random.NextDouble() < _outOfOrder));
}
#endregion
}
}