668 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			668 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using FishNet.Connection;
 | |
| using FishNet.Managing.Timing;
 | |
| using FishNet.Serializing;
 | |
| using FishNet.Transporting;
 | |
| using FishNet.Transporting.Multipass;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Runtime.CompilerServices;
 | |
| using UnityEngine;
 | |
| 
 | |
| namespace FishNet.Managing.Transporting
 | |
| {
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Communicates with the Transport to send and receive data.
 | |
|     /// </summary>
 | |
|     [DisallowMultipleComponent]
 | |
|     [AddComponentMenu("FishNet/Manager/TransportManager")]
 | |
|     public sealed partial class TransportManager : MonoBehaviour
 | |
|     {
 | |
|         #region Types.
 | |
|         private struct DisconnectingClient
 | |
|         {
 | |
|             public uint Tick;
 | |
|             public NetworkConnection Connection;
 | |
| 
 | |
|             public DisconnectingClient(uint tick, NetworkConnection connection)
 | |
|             {
 | |
|                 Tick = tick;
 | |
|                 Connection = connection;
 | |
|             }
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Public.
 | |
|         /// <summary>
 | |
|         /// Called before IterateOutgoing has started.
 | |
|         /// </summary>
 | |
|         internal event Action OnIterateOutgoingStart;
 | |
|         /// <summary>
 | |
|         /// Called after IterateOutgoing has completed.
 | |
|         /// </summary>
 | |
|         internal event Action OnIterateOutgoingEnd;
 | |
|         /// <summary>
 | |
|         /// Called before IterateIncoming has started. True for on server, false for on client.
 | |
|         /// </summary>
 | |
|         internal event Action<bool> OnIterateIncomingStart;
 | |
|         /// <summary>
 | |
|         /// Called after IterateIncoming has completed. True for on server, false for on client.
 | |
|         /// </summary>
 | |
|         internal event Action<bool> OnIterateIncomingEnd;
 | |
|         /// <summary>
 | |
|         /// The current Transport being used.
 | |
|         /// </summary>
 | |
|         [Tooltip("The current Transport being used.")]
 | |
|         public Transport Transport;
 | |
|         #endregion
 | |
| 
 | |
|         #region Serialized.
 | |
|         /// <summary>
 | |
|         /// Layer used to modify data before it is sent or received.
 | |
|         /// </summary>
 | |
|         [Tooltip("Layer used to modify data before it is sent or received.")]
 | |
|         [SerializeField]
 | |
|         private IntermediateLayer _intermediateLayer;
 | |
|         /// <summary>
 | |
|         /// 
 | |
|         /// </summary>
 | |
|         [Tooltip("Latency simulation settings.")]
 | |
|         [SerializeField]
 | |
|         private LatencySimulator _latencySimulator = new LatencySimulator();
 | |
|         /// <summary>
 | |
|         /// Latency simulation settings.
 | |
|         /// </summary>
 | |
|         public LatencySimulator LatencySimulator
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 //Shouldn't ever be null unless the user nullifies it.
 | |
|                 if (_latencySimulator == null)
 | |
|                     _latencySimulator = new LatencySimulator();
 | |
|                 return _latencySimulator;
 | |
|             }
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Private.
 | |
|         /// <summary>
 | |
|         /// NetworkConnections on the server which have to send data to clients.
 | |
|         /// </summary>
 | |
|         private List<NetworkConnection> _dirtyToClients = new List<NetworkConnection>();
 | |
|         /// <summary>
 | |
|         /// PacketBundles to send to the server.
 | |
|         /// </summary>
 | |
|         private List<PacketBundle> _toServerBundles = new List<PacketBundle>();
 | |
|         /// <summary>
 | |
|         /// NetworkManager handling this TransportManager.
 | |
|         /// </summary>
 | |
|         private NetworkManager _networkManager;
 | |
|         /// <summary>
 | |
|         /// Clients which are pending disconnects.
 | |
|         /// </summary>
 | |
|         private List<DisconnectingClient> _disconnectingClients = new List<DisconnectingClient>();
 | |
|         /// <summary>
 | |
|         /// Lowest MTU of all transports for channels.
 | |
|         /// </summary>
 | |
|         private int[] _lowestMtu;
 | |
|         #endregion
 | |
| 
 | |
|         #region Consts.
 | |
|         /// <summary>
 | |
|         /// Number of bytes sent for PacketId.
 | |
|         /// </summary>
 | |
|         public const byte PACKET_ID_BYTES = 2;
 | |
|         /// <summary>
 | |
|         /// Number of bytes sent for ObjectId.
 | |
|         /// </summary>
 | |
|         public const byte OBJECT_ID_BYTES = 2;
 | |
|         /// <summary>
 | |
|         /// Number of bytes sent for ComponentIndex.
 | |
|         /// </summary>
 | |
|         public const byte COMPONENT_INDEX_BYTES = 1;
 | |
|         /// <summary>
 | |
|         /// Number of bytes sent for Tick.
 | |
|         /// </summary>
 | |
|         public const byte TICK_BYTES = 4;
 | |
|         /// <summary>
 | |
|         /// Number of bytes sent to indicate split count.
 | |
|         /// </summary>
 | |
|         private const byte SPLIT_COUNT_BYTES = 4;
 | |
|         /// <summary>
 | |
|         /// Number of bytes required for split data. 
 | |
|         /// </summary>
 | |
|         public const byte SPLIT_INDICATOR_SIZE = (PACKET_ID_BYTES + SPLIT_COUNT_BYTES);
 | |
|         /// <summary>
 | |
|         /// Number of channels supported.
 | |
|         /// </summary>
 | |
|         public const byte CHANNEL_COUNT = 2;
 | |
|         #endregion
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Initializes this script for use.
 | |
|         /// </summary>
 | |
|         internal void InitializeOnce_Internal(NetworkManager manager)
 | |
|         {
 | |
|             _networkManager = manager;
 | |
|             /* If transport isn't specified then add default
 | |
|              * transport. */
 | |
|             if (Transport == null && !gameObject.TryGetComponent<Transport>(out Transport))
 | |
|                 Transport = gameObject.AddComponent<FishNet.Transporting.Tugboat.Tugboat>();
 | |
| 
 | |
|             Transport.Initialize(_networkManager, 0);
 | |
|             //Cache lowest Mtus.
 | |
|             _lowestMtu = new int[CHANNEL_COUNT];
 | |
|             for (byte i = 0; i < CHANNEL_COUNT; i++)
 | |
|                 _lowestMtu[i] = GetLowestMTU(i);
 | |
| 
 | |
|             InitializeToServerBundles();
 | |
|             if (_intermediateLayer != null)
 | |
|                 _intermediateLayer.InitializeOnce(this);
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|             _latencySimulator.Initialize(manager, Transport);
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sets a connection from server to client dirty.
 | |
|         /// </summary>
 | |
|         /// <param name="conn"></param>
 | |
|         internal void ServerDirty(NetworkConnection conn)
 | |
|         {
 | |
|             _dirtyToClients.Add(conn);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Initializes ToServerBundles for use.
 | |
|         /// </summary>
 | |
|         private void InitializeToServerBundles()
 | |
|         {
 | |
|             /* For ease of use FishNet will always have
 | |
|              * only two channels, reliable and unreliable. 
 | |
|              * Even if the transport only supports reliable
 | |
|              * also setup for unreliable. */
 | |
|             for (byte i = 0; i < CHANNEL_COUNT; i++)
 | |
|             {
 | |
|                 int mtu = GetLowestMTU(i);
 | |
|                 _toServerBundles.Add(new PacketBundle(_networkManager, mtu));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         #region GetMTU.
 | |
|         /* Returned MTUs are always -1 to allow an extra byte
 | |
|          * to specify channel where certain transports do
 | |
|          * not allow or provide channel information. */
 | |
|         /// <summary>
 | |
|         /// Returns the lowest MTU for a channel. When using multipass this will evaluate all transports within Multipass.
 | |
|         /// </summary>
 | |
|         /// <param name="channel"></param>
 | |
|         /// <returns></returns>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         public int GetLowestMTU(byte channel)
 | |
|         {
 | |
|             //Use cached if available.
 | |
|             if (_lowestMtu[channel] > 0)
 | |
|                 return _lowestMtu[channel];
 | |
| 
 | |
|             if (Transport is Multipass mp)
 | |
|             {
 | |
|                 int? lowestMtu = null;
 | |
|                 foreach (Transport t in mp.Transports)
 | |
|                 {
 | |
|                     int thisMtu = t.GetMTU(channel);
 | |
|                     if (lowestMtu == null || thisMtu < lowestMtu.Value)
 | |
|                         lowestMtu = thisMtu;
 | |
|                 }
 | |
| 
 | |
|                 //If lowest was not changed return unset.
 | |
|                 if (lowestMtu == null)
 | |
|                 {
 | |
|                     return -1;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     int mtu = lowestMtu.Value;
 | |
|                     if (mtu >= 0)
 | |
|                         mtu -= 1;
 | |
|                     return mtu;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 return GetMTU(channel);
 | |
|             }
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Gets MTU on the current transport for channel.
 | |
|         /// </summary>
 | |
|         /// <param name="channel">Channel to get MTU of.</param>
 | |
|         /// <returns></returns>
 | |
|         public int GetMTU(byte channel)
 | |
|         {
 | |
|             int mtu = Transport.GetMTU(channel);
 | |
|             if (mtu >= 0)
 | |
|                 mtu -= 1;
 | |
|             return mtu;
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Gets MTU on the transportIndex for channel. This requires use of Multipass.
 | |
|         /// </summary>
 | |
|         /// <param name="transportIndex">Index of the transport to get the MTU on.</param>
 | |
|         /// <param name="channel">Channel to get MTU of.</param>
 | |
|         /// <returns></returns>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         public int GetMTU(int transportIndex, byte channel)
 | |
|         {
 | |
|             if (Transport is Multipass mp)
 | |
|             {
 | |
|                 int mtu = mp.GetMTU(channel, transportIndex);
 | |
|                 if (mtu >= 0)
 | |
|                     mtu -= 1;
 | |
|                 return mtu;
 | |
|             }
 | |
|             //Using first/only transport.
 | |
|             else if (transportIndex == 0)
 | |
|             {
 | |
|                 return GetMTU(channel);
 | |
|             }
 | |
|             //Unhandled.
 | |
|             else
 | |
|             {
 | |
|                 _networkManager.LogWarning($"MTU cannot be returned with transportIndex because {typeof(Multipass).Name} is not in use.");
 | |
|                 return -1;
 | |
|             }
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Gets MTU on the transport type for channel. This requires use of Multipass.
 | |
|         /// </summary>
 | |
|         /// <typeparam name="T">Tyep of transport to use.</typeparam>
 | |
|         /// <param name="channel">Channel to get MTU of.</param>
 | |
|         /// <returns></returns>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         public int GetMTU<T>(byte channel) where T : Transport
 | |
|         {
 | |
|             Transport transport = GetTransport<T>();
 | |
|             if (transport != null)
 | |
|             {
 | |
|                 int mtu = transport.GetMTU(channel);
 | |
|                 if (mtu >= 0)
 | |
|                     mtu -= 1;
 | |
|                 return mtu;
 | |
|             }
 | |
| 
 | |
|             //Fall through.
 | |
|             return -1;
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Passes received to the intermediate layer.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         internal ArraySegment<byte> ProcessIntermediateIncoming(ArraySegment<byte> src, bool fromServer)
 | |
|         {
 | |
|             return (_intermediateLayer == null) ? src : _intermediateLayer.HandleIncoming(src, fromServer);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Passes sent to the intermediate layer.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         private ArraySegment<byte> ProcessIntermediateOutgoing(ArraySegment<byte> src, bool toServer)
 | |
|         {
 | |
|             return (_intermediateLayer == null) ? src : _intermediateLayer.HandleOutoing(src, toServer);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends data to a client.
 | |
|         /// </summary>
 | |
|         /// <param name="channelId">Channel to send on.</param>
 | |
|         /// <param name="segment">Data to send.</param>
 | |
|         /// <param name="connection">Connection to send to. Use null for all clients.</param>
 | |
|         /// <param name="splitLargeMessages">True to split large packets which exceed MTU and send them in order on the reliable channel.</param>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         internal void SendToClient(byte channelId, ArraySegment<byte> segment, NetworkConnection connection, bool splitLargeMessages = true)
 | |
|         {
 | |
|             segment = ProcessIntermediateOutgoing(segment, false);
 | |
|             SetSplitValues(channelId, segment, splitLargeMessages, out int requiredSplitMessages, out int maxSplitMessageSize);
 | |
|             SendToClient_Internal(channelId, segment, connection, requiredSplitMessages, maxSplitMessageSize);
 | |
|         }
 | |
|         private void SendToClient_Internal(byte channelId, ArraySegment<byte> segment, NetworkConnection connection, int requiredSplitMessages, int maxSplitMessageSize)
 | |
|         {
 | |
|             if (connection == null)
 | |
|                 return;
 | |
| 
 | |
|             if (requiredSplitMessages > 1)
 | |
|                 SendSplitData(connection, ref segment, requiredSplitMessages, maxSplitMessageSize);
 | |
|             else
 | |
|                 connection.SendToClient(channelId, segment);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends data to observers.
 | |
|         /// </summary>
 | |
|         /// <param name="channelId"></param>
 | |
|         /// <param name="segment"></param>
 | |
|         /// <param name="observers"></param>
 | |
|         /// <param name="splitLargeMessages">True to split large packets which exceed MTU and send them in order on the reliable channel.</param>
 | |
|         internal void SendToClients(byte channelId, ArraySegment<byte> segment, HashSet<NetworkConnection> observers, HashSet<NetworkConnection> excludedConnections = null, bool splitLargeMessages = true)
 | |
|         {
 | |
|             segment = ProcessIntermediateOutgoing(segment, false);
 | |
|             SetSplitValues(channelId, segment, splitLargeMessages, out int requiredSplitMessages, out int maxSplitMessageSize);
 | |
|             SendToClients_Internal(channelId, segment, observers, excludedConnections, requiredSplitMessages, maxSplitMessageSize);
 | |
|         }
 | |
|         private void SendToClients_Internal(byte channelId, ArraySegment<byte> segment, HashSet<NetworkConnection> observers, HashSet<NetworkConnection> excludedConnections, int requiredSplitMessages, int maxSplitMessageSize)
 | |
|         {
 | |
|             if (excludedConnections == null || excludedConnections.Count == 0)
 | |
|             {
 | |
|                 foreach (NetworkConnection conn in observers)
 | |
|                     SendToClient_Internal(channelId, segment, conn, requiredSplitMessages, maxSplitMessageSize);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 foreach (NetworkConnection conn in observers)
 | |
|                 {
 | |
|                     if (excludedConnections.Contains(conn))
 | |
|                         continue;
 | |
|                     SendToClient_Internal(channelId, segment, conn, requiredSplitMessages, maxSplitMessageSize);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends data to all clients.
 | |
|         /// </summary>
 | |
|         /// <param name="channelId">Channel to send on.</param>
 | |
|         /// <param name="segment">Data to send.</param>
 | |
|         /// <param name="splitLargeMessages">True to split large packets which exceed MTU and send them in order on the reliable channel.</param>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         internal void SendToClients(byte channelId, ArraySegment<byte> segment, bool splitLargeMessages = true)
 | |
|         {
 | |
|             segment = ProcessIntermediateOutgoing(segment, false);
 | |
|             SetSplitValues(channelId, segment, splitLargeMessages, out int requiredSplitMessages, out int maxSplitMessageSize);
 | |
|             SendToClients_Internal(channelId, segment, requiredSplitMessages, maxSplitMessageSize);
 | |
|         }
 | |
|         private void SendToClients_Internal(byte channelId, ArraySegment<byte> segment, int requiredSplitMessages, int maxSplitMessageSize)
 | |
|         {
 | |
|             /* Rather than buffer the message once and send to every client
 | |
|              * it must be queued into every client. This ensures clients
 | |
|              * receive the message in order of other packets being
 | |
|              * delivered to them. */
 | |
|             foreach (NetworkConnection conn in _networkManager.ServerManager.Clients.Values)
 | |
|                 SendToClient_Internal(channelId, segment, conn, requiredSplitMessages, maxSplitMessageSize);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends data to the server.
 | |
|         /// </summary>
 | |
|         /// <param name="channelId">Channel to send on.</param>
 | |
|         /// <param name="segment">Data to send.</param>
 | |
|         /// <param name="splitLargeMessages">True to split large packets which exceed MTU and send them in order on the reliable channel.</param>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         internal void SendToServer(byte channelId, ArraySegment<byte> segment, bool splitLargeMessages = true)
 | |
|         {
 | |
|             segment = ProcessIntermediateOutgoing(segment, true);
 | |
|             SetSplitValues(channelId, segment, splitLargeMessages, out int requiredSplitMessages, out int maxSplitMessageSize);
 | |
|             SendToServer_Internal(channelId, segment, requiredSplitMessages, maxSplitMessageSize);
 | |
|         }
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         private void SendToServer_Internal(byte channelId, ArraySegment<byte> segment, int requiredSplitMessages, int maxSplitMessageSize)
 | |
|         {
 | |
|             if (channelId >= _toServerBundles.Count)
 | |
|                 channelId = (byte)Channel.Reliable;
 | |
| 
 | |
|             if (requiredSplitMessages > 1)
 | |
|                 SendSplitData(null, ref segment, requiredSplitMessages, maxSplitMessageSize);
 | |
|             else
 | |
|                 _toServerBundles[channelId].Write(segment);
 | |
|         }
 | |
| 
 | |
|         #region Splitting.     
 | |
|         /// <summary>
 | |
|         /// Checks if a message can be split and outputs split information if so.
 | |
|         /// </summary>
 | |
|         private void SetSplitValues(byte channelId, ArraySegment<byte> segment, bool split, out int requiredSplitMessages, out int maxSplitMessageSize)
 | |
|         {
 | |
|             if (!split)
 | |
|             {
 | |
|                 requiredSplitMessages = 0;
 | |
|                 maxSplitMessageSize = 0;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 SplitRequired(channelId, segment.Count, out requiredSplitMessages, out maxSplitMessageSize);
 | |
|             }
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// True if data must be split.
 | |
|         /// </summary>
 | |
|         /// <param name="channelId"></param>
 | |
|         /// <param name="segmentSize"></param>
 | |
|         /// <returns></returns>
 | |
|         private bool SplitRequired(byte channelId, int segmentSize, out int requiredMessages, out int maxMessageSize)
 | |
|         {
 | |
|             maxMessageSize = GetLowestMTU(channelId) - (TransportManager.TICK_BYTES + SPLIT_INDICATOR_SIZE);
 | |
|             requiredMessages = Mathf.CeilToInt((float)segmentSize / maxMessageSize);
 | |
| 
 | |
|             return (requiredMessages > 1);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Splits data going to which is too large to fit within the transport MTU.
 | |
|         /// </summary>
 | |
|         /// <param name="conn">Connection to send to. If null data will be sent to the server.</param>
 | |
|         /// <returns>True if data was sent split.</returns>
 | |
|         private void SendSplitData(NetworkConnection conn, ref ArraySegment<byte> segment, int requiredMessages, int maxMessageSize)
 | |
|         {
 | |
|             if (requiredMessages <= 1)
 | |
|             {
 | |
|                 _networkManager.LogError($"SendSplitData was called with {requiredMessages} required messages. This method should only be called if messages must be split into 2 pieces or more.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             byte channelId = (byte)Channel.Reliable;
 | |
|             PooledWriter headerWriter = WriterPool.GetWriter();
 | |
|             headerWriter.WritePacketId(PacketId.Split);
 | |
|             headerWriter.WriteInt32(requiredMessages);
 | |
|             ArraySegment<byte> headerSegment = headerWriter.GetArraySegment();
 | |
| 
 | |
|             int writeIndex = 0;
 | |
|             bool firstWrite = true;
 | |
|             //Send to connection until everything is written.
 | |
|             while (writeIndex < segment.Count)
 | |
|             {
 | |
|                 int headerReduction = 0;
 | |
|                 if (firstWrite)
 | |
|                 {
 | |
|                     headerReduction = headerSegment.Count;
 | |
|                     firstWrite = false;
 | |
|                 }
 | |
|                 int chunkSize = Mathf.Min(segment.Count - writeIndex - headerReduction, maxMessageSize);
 | |
|                 //Make a new array segment for the chunk that is getting split.
 | |
|                 ArraySegment<byte> splitSegment = new ArraySegment<byte>(
 | |
|                     segment.Array, segment.Offset + writeIndex, chunkSize);
 | |
| 
 | |
|                 //If connection is specified then it's going to a client.
 | |
|                 if (conn != null)
 | |
|                 {
 | |
|                     conn.SendToClient(channelId, headerSegment, true);
 | |
|                     conn.SendToClient(channelId, splitSegment);
 | |
|                 }
 | |
|                 //Otherwise it's going to the server.
 | |
|                 else
 | |
|                 {
 | |
|                     _toServerBundles[channelId].Write(headerSegment, true);
 | |
|                     _toServerBundles[channelId].Write(splitSegment, false);
 | |
|                 }
 | |
| 
 | |
|                 writeIndex += chunkSize;
 | |
|             }
 | |
| 
 | |
|             headerWriter.Dispose();
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Processes data received by the socket.
 | |
|         /// </summary>
 | |
|         /// <param name="server">True to process data received on the server.</param>
 | |
|         internal void IterateIncoming(bool server)
 | |
|         {
 | |
|             OnIterateIncomingStart?.Invoke(server);
 | |
|             Transport.IterateIncoming(server);
 | |
|             OnIterateIncomingEnd?.Invoke(server);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Processes data to be sent by the socket.
 | |
|         /// </summary>
 | |
|         /// <param name="toServer">True to process data received on the server.</param>
 | |
|         internal void IterateOutgoing(bool toServer)
 | |
|         {
 | |
|             OnIterateOutgoingStart?.Invoke();
 | |
|             int channelCount = CHANNEL_COUNT;
 | |
|             ulong sentBytes = 0;
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|             bool latencySimulatorEnabled = LatencySimulator.CanSimulate;
 | |
| #endif
 | |
|             /* If sending to the client. */
 | |
|             if (!toServer)
 | |
|             {
 | |
|                 TimeManager tm = _networkManager.TimeManager;
 | |
|                 uint localTick = tm.LocalTick;
 | |
|                 //Write any dirty syncTypes.
 | |
|                 _networkManager.ServerManager.Objects.WriteDirtySyncTypes();
 | |
| 
 | |
|                 int dirtyCount = _dirtyToClients.Count;
 | |
| 
 | |
|                 //Run through all dirty connections to send data to.
 | |
|                 for (int z = 0; z < dirtyCount; z++)
 | |
|                 {
 | |
|                     NetworkConnection conn = _dirtyToClients[z];
 | |
|                     if (conn == null || !conn.IsValid)
 | |
|                         continue;
 | |
| 
 | |
|                     //Get packets for every channel.
 | |
|                     for (byte channel = 0; channel < channelCount; channel++)
 | |
|                     {
 | |
|                         if (conn.GetPacketBundle(channel, out PacketBundle pb))
 | |
|                         {
 | |
|                             for (int i = 0; i < pb.WrittenBuffers; i++)
 | |
|                             {
 | |
|                                 //Length should always be more than 0 but check to be safe.
 | |
|                                 if (pb.GetBuffer(i, out ByteBuffer bb))
 | |
|                                 {
 | |
|                                     ArraySegment<byte> segment = new ArraySegment<byte>(bb.Data, 0, bb.Length);
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|                                     if (latencySimulatorEnabled)
 | |
|                                         _latencySimulator.AddOutgoing(channel, segment, false, conn.ClientId);
 | |
|                                     else
 | |
| #endif
 | |
|                                         Transport.SendToClient(channel, segment, conn.ClientId);
 | |
|                                     sentBytes += (ulong)segment.Count;
 | |
|                                 }
 | |
|                             }
 | |
| 
 | |
|                             pb.Reset();
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     /* When marked as disconnecting data will still be sent
 | |
|                      * this iteration but the connection will be marked as invalid.
 | |
|                      * This will prevent future data from going out/coming in.
 | |
|                      * Also the connection will be added to a disconnecting collection
 | |
|                      * so it will it disconnected briefly later to allow data from
 | |
|                      * this tick to send. */
 | |
|                     if (conn.Disconnecting)
 | |
|                     {
 | |
|                         uint requiredTicks = tm.TimeToTicks(0.1d, TickRounding.RoundUp);
 | |
|                         /* Require 100ms or 2 ticks to pass
 | |
|                          * before disconnecting to allow for the
 | |
|                          * higher chance of success that remaining
 | |
|                          * data is sent. */
 | |
|                         requiredTicks = Math.Max(requiredTicks, 2);
 | |
|                         _disconnectingClients.Add(new DisconnectingClient(requiredTicks + localTick, conn));
 | |
|                     }
 | |
| 
 | |
|                     conn.ResetServerDirty();
 | |
|                 }
 | |
| 
 | |
|                 //Iterate disconnects.
 | |
|                 for (int i = 0; i < _disconnectingClients.Count; i++)
 | |
|                 {
 | |
|                     DisconnectingClient dc = _disconnectingClients[i];
 | |
|                     if (localTick >= dc.Tick)
 | |
|                     {
 | |
|                         _networkManager.TransportManager.Transport.StopConnection(dc.Connection.ClientId, true);
 | |
|                         _disconnectingClients.RemoveAt(i);
 | |
|                         i--;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 _networkManager.StatisticsManager.NetworkTraffic.LocalServerSentData(sentBytes);
 | |
| 
 | |
|                 if (dirtyCount == _dirtyToClients.Count)
 | |
|                     _dirtyToClients.Clear();
 | |
|                 else if (dirtyCount > 0)
 | |
|                     _dirtyToClients.RemoveRange(0, dirtyCount);
 | |
|             }
 | |
|             /* If sending to the server. */
 | |
|             else
 | |
|             {
 | |
|                 for (byte channel = 0; channel < channelCount; channel++)
 | |
|                 {
 | |
|                     if (PacketBundle.GetPacketBundle(channel, _toServerBundles, out PacketBundle pb))
 | |
|                     {
 | |
|                         for (int i = 0; i < pb.WrittenBuffers; i++)
 | |
|                         {
 | |
|                             if (pb.GetBuffer(i, out ByteBuffer bb))
 | |
|                             {
 | |
|                                 ArraySegment<byte> segment = new ArraySegment<byte>(bb.Data, 0, bb.Length);
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|                                 if (latencySimulatorEnabled)
 | |
|                                     _latencySimulator.AddOutgoing(channel, segment);
 | |
|                                 else
 | |
| #endif
 | |
|                                     Transport.SendToServer(channel, segment);
 | |
|                                 sentBytes += (ulong)segment.Count;
 | |
|                             }
 | |
|                         }
 | |
|                         pb.Reset();
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 _networkManager.StatisticsManager.NetworkTraffic.LocalClientSentData(sentBytes);
 | |
|             }
 | |
| 
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|             if (latencySimulatorEnabled)
 | |
|                 _latencySimulator.IterateOutgoing(toServer);
 | |
| #endif
 | |
| 
 | |
|             Transport.IterateOutgoing(toServer);
 | |
|             OnIterateOutgoingEnd?.Invoke();
 | |
|         }
 | |
| 
 | |
|         #region Editor.
 | |
| #if UNITY_EDITOR
 | |
|         private void OnValidate()
 | |
|         {
 | |
|             if (Transport == null)
 | |
|                 Transport = GetComponent<Transport>();
 | |
| 
 | |
|             /* Update enabled state to force a reset if needed.
 | |
|              * This may be required if the user checked the enabled
 | |
|              * tick box at runtime. If enabled value didn't change
 | |
|              * then the Get will be the same as the Set and nothing
 | |
|              * will happen. */
 | |
|             _latencySimulator.SetEnabled(_latencySimulator.GetEnabled());
 | |
|         }
 | |
| #endif
 | |
|         #endregion
 | |
| 
 | |
|     }
 | |
| 
 | |
| 
 | |
| } |