Update FishNet

This commit is contained in:
2026-04-07 03:11:52 +07:00
parent 9675b7b31d
commit ba7513d478
869 changed files with 3675 additions and 2764 deletions
@@ -13,6 +13,6 @@ AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
packageVersion: 4.7.1R
assetPath: Assets/FishNet/Runtime/Managing/Transporting/IntermediateLayer.cs
uploadId: 866910
uploadId: 892096
@@ -13,6 +13,6 @@ AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
packageVersion: 4.7.1R
assetPath: Assets/FishNet/Runtime/Managing/Transporting/LatencySimulator.cs
uploadId: 866910
uploadId: 892096
@@ -1,17 +1,18 @@
using FishNet.Serializing;
using System;
using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Managing.Transporting
{
internal class SplitReader
internal class SplitReader : IResettable
{
#region Private.
/// <summary>
/// Tick split is for.
/// Tick must be a negative value so that it's impossible for the first tick to align.
/// Writer containing the combined split packet.
/// </summary>
private long _tick = -1;
private readonly PooledWriter _writer = new();
/// <summary>
/// Expected number of splits.
/// </summary>
@@ -21,40 +22,52 @@ namespace FishNet.Managing.Transporting
/// </summary>
private ushort _receivedMessages;
/// <summary>
/// Writer containing split packet combined.
/// The maximum allowed bytes which can be read. This acts as a guard against overflow.
/// </summary>
private PooledWriter _writer = WriterPool.Retrieve();
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
internal SplitReader()
public void Initialize(NetworkManager networkManager, uint maximumClientBytes, bool isSenderClient, int expectedMessages)
{
// Increase capacity to reduce the chance of resizing.
_writer.EnsureBufferCapacity(20000);
}
/// <summary>
/// Gets split header values.
/// </summary>
internal void GetHeader(PooledReader reader, out int expectedMessages)
{
expectedMessages = reader.ReadInt32();
}
/// <summary>
/// Combines split data.
/// </summary>
internal void Write(uint tick, PooledReader reader, int expectedMessages)
{
// New tick which means new split.
if (tick != _tick)
Reset(tick, 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
@@ -63,33 +76,40 @@ namespace FishNet.Managing.Transporting
* 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 ArraySegment<byte> GetFullMessage()
internal bool TryGetFullMessage(out ArraySegment<byte> segment)
{
if (_receivedMessages < _expectedMessages)
{
return default;
}
else
{
ArraySegment<byte> segment = _writer.GetArraySegment();
Reset();
return segment;
segment = ArraySegment<byte>.Empty;
return false;
}
segment = _writer.GetArraySegment();
return true;
}
private void Reset(uint tick = 0, int expectedMessages = 0)
public void ResetState()
{
_tick = tick;
_receivedMessages = 0;
_expectedMessages = expectedMessages;
_writer.Clear();
_expectedMessages = 0;
_receivedMessages = 0;
_maximumClientBytes = 0;
_networkManager = null;
}
public void InitializeState() { }
}
}
@@ -13,6 +13,6 @@ AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
packageVersion: 4.7.1R
assetPath: Assets/FishNet/Runtime/Managing/Transporting/SplitReader.cs
uploadId: 866910
uploadId: 892096
@@ -13,6 +13,6 @@ AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
packageVersion: 4.7.1R
assetPath: Assets/FishNet/Runtime/Managing/Transporting/TransportManager.QOL.cs
uploadId: 866910
uploadId: 892096
@@ -12,6 +12,7 @@ using System.Collections.Generic;
using FishNet.Managing.Statistic;
using GameKit.Dependencies.Utilities;
using UnityEngine;
using UnityEngine.Serialization;
namespace FishNet.Managing.Transporting
{
@@ -66,6 +67,13 @@ namespace FishNet.Managing.Transporting
#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.")]
@@ -123,6 +131,10 @@ namespace FishNet.Managing.Transporting
/// <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.
@@ -153,8 +165,7 @@ namespace FishNet.Managing.Transporting
/// <summary>
/// Number of bytes required for split data.
/// </summary>
/// // todo: This shouldn't have to include TickBytes but there is a parse error if it's not included. Figure out why.
public const byte SPLIT_INDICATOR_LENGTH = UNPACKED_TICK_LENGTH + PACKETID_LENGTH + SPLIT_COUNT_LENGTH;
public const byte SPLIT_PACKET_HEADER_LENGTH = PACKETID_LENGTH + SPLIT_COUNT_LENGTH;
/// <summary>
/// Number of channels supported.
/// </summary>
@@ -168,6 +179,22 @@ namespace FishNet.Managing.Transporting
/// 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>
@@ -188,9 +215,9 @@ namespace FishNet.Managing.Transporting
if (_intermediateLayer != null)
_intermediateLayer.InitializeOnce(this);
#if DEVELOPMENT
#if DEVELOPMENT
_latencySimulator.Initialize(manager, Transport);
#endif
#endif
}
/// <summary>
@@ -287,75 +314,6 @@ namespace FishNet.Managing.Transporting
CollectionCaches<NetworkConnection>.Store(clientsForIndex);
}
///// <summary>
///// Gets port for the first transport, or client transport if using Multipass.
///// </summary>
// private ushort GetPort(bool asServer)
// {
// if (Transport is Multipass mp)
// {
// if (asServer)
// return mp.Transports[0].GetPort();
// else
// return mp.ClientTransport.GetPort();
// }
// else
// {
// return Transport.GetPort();
// }
// }
///// <summary>
///// Stops the local server or client connection.
///// </summary>
// internal bool StopConnection(bool asServer)
// {
// return Transport.StopConnection(asServer);
// }
///// <summary>
///// Starts the local server or client connection.
///// </summary>
// internal bool StartConnection(bool asServer)
// {
// return Transport.StartConnection(asServer);
// }
///// <summary>
///// Starts the local server or client connection.
///// </summary>
// internal bool StartConnection(string address, bool asServer)
// {
// return StartConnection(address, GetPort(asServer), asServer);
// }
///// <summary>
///// Starts the local server or client connection on the first transport or ClientTransport if using Multipass and as client.
///// </summary>
// internal bool StartConnection(string address, ushort port, bool asServer)
// {
// Transport t;
// if (Transport is Multipass mp)
// {
// if (asServer)
// t = mp.Transports[0];
// else
// t = mp.ClientTransport;
// }
// else
// {
// t = Transport;
// }
// /* SetServerBindAddress must be called explictly. Only
// * set address if for client. */
// if (!asServer)
// t.SetClientAddress(address);
// t.SetPort(port);
// return t.StartConnection(asServer);
// }
/// <summary>
/// Sets a connection from server to client dirty.
/// </summary>
@@ -449,6 +407,7 @@ namespace FishNet.Managing.Transporting
public int GetLowestMTU(byte channel)
{
SetLowestMTUs();
return GetMTUWithReserve(_lowestMtus[channel]);
}
@@ -460,6 +419,7 @@ namespace FishNet.Managing.Transporting
public int GetMTU(byte channel)
{
SetLowestMTUs();
int mtu = Transport.GetMTU(channel);
if (mtu == INVALID_MTU)
return mtu;
@@ -484,18 +444,19 @@ namespace FishNet.Managing.Transporting
return GetMTUWithReserve(mtu);
}
// Using first/only transport.
else if (transportIndex == 0)
{
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;
}
_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>
@@ -542,38 +503,23 @@ namespace FishNet.Managing.Transporting
/// <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, bool splitLargeMessages = true, DataOrderType orderType = DataOrderType.Default)
internal void SendToClient(byte channelId, ArraySegment<byte> segment, NetworkConnection connection, DataOrderType orderType = DataOrderType.Default)
{
SetSplitValues(channelId, segment, splitLargeMessages, out int requiredMessages, out int maxSplitMessageSize);
SendToClient(channelId, segment, connection, requiredMessages, maxSplitMessageSize, orderType);
}
channelId = GetFallbackChannelIdAsNeeded(channelId);
private void SendToClient(byte channelId, ArraySegment<byte> segment, NetworkConnection connection, int requiredSplitMessages, int maxSplitMessageSize, DataOrderType orderType = DataOrderType.Default)
{
if (connection == null)
return;
if (requiredSplitMessages > 1)
SendSplitData(connection, ref segment, requiredSplitMessages, maxSplitMessageSize, orderType);
else
connection.SendToClient(channelId, segment, false, orderType);
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, bool splitLargeMessages = true, DataOrderType orderType = DataOrderType.Default)
{
SetSplitValues(channelId, segment, splitLargeMessages, out int requiredMessages, out int maxSplitMessageSize);
SendToClients(channelId, segment, observers, excludedConnections, requiredMessages, maxSplitMessageSize, orderType);
}
private void SendToClients(byte channelId, ArraySegment<byte> segment, HashSet<NetworkConnection> observers, HashSet<NetworkConnection> excludedConnections, int requiredSplitMessages, int maxSplitMessageSize, DataOrderType orderType = DataOrderType.Default)
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, requiredSplitMessages, maxSplitMessageSize, orderType);
SendToClient(channelId, segment, conn, orderType);
}
else
{
@@ -581,7 +527,8 @@ namespace FishNet.Managing.Transporting
{
if (excludedConnections.Contains(conn))
continue;
SendToClient(channelId, segment, conn, requiredSplitMessages, maxSplitMessageSize, orderType);
SendToClient(channelId, segment, conn, orderType);
}
}
}
@@ -592,20 +539,14 @@ namespace FishNet.Managing.Transporting
/// <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, bool splitLargeMessages = true)
{
SetSplitValues(channelId, segment, splitLargeMessages, out int requiredMessages, out int maxSplitMessageSize);
SendToClients_Internal(channelId, segment, requiredMessages, maxSplitMessageSize);
}
private void SendToClients_Internal(byte channelId, ArraySegment<byte> segment, int requiredSplitMessages, int maxSplitMessageSize)
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, requiredSplitMessages, maxSplitMessageSize);
SendToClient(channelId, segment, conn);
}
/// <summary>
@@ -614,131 +555,76 @@ namespace FishNet.Managing.Transporting
/// <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, bool splitLargeMessages = true, DataOrderType orderType = DataOrderType.Default)
internal void SendToServer(byte channelId, ArraySegment<byte> segment, DataOrderType orderType = DataOrderType.Default)
{
SetSplitValues(channelId, segment, splitLargeMessages, out int requiredMessages, out int maxSplitMessageSize);
SendToServer(channelId, segment, requiredMessages, maxSplitMessageSize, orderType);
}
channelId = GetFallbackChannelIdAsNeeded(channelId);
private void SendToServer(byte channelId, ArraySegment<byte> segment, int requiredMessages, int maxSplitMessageSize, DataOrderType orderType)
{
if (channelId >= _toServerBundles.Count)
channelId = (byte)Channel.Reliable;
if (requiredMessages > 1)
SendSplitData(null, ref segment, requiredMessages, maxSplitMessageSize, orderType);
else
_toServerBundles[channelId].Write(segment, false, orderType);
}
#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 requiredMessages, out int maxSplitMessageSize)
{
if (!split)
{
requiredMessages = 0;
maxSplitMessageSize = 0;
}
else
{
SplitRequired(channelId, segment.Count, out requiredMessages, out maxSplitMessageSize);
}
if (SendSplitMessage(conn: null, channelId, segment, orderType) == SPLIT_NOT_REQUIRED_VALUE)
_toServerBundles[channelId].Write(segment, forceNewBuffer: false, orderType);
}
/// <summary>
/// Checks to set channel to reliable if dataLength is too long.
/// Gets the channelId to use, returning a fallback Id if the provided channelId is not supported.
/// </summary>
internal void CheckSetReliableChannel(int dataLength, ref Channel channel)
{
if (channel == Channel.Reliable)
return;
bool requiresMultipleMessages = GetRequiredMessageCount((byte)channel, dataLength, out _) > 1;
if (requiresMultipleMessages)
channel = Channel.Reliable;
}
/// <summary>
/// Gets the required number of messages needed for segmentSize and channel.
/// </summary>
private int GetRequiredMessageCount(byte channelId, int segmentSize, out int maxMessageSize)
{
maxMessageSize = GetLowestMTU(channelId) - SPLIT_INDICATOR_LENGTH;
return Mathf.CeilToInt((float)segmentSize / maxMessageSize);
}
/// <summary>
/// True if data must be split.
/// </summary>
/// <param name = "channelId"></param>
/// <param name = "segmentSize"></param>
private bool SplitRequired(byte channelId, int segmentSize, out int requiredMessages, out int maxMessageSize)
{
requiredMessages = GetRequiredMessageCount(channelId, segmentSize, out maxMessageSize);
bool splitRequired = requiredMessages > 1;
if (splitRequired && channelId != (byte)Channel.Reliable)
_networkManager.LogError($"A message of length {segmentSize} requires the reliable channel but was sent on channel {(Channel)channelId}. Please file this stack trace as a bug report.");
return splitRequired;
}
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 void SendSplitData(NetworkConnection conn, ref ArraySegment<byte> segment, int requiredMessages, int maxMessageSize, DataOrderType orderType)
private int SendSplitMessage(NetworkConnection conn, byte channelId, ArraySegment<byte> segment, DataOrderType orderType)
{
if (requiredMessages <= 1)
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($"SendSplitData was called with {requiredMessages} required messages. This method should only be called if messages must be split into 2 pieces or more.");
return;
_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;
}
byte channelId = (byte)Channel.Reliable;
PooledWriter headerWriter = WriterPool.Retrieve();
headerWriter.WritePacketIdUnpacked(PacketId.Split);
headerWriter.WriteInt32(requiredMessages);
ArraySegment<byte> headerSegment = headerWriter.GetArraySegment();
//Writer used to write the header and segment of each split message.
PooledWriter splitWriter = WriterPool.Retrieve();
int writeIndex = 0;
bool firstWrite = true;
// Send to connection until everything is written.
while (writeIndex < segment.Count)
//Channel is forced to reliable for split messages.
channelId = SPLIT_PACKET_CHANNELID;
for (int i = 0; i < messageCount; i++)
{
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(segment.Array, segment.Offset + writeIndex, chunkSize);
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, headerSegment, true);
conn.SendToClient(channelId, splitSegment);
}
conn.SendToClient(channelId, splitWriter.GetArraySegment());
// Otherwise it's going to the server.
else
{
_toServerBundles[channelId].Write(headerSegment, true, orderType);
_toServerBundles[channelId].Write(splitSegment, false, orderType);
}
_toServerBundles[channelId].Write(splitWriter.GetArraySegment(), forceNewBuffer: false, orderType);
writeIndex += chunkSize;
splitWriter.Clear();
}
headerWriter.Store();
WriterPool.Store(splitWriter);
return SPLIT_SENT_VALUE;
}
#endregion
/// <summary>
/// Processes data received by the socket.
@@ -763,9 +649,9 @@ namespace FishNet.Managing.Transporting
OnIterateOutgoingStart?.Invoke();
int channelCount = CHANNEL_COUNT;
ulong sentBytes = 0;
#if DEVELOPMENT
#if DEVELOPMENT
bool latencySimulatorEnabled = LatencySimulator.CanSimulate;
#endif
#endif
if (asServer)
SendAsServer();
else
@@ -806,11 +692,11 @@ namespace FishNet.Managing.Transporting
ArraySegment<byte> segment = new(bb.Data, 0, bb.Length);
if (HasIntermediateLayer)
segment = ProcessIntermediateOutgoing(segment, false);
#if DEVELOPMENT
#if DEVELOPMENT
if (latencySimulatorEnabled)
_latencySimulator.AddOutgoing(channel, segment, false, conn.ClientId);
else
#endif
#endif
Transport.SendToClient(channel, segment, conn.ClientId);
sentBytes += (ulong)segment.Count;
}
@@ -881,11 +767,11 @@ namespace FishNet.Managing.Transporting
ArraySegment<byte> segment = new(bb.Data, 0, bb.Length);
if (HasIntermediateLayer)
segment = ProcessIntermediateOutgoing(segment, true);
#if DEVELOPMENT
#if DEVELOPMENT
if (latencySimulatorEnabled)
_latencySimulator.AddOutgoing(channel, segment);
else
#endif
#endif
Transport.SendToServer(channel, segment);
sentBytes += (ulong)segment.Count;
}
@@ -900,17 +786,17 @@ namespace FishNet.Managing.Transporting
_networkTrafficStatistics.AddOutboundSocketData(sentBytes, asServer: false);
}
#if DEVELOPMENT
#if DEVELOPMENT
if (latencySimulatorEnabled)
_latencySimulator.IterateOutgoing(asServer);
#endif
#endif
Transport.IterateOutgoing(asServer);
OnIterateOutgoingEnd?.Invoke();
}
#region Editor.
#if UNITY_EDITOR
#if UNITY_EDITOR
private void OnValidate()
{
if (Transport == null)
@@ -923,7 +809,7 @@ namespace FishNet.Managing.Transporting
* will happen. */
_latencySimulator.SetEnabled(_latencySimulator.GetEnabled());
}
#endif
#endif
#endregion
}
}
@@ -13,6 +13,6 @@ AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
packageVersion: 4.7.1R
assetPath: Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs
uploadId: 866910
uploadId: 892096