Add FishNet network library
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Transporting
|
||||
{
|
||||
/// <summary>
|
||||
/// When inherited from this may be used with the TransportManager to alter messages before they are sent and received.
|
||||
/// </summary>
|
||||
public abstract class IntermediateLayer : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// TransportManager associated with this script.
|
||||
/// </summary>
|
||||
public TransportManager TransportManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when data is received.
|
||||
/// </summary>
|
||||
/// <param name = "src">Original data.</param>
|
||||
/// <param name = "fromServer">True if receiving from the server, false if from a client.</param>
|
||||
/// <returns>Modified data.</returns>
|
||||
public abstract ArraySegment<byte> HandleIncoming(ArraySegment<byte> src, bool fromServer);
|
||||
|
||||
/// <summary>
|
||||
/// Called when data is sent.
|
||||
/// </summary>
|
||||
/// <param name = "src">Original data.</param>
|
||||
/// <param name = "toServer">True if sending to the server, false if to a client.</param>
|
||||
/// <returns>Modified data.</returns>
|
||||
public abstract ArraySegment<byte> HandleOutgoing(ArraySegment<byte> src, bool toServer);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this IntermediateLayer for use.
|
||||
/// </summary>
|
||||
public virtual void InitializeOnce(TransportManager manager) => TransportManager = manager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f8a2e0f9aaea614887f5f7b15350e46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.7.3R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Transporting/IntermediateLayer.cs
|
||||
uploadId: 987062
|
||||
@@ -0,0 +1,388 @@
|
||||
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
|
||||
{
|
||||
[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)
|
||||
{
|
||||
ConnectionId = connectionId;
|
||||
SendTime = Time.unscaledTime + latency;
|
||||
Length = segment.Count;
|
||||
Data = ByteArrayPool.Retrieve(Length);
|
||||
Buffer.BlockCopy(segment.Array, segment.Offset, Data, 0, Length);
|
||||
}
|
||||
|
||||
public ArraySegment<byte> GetSegment()
|
||||
{
|
||||
return new(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();
|
||||
/// <summary>
|
||||
/// Unreliable messages to the server.
|
||||
/// </summary>
|
||||
private List<Message> _toServerUnreliable = new();
|
||||
/// <summary>
|
||||
/// Reliable messages to clients.
|
||||
/// </summary>
|
||||
private List<Message> _toClientReliable = new();
|
||||
/// <summary>
|
||||
/// Unreliable messages to clients.
|
||||
/// </summary>
|
||||
private List<Message> _toClientUnreliable = new();
|
||||
/// <summary>
|
||||
/// NetworkManager for this instance.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
/// <summary>
|
||||
/// Used to generate chances of latency.
|
||||
/// </summary>
|
||||
private readonly System.Random _random = new();
|
||||
#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)
|
||||
{
|
||||
// If not enabled exit early to save work.
|
||||
if (!GetEnabled())
|
||||
return;
|
||||
|
||||
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.IsHostStarted)
|
||||
{
|
||||
/* 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(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 = "asServer">True to send data from the local server to clients, false to send from the local client to server.
|
||||
public void IterateOutgoing(bool asServer)
|
||||
{
|
||||
if (_transport == null)
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (asServer)
|
||||
{
|
||||
IterateCollection(_toClientReliable, Channel.Reliable);
|
||||
IterateCollection(_toClientUnreliable, Channel.Unreliable);
|
||||
}
|
||||
else
|
||||
{
|
||||
IterateCollection(_toServerReliable, Channel.Reliable);
|
||||
IterateCollection(_toServerUnreliable, 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 (asServer)
|
||||
_transport.SendToClient(cByte, msg.GetSegment(), msg.ConnectionId);
|
||||
else
|
||||
_transport.SendToServer(cByte, msg.GetSegment());
|
||||
|
||||
iterations++;
|
||||
}
|
||||
|
||||
if (iterations > 0)
|
||||
{
|
||||
for (int i = 0; i < iterations; i++)
|
||||
ByteArrayPool.Store(collection[i].Data);
|
||||
collection.RemoveRange(0, iterations);
|
||||
}
|
||||
}
|
||||
|
||||
_transport.IterateOutgoing(asServer);
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82bfafe804acb534fbf04c88de6eeed1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.7.3R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Transporting/LatencySimulator.cs
|
||||
uploadId: 987062
|
||||
@@ -0,0 +1,115 @@
|
||||
using FishNet.Serializing;
|
||||
using System;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Transporting
|
||||
{
|
||||
internal class SplitReader : IResettable
|
||||
{
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Writer containing the combined split packet.
|
||||
/// </summary>
|
||||
private readonly PooledWriter _writer = new();
|
||||
/// <summary>
|
||||
/// Expected number of splits.
|
||||
/// </summary>
|
||||
private int _expectedMessages;
|
||||
/// <summary>
|
||||
/// Number of splits received so far.
|
||||
/// </summary>
|
||||
private ushort _receivedMessages;
|
||||
/// <summary>
|
||||
/// The maximum allowed bytes which can be read. This acts as a guard against overflow.
|
||||
/// </summary>
|
||||
private uint _maximumClientBytes;
|
||||
/// <summary>
|
||||
/// NetworkManager for this.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
/// <summary>
|
||||
/// True if the sender of the split packet is a client.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool _isSenderClient;
|
||||
#endregion
|
||||
|
||||
public void Initialize(NetworkManager networkManager, uint maximumClientBytes, bool isSenderClient, int expectedMessages)
|
||||
{
|
||||
_networkManager = networkManager;
|
||||
_maximumClientBytes = maximumClientBytes;
|
||||
_isSenderClient = isSenderClient;
|
||||
_expectedMessages = expectedMessages;
|
||||
|
||||
|
||||
/* This is just a guess as to how large the end
|
||||
* message could be. If the writer is not the minimum
|
||||
* of this length then resize it. */
|
||||
int estimatedBufferSize = expectedMessages * 1500;
|
||||
if (_writer.Capacity < estimatedBufferSize)
|
||||
_writer.EnsureBufferCapacity(estimatedBufferSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines split data.
|
||||
/// </summary>
|
||||
internal bool Write(PooledReader reader)
|
||||
{
|
||||
if (_isSenderClient)
|
||||
{
|
||||
long totalBytes = _writer.Length + reader.Remaining;
|
||||
|
||||
if (totalBytes > _maximumClientBytes)
|
||||
{
|
||||
_networkManager.LogError($"A split packet of [{totalBytes}] exceeds the maximum allowed bytes of [{_maximumClientBytes}].");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Empty remainder of reader into the writer.
|
||||
* It does not matter if parts of the reader
|
||||
* contain data added after the split because
|
||||
* once the split is fully combined the data
|
||||
* is parsed as though it came in as one message,
|
||||
* which is how data is normally read. */
|
||||
ArraySegment<byte> data = reader.ReadArraySegment(reader.Remaining);
|
||||
_writer.WriteArraySegment(data);
|
||||
|
||||
|
||||
_receivedMessages++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if all split messages have been received.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool TryGetFullMessage(out ArraySegment<byte> segment)
|
||||
{
|
||||
if (_receivedMessages < _expectedMessages)
|
||||
{
|
||||
segment = ArraySegment<byte>.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
segment = _writer.GetArraySegment();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_writer.Clear();
|
||||
|
||||
_expectedMessages = 0;
|
||||
_receivedMessages = 0;
|
||||
_maximumClientBytes = 0;
|
||||
|
||||
_networkManager = null;
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1c06be1540b77842be35823aa54b19b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.7.3R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Transporting/SplitReader.cs
|
||||
uploadId: 987062
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Transporting.Multipass;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Transporting
|
||||
{
|
||||
/// <summary>
|
||||
/// Communicates with the Transport to send and receive data.
|
||||
/// </summary>
|
||||
public sealed partial class TransportManager : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns IsLocalTransport for the transportId, optionally checking against a connectionId.
|
||||
/// </summary>
|
||||
public bool IsLocalTransport(int transportId, int connectionId = NetworkConnection.UNSET_CLIENTID_VALUE)
|
||||
{
|
||||
if (Transport == null)
|
||||
return false;
|
||||
|
||||
if (Transport is Multipass mp)
|
||||
return mp.IsLocalTransport(transportId, connectionId);
|
||||
else
|
||||
return Transport.IsLocalTransport(connectionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets transport on index.
|
||||
/// Commonly index will be 0 unless using Multipass.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Transport GetTransport(int index)
|
||||
{
|
||||
// If using multipass try to find the correct transport.
|
||||
if (Transport is Multipass mp)
|
||||
{
|
||||
return mp.GetTransport(index);
|
||||
}
|
||||
// Not using multipass.
|
||||
else
|
||||
{
|
||||
return Transport;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets transport of type T.
|
||||
/// </summary>
|
||||
/// <returns>Returns the found transport which is of type T. Returns default of T if not found.</returns>
|
||||
public T GetTransport<T>() where T : Transport
|
||||
{
|
||||
// If using multipass try to find the correct transport.
|
||||
if (Transport is Multipass mp)
|
||||
{
|
||||
if (typeof(T) == typeof(Multipass))
|
||||
return (T)(object)mp;
|
||||
else
|
||||
return mp.GetTransport<T>();
|
||||
}
|
||||
// Not using multipass.
|
||||
else
|
||||
{
|
||||
if (Transport.GetType() == typeof(T))
|
||||
return (T)(object)Transport;
|
||||
else
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all transports configured on the TransportManager.
|
||||
/// </summary>
|
||||
/// <param name = "includeMultipass">True to add Multipass to the results if being used. When false and using Multipass only the transport specified within Multipass will be returned.</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>This returns a collection from cache.</remarks>
|
||||
public List<Transport> GetAllTransports(bool includeMultipass)
|
||||
{
|
||||
List<Transport> results = CollectionCaches<Transport>.RetrieveList();
|
||||
|
||||
// If using multipass check all transports.
|
||||
if (Transport is Multipass mp)
|
||||
{
|
||||
if (includeMultipass)
|
||||
results.Add(Transport);
|
||||
|
||||
|
||||
foreach (Transport t in mp.Transports)
|
||||
results.Add(t);
|
||||
}
|
||||
// Not using multipass.
|
||||
else
|
||||
{
|
||||
results.Add(Transport);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aac8eab4e511d7e4dbc81eb74aea7f23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.7.3R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Transporting/TransportManager.QOL.cs
|
||||
uploadId: 987062
|
||||
@@ -0,0 +1,829 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Transporting.Multipass;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Managing.Statistic;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
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>
|
||||
/// Returns if an IntermediateLayer is in use.
|
||||
/// </summary>
|
||||
public bool HasIntermediateLayer => _intermediateLayer != null;
|
||||
/// <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>
|
||||
/// The maximum amount of bytes of any combined packet that a client may send.
|
||||
/// </summary>
|
||||
public uint MaximumClientPacketSize => _maximumClientPacketSize;
|
||||
[Tooltip("The maximum amount of bytes of any combined packet that a client may send.")]
|
||||
[SerializeField]
|
||||
private uint _maximumClientPacketSize = 20480;
|
||||
/// <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();
|
||||
/// <summary>
|
||||
/// Latency simulation settings.
|
||||
/// </summary>
|
||||
public LatencySimulator LatencySimulator
|
||||
{
|
||||
get
|
||||
{
|
||||
// Shouldn't ever be null unless the user nullifies it.
|
||||
if (_latencySimulator == null)
|
||||
_latencySimulator = new();
|
||||
return _latencySimulator;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// NetworkConnections on the server which have to send data to clients.
|
||||
/// </summary>
|
||||
private List<NetworkConnection> _dirtyToClients = new();
|
||||
/// <summary>
|
||||
/// PacketBundles to send to the server.
|
||||
/// </summary>
|
||||
private List<PacketBundle> _toServerBundles = new();
|
||||
/// <summary>
|
||||
/// NetworkManager handling this TransportManager.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
/// <summary>
|
||||
/// Clients which are pending disconnects.
|
||||
/// </summary>
|
||||
private List<DisconnectingClient> _disconnectingClients = new();
|
||||
/// <summary>
|
||||
/// Lowest MTU of all transports for channels.
|
||||
/// </summary>
|
||||
private int[] _lowestMtus;
|
||||
/// <summary>
|
||||
/// Lowest MTU of all transports of all channels.
|
||||
/// </summary>
|
||||
private int _lowestMtu = 0;
|
||||
/// <summary>
|
||||
/// Custom amount to reserve on the MTU.
|
||||
/// </summary>
|
||||
private int _customMtuReserve = MINIMUM_MTU_RESERVE;
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
private NetworkTrafficStatistics _networkTrafficStatistics;
|
||||
/// <summary>
|
||||
/// Maximum size which each segment of a split message can be.
|
||||
/// </summary>
|
||||
private int _maximumSplitPacketSegmentLength => GetLowestMTU(SPLIT_PACKET_CHANNELID) - SPLIT_PACKET_HEADER_LENGTH - UNPACKED_TICK_LENGTH;
|
||||
#endregion
|
||||
|
||||
#region Consts.
|
||||
/// <summary>
|
||||
/// Number of bytes sent for PacketId.
|
||||
/// </summary>
|
||||
public const byte PACKETID_LENGTH = 2;
|
||||
/// <summary>
|
||||
/// Number of bytes sent for ObjectId.
|
||||
/// </summary>
|
||||
public const byte OBJECT_ID_LENGTH = 2;
|
||||
/// <summary>
|
||||
/// Number of bytes sent for ComponentIndex.
|
||||
/// </summary>
|
||||
public const byte COMPONENT_INDEX_LENGTH = 1;
|
||||
/// <summary>
|
||||
/// Number of bytes sent for Tick.
|
||||
/// </summary>
|
||||
public const byte UNPACKED_TICK_LENGTH = 4;
|
||||
/// <summary>
|
||||
/// Number of bytes sent for an unpacked size, such as a collection or array size.
|
||||
/// </summary>
|
||||
public const byte UNPACKED_SIZE_LENGTH = 4;
|
||||
/// <summary>
|
||||
/// Number of bytes sent to indicate split count.
|
||||
/// </summary>
|
||||
private const byte SPLIT_COUNT_LENGTH = 4;
|
||||
/// <summary>
|
||||
/// Number of bytes required for split data.
|
||||
/// </summary>
|
||||
public const byte SPLIT_PACKET_HEADER_LENGTH = PACKETID_LENGTH + SPLIT_COUNT_LENGTH;
|
||||
/// <summary>
|
||||
/// Number of channels supported.
|
||||
/// </summary>
|
||||
public const byte CHANNEL_COUNT = 2;
|
||||
/// <summary>
|
||||
/// MTU reserved for internal use.
|
||||
/// 1 byte is used to specify channel in packets for transports that do not include channel within their packet header. This is transport dependent.
|
||||
/// </summary>
|
||||
public const int MINIMUM_MTU_RESERVE = 1;
|
||||
/// <summary>
|
||||
/// Value to use when a MTU could not be found.
|
||||
/// </summary>
|
||||
public const int INVALID_MTU = -1;
|
||||
/// <summary>
|
||||
/// A split message was not required, the value can be sent normally.
|
||||
/// </summary>
|
||||
private const int SPLIT_NOT_REQUIRED_VALUE = 0;
|
||||
/// <summary>
|
||||
/// A message was sent split.
|
||||
/// </summary>
|
||||
private const int SPLIT_SENT_VALUE = 1;
|
||||
/// <summary>
|
||||
/// An error occurred while trying to split a message.
|
||||
/// </summary>
|
||||
private const int SPLIT_ERROR_VALUE = 2;
|
||||
/// <summary>
|
||||
/// ChannelId to use for split packets.
|
||||
/// </summary>
|
||||
private const byte SPLIT_PACKET_CHANNELID = (byte)Channel.Reliable;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this script for use.
|
||||
/// </summary>
|
||||
internal void InitializeOnce_Internal(NetworkManager manager)
|
||||
{
|
||||
_networkManager = manager;
|
||||
TryAddDefaultTransport();
|
||||
Transport.Initialize(_networkManager, 0);
|
||||
SetLowestMTUs();
|
||||
InitializeToServerBundles();
|
||||
|
||||
manager.StatisticsManager.TryGetNetworkTrafficStatistics(out _networkTrafficStatistics);
|
||||
|
||||
manager.ServerManager.OnServerConnectionState += ServerManager_OnServerConnectionState;
|
||||
manager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
|
||||
|
||||
if (_intermediateLayer != null)
|
||||
_intermediateLayer.InitializeOnce(this);
|
||||
#if DEVELOPMENT
|
||||
_latencySimulator.Initialize(manager, Transport);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the lowest MTU values.
|
||||
/// </summary>
|
||||
private void SetLowestMTUs()
|
||||
{
|
||||
// Already set.
|
||||
if (_lowestMtu != 0)
|
||||
return;
|
||||
|
||||
/* At least one transport is required.
|
||||
* Try to add default. If a transport is already
|
||||
* specified the add method will just exit early. */
|
||||
TryAddDefaultTransport();
|
||||
|
||||
int allLowest = int.MaxValue;
|
||||
// Cache lowest Mtus.
|
||||
_lowestMtus = new int[CHANNEL_COUNT];
|
||||
for (byte i = 0; i < CHANNEL_COUNT; i++)
|
||||
{
|
||||
int channelLowest = int.MaxValue;
|
||||
if (Transport is Multipass mp)
|
||||
{
|
||||
foreach (Transport t in mp.Transports)
|
||||
{
|
||||
int mtu = t.GetMTU(i);
|
||||
if (mtu != INVALID_MTU)
|
||||
channelLowest = Mathf.Min(channelLowest, mtu);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
channelLowest = Transport.GetMTU(i);
|
||||
}
|
||||
|
||||
_lowestMtus[i] = channelLowest;
|
||||
_lowestMtu = Mathf.Min(allLowest, channelLowest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the default transport if a transport is not yet specified.
|
||||
/// </summary>
|
||||
private void TryAddDefaultTransport()
|
||||
{
|
||||
if (Transport == null && !gameObject.TryGetComponent(out Transport))
|
||||
Transport = gameObject.AddComponent<FishNet.Transporting.Tugboat.Tugboat>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the local connection state changes for the client.
|
||||
/// </summary>
|
||||
private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj)
|
||||
{
|
||||
// Not stopped.
|
||||
if (obj.ConnectionState != LocalConnectionState.Stopped)
|
||||
return;
|
||||
|
||||
// Reset toServer data.
|
||||
foreach (PacketBundle pb in _toServerBundles)
|
||||
pb.Reset(resetSendLast: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the local connection state changes for the server.
|
||||
/// </summary>
|
||||
private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs obj)
|
||||
{
|
||||
// Not stopped.
|
||||
if (obj.ConnectionState != LocalConnectionState.Stopped)
|
||||
return;
|
||||
|
||||
// If no server is started just clear all dirtyToClients.
|
||||
if (!_networkManager.ServerManager.IsAnyServerStarted())
|
||||
{
|
||||
_dirtyToClients.Clear();
|
||||
_disconnectingClients.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Only one server is stopped, remove connections for that server.
|
||||
int index = obj.TransportIndex;
|
||||
|
||||
//Remove any disconnecting clients for the stopped transport.
|
||||
for (int i = 0; i < _disconnectingClients.Count; i++)
|
||||
{
|
||||
DisconnectingClient dc = _disconnectingClients[i];
|
||||
|
||||
if (dc.Connection == null || dc.Connection.TransportIndex == index)
|
||||
{
|
||||
_disconnectingClients.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove any dirty connection indicators for the transport.
|
||||
for (int i = 0; i < _dirtyToClients.Count; i++)
|
||||
{
|
||||
NetworkConnection connection = _dirtyToClients[i];
|
||||
|
||||
if (connection == null || connection.TransportIndex == index)
|
||||
{
|
||||
_dirtyToClients.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(_networkManager, mtu));
|
||||
}
|
||||
}
|
||||
|
||||
#region GetMTU.
|
||||
/// <summary>
|
||||
/// Returns MTU excluding reserve amount.
|
||||
/// </summary>
|
||||
private int GetMTUWithReserve(int mtu)
|
||||
{
|
||||
int value = mtu - MINIMUM_MTU_RESERVE - _customMtuReserve;
|
||||
/* If MTU is extremely low then warn user.
|
||||
* The number choosen has no significant value. */
|
||||
if (value <= 100)
|
||||
{
|
||||
string msg = $"Available MTU of {mtu} is significantly low; an invalid MTU will be returned. Check transport settings, or reduce MTU reserve if you set one using {nameof(SetMTUReserve)}";
|
||||
_networkManager.LogWarning(msg);
|
||||
|
||||
return INVALID_MTU;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom value to reserve for the internal buffers.
|
||||
/// This value is also deducted from transport MTU when using GetMTU methods.
|
||||
/// </summary>
|
||||
/// <param name = "value">Value to use.</param>
|
||||
public void SetMTUReserve(int value)
|
||||
{
|
||||
if ((_networkManager != null && _networkManager.IsClientStarted) || _networkManager.IsServerStarted)
|
||||
{
|
||||
_networkManager.LogError($"A custom MTU reserve cannot be set after the server or client have been started or connected.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value < MINIMUM_MTU_RESERVE)
|
||||
{
|
||||
_networkManager.Log($"MTU reserve {value} is below minimum value of {MINIMUM_MTU_RESERVE}. Value has been updated to {MINIMUM_MTU_RESERVE}.");
|
||||
value = MINIMUM_MTU_RESERVE;
|
||||
}
|
||||
|
||||
_customMtuReserve = value;
|
||||
InitializeToServerBundles();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current MTU reserve.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetMTUReserve() => _customMtuReserve;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the lowest MTU of all channels. When using multipass this will evaluate all transports within Multipass.
|
||||
/// </summary>
|
||||
/// <param name = "channel"></param>
|
||||
/// <returns></returns>
|
||||
public int GetLowestMTU()
|
||||
{
|
||||
SetLowestMTUs();
|
||||
return GetMTUWithReserve(_lowestMtu);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public int GetLowestMTU(byte channel)
|
||||
{
|
||||
SetLowestMTUs();
|
||||
|
||||
return GetMTUWithReserve(_lowestMtus[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)
|
||||
{
|
||||
SetLowestMTUs();
|
||||
|
||||
int mtu = Transport.GetMTU(channel);
|
||||
if (mtu == INVALID_MTU)
|
||||
return mtu;
|
||||
|
||||
return GetMTUWithReserve(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>
|
||||
public int GetMTU(int transportIndex, byte channel)
|
||||
{
|
||||
if (Transport is Multipass mp)
|
||||
{
|
||||
int mtu = mp.GetMTU(channel, transportIndex);
|
||||
if (mtu == INVALID_MTU)
|
||||
return INVALID_MTU;
|
||||
|
||||
return GetMTUWithReserve(mtu);
|
||||
}
|
||||
// Using first/only transport.
|
||||
if (transportIndex == 0)
|
||||
return GetMTU(channel);
|
||||
|
||||
// Unhandled.
|
||||
_networkManager.LogWarning($"MTU cannot be returned with transportIndex because {typeof(Multipass).Name} is not in use.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Channel.Reliable if data length is over MTU for the provided channel.
|
||||
/// </summary>
|
||||
public Channel GetReliableChannelIfOverMTU(int dataLength, Channel currentChannel) => dataLength > GetMTU((byte)currentChannel) ? Channel.Reliable : currentChannel;
|
||||
|
||||
/// <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>
|
||||
public int GetMTU<T>(byte channel) where T : Transport
|
||||
{
|
||||
Transport transport = GetTransport<T>();
|
||||
if (transport != null)
|
||||
{
|
||||
int mtu = transport.GetMTU(channel);
|
||||
if (mtu == INVALID_MTU)
|
||||
return mtu;
|
||||
|
||||
return GetMTUWithReserve(mtu);
|
||||
}
|
||||
|
||||
// Fall through.
|
||||
return INVALID_MTU;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Passes received to the intermediate layer.
|
||||
/// </summary>
|
||||
internal ArraySegment<byte> ProcessIntermediateIncoming(ArraySegment<byte> src, bool fromServer)
|
||||
{
|
||||
return _intermediateLayer.HandleIncoming(src, fromServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Passes sent to the intermediate layer.
|
||||
/// </summary>
|
||||
private ArraySegment<byte> ProcessIntermediateOutgoing(ArraySegment<byte> src, bool toServer)
|
||||
{
|
||||
return _intermediateLayer.HandleOutgoing(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>
|
||||
internal void SendToClient(byte channelId, ArraySegment<byte> segment, NetworkConnection connection, DataOrderType orderType = DataOrderType.Default)
|
||||
{
|
||||
channelId = GetFallbackChannelIdAsNeeded(channelId);
|
||||
|
||||
if (SendSplitMessage(connection, channelId, segment, orderType) == SPLIT_NOT_REQUIRED_VALUE)
|
||||
connection.SendToClient(channelId, segment, forceNewBuffer: false, orderType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends data to observers.
|
||||
/// </summary>
|
||||
internal void SendToClients(byte channelId, ArraySegment<byte> segment, HashSet<NetworkConnection> observers, HashSet<NetworkConnection> excludedConnections = null, DataOrderType orderType = DataOrderType.Default)
|
||||
{
|
||||
if (excludedConnections == null || excludedConnections.Count == 0)
|
||||
{
|
||||
foreach (NetworkConnection conn in observers)
|
||||
SendToClient(channelId, segment, conn, orderType);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (NetworkConnection conn in observers)
|
||||
{
|
||||
if (excludedConnections.Contains(conn))
|
||||
continue;
|
||||
|
||||
SendToClient(channelId, segment, conn, orderType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
internal void SendToClients(byte channelId, ArraySegment<byte> segment)
|
||||
{
|
||||
/* 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(channelId, segment, conn);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
internal void SendToServer(byte channelId, ArraySegment<byte> segment, DataOrderType orderType = DataOrderType.Default)
|
||||
{
|
||||
channelId = GetFallbackChannelIdAsNeeded(channelId);
|
||||
|
||||
if (SendSplitMessage(conn: null, channelId, segment, orderType) == SPLIT_NOT_REQUIRED_VALUE)
|
||||
_toServerBundles[channelId].Write(segment, forceNewBuffer: false, orderType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channelId to use, returning a fallback Id if the provided channelId is not supported.
|
||||
/// </summary>
|
||||
private byte GetFallbackChannelIdAsNeeded(byte channelId) => channelId > _toServerBundles.Count ? (byte)Channel.Reliable : channelId;
|
||||
|
||||
/// <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 int SendSplitMessage(NetworkConnection conn, byte channelId, ArraySegment<byte> segment, DataOrderType orderType)
|
||||
{
|
||||
int lowestMTU = GetLowestMTU(channelId);
|
||||
int segmentCount = segment.Count;
|
||||
|
||||
//Splitting is not required.
|
||||
if (segmentCount <= lowestMTU)
|
||||
//0 indicates no split required.
|
||||
return SPLIT_NOT_REQUIRED_VALUE;
|
||||
|
||||
int maximumSegmentLength = _maximumSplitPacketSegmentLength;
|
||||
int messageCount = (int)Math.Ceiling((double)segmentCount / maximumSegmentLength);
|
||||
|
||||
/* If going to the server and value exceeds the
|
||||
* maximum segment size then the data cannot be sent. */
|
||||
if (conn == null && messageCount * maximumSegmentLength > _maximumClientPacketSize)
|
||||
{
|
||||
_networkManager.LogError($"A packet of length {segmentCount} cannot be sent because it exceeds the maximum packet size allowed by a client of {_maximumClientPacketSize}.");
|
||||
return SPLIT_ERROR_VALUE;
|
||||
}
|
||||
|
||||
//Writer used to write the header and segment of each split message.
|
||||
PooledWriter splitWriter = WriterPool.Retrieve();
|
||||
|
||||
//Channel is forced to reliable for split messages.
|
||||
channelId = SPLIT_PACKET_CHANNELID;
|
||||
|
||||
for (int i = 0; i < messageCount; i++)
|
||||
{
|
||||
splitWriter.WritePacketIdUnpacked(PacketId.Split);
|
||||
splitWriter.WriteInt32(messageCount);
|
||||
|
||||
int startPosition = i * maximumSegmentLength;
|
||||
|
||||
int chunkSize = Mathf.Min(segment.Count - startPosition, maximumSegmentLength);
|
||||
ArraySegment<byte> splitSegment = new(segment.Array, segment.Offset + startPosition, chunkSize);
|
||||
splitWriter.WriteArraySegment(splitSegment);
|
||||
|
||||
// If connection is specified then it's going to a client.
|
||||
if (conn != null)
|
||||
conn.SendToClient(channelId, splitWriter.GetArraySegment());
|
||||
// Otherwise it's going to the server.
|
||||
else
|
||||
_toServerBundles[channelId].Write(splitWriter.GetArraySegment(), forceNewBuffer: false, orderType);
|
||||
|
||||
splitWriter.Clear();
|
||||
}
|
||||
|
||||
WriterPool.Store(splitWriter);
|
||||
|
||||
return SPLIT_SENT_VALUE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes data received by the socket.
|
||||
/// </summary>
|
||||
/// <param name = "asServer">True to read data from clients, false to read data from the server.
|
||||
internal void IterateIncoming(bool asServer)
|
||||
{
|
||||
OnIterateIncomingStart?.Invoke(asServer);
|
||||
Transport.IterateIncoming(asServer);
|
||||
OnIterateIncomingEnd?.Invoke(asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes data to be sent by the socket.
|
||||
/// </summary>
|
||||
/// <param name = "asServer">True to send data from the local server to clients, false to send from the local client to server.
|
||||
internal void IterateOutgoing(bool asServer)
|
||||
{
|
||||
if (asServer && _networkManager.ServerManager.AreAllServersStopped())
|
||||
return;
|
||||
|
||||
OnIterateOutgoingStart?.Invoke();
|
||||
int channelCount = CHANNEL_COUNT;
|
||||
ulong sentBytes = 0;
|
||||
#if DEVELOPMENT
|
||||
bool latencySimulatorEnabled = LatencySimulator.CanSimulate;
|
||||
#endif
|
||||
if (asServer)
|
||||
SendAsServer();
|
||||
else
|
||||
SendAsClient();
|
||||
|
||||
// Sends data as server.
|
||||
void SendAsServer()
|
||||
{
|
||||
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))
|
||||
{
|
||||
ProcessPacketBundle(pb);
|
||||
ProcessPacketBundle(pb.GetSendLastBundle(), true);
|
||||
|
||||
void ProcessPacketBundle(PacketBundle ppb, bool isLast = false)
|
||||
{
|
||||
for (int i = 0; i < ppb.WrittenBuffers; i++)
|
||||
{
|
||||
// Length should always be more than 0 but check to be safe.
|
||||
if (ppb.GetBuffer(i, out ByteBuffer bb))
|
||||
{
|
||||
ArraySegment<byte> segment = new(bb.Data, 0, bb.Length);
|
||||
if (HasIntermediateLayer)
|
||||
segment = ProcessIntermediateOutgoing(segment, false);
|
||||
#if DEVELOPMENT
|
||||
if (latencySimulatorEnabled)
|
||||
_latencySimulator.AddOutgoing(channel, segment, false, conn.ClientId);
|
||||
else
|
||||
#endif
|
||||
Transport.SendToClient(channel, segment, conn.ClientId);
|
||||
sentBytes += (ulong)segment.Count;
|
||||
}
|
||||
}
|
||||
|
||||
ppb.Reset(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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(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--;
|
||||
}
|
||||
}
|
||||
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddOutboundSocketData(sentBytes, asServer: true);
|
||||
|
||||
if (dirtyCount == _dirtyToClients.Count)
|
||||
_dirtyToClients.Clear();
|
||||
else if (dirtyCount > 0)
|
||||
_dirtyToClients.RemoveRange(0, dirtyCount);
|
||||
}
|
||||
|
||||
// Sends data as client.
|
||||
void SendAsClient()
|
||||
{
|
||||
for (byte channel = 0; channel < channelCount; channel++)
|
||||
{
|
||||
if (PacketBundle.GetPacketBundle(channel, _toServerBundles, out PacketBundle pb))
|
||||
{
|
||||
ProcessPacketBundle(pb);
|
||||
ProcessPacketBundle(pb.GetSendLastBundle());
|
||||
|
||||
void ProcessPacketBundle(PacketBundle ppb)
|
||||
{
|
||||
for (int i = 0; i < ppb.WrittenBuffers; i++)
|
||||
{
|
||||
if (ppb.GetBuffer(i, out ByteBuffer bb))
|
||||
{
|
||||
ArraySegment<byte> segment = new(bb.Data, 0, bb.Length);
|
||||
if (HasIntermediateLayer)
|
||||
segment = ProcessIntermediateOutgoing(segment, true);
|
||||
#if DEVELOPMENT
|
||||
if (latencySimulatorEnabled)
|
||||
_latencySimulator.AddOutgoing(channel, segment);
|
||||
else
|
||||
#endif
|
||||
Transport.SendToServer(channel, segment);
|
||||
sentBytes += (ulong)segment.Count;
|
||||
}
|
||||
}
|
||||
|
||||
ppb.Reset(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddOutboundSocketData(sentBytes, asServer: false);
|
||||
}
|
||||
|
||||
#if DEVELOPMENT
|
||||
if (latencySimulatorEnabled)
|
||||
_latencySimulator.IterateOutgoing(asServer);
|
||||
#endif
|
||||
|
||||
Transport.IterateOutgoing(asServer);
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34e4a322dca349547989b14021da4e23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.7.3R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs
|
||||
uploadId: 987062
|
||||
Reference in New Issue
Block a user