forked from cgvr/DeltaVR
		
	
		
			
				
	
	
		
			377 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			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
 | 
						|
    }
 | 
						|
}
 | 
						|
 |