using FishNet.Managing;
using FishNet.Utility.Extension;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Transporting.Multipass
{
[AddComponentMenu("FishNet/Transport/Multipass")]
public class Multipass : Transport
{
#region Types.
public struct TransportIdData
{
public int TransportId;
public int TransportIndex;
public TransportIdData(int transportId, int transportIndex)
{
TransportId = transportId;
TransportIndex = transportIndex;
}
}
#endregion
#region Public.
///
/// While true server actions such as starting or stopping the server will run on all transport.
///
[Tooltip("While true server actions such as starting or stopping the server will run on all transport.")]
public bool GlobalServerActions = true;
///
///
///
private Transport _clientTransport;
///
/// Transport the client is using.
/// Use SetClientTransport to assign this value.
///
[HideInInspector]
public Transport ClientTransport
{
get
{
//If not yet set.
if (_clientTransport == null)
{
//If there are transports to set from.
if (_transports.Count != 0)
_clientTransport = _transports[0];
/* Give feedback to developer that transport was not set
* before accessing this. Transport should always be set
* manually rather than assuming the default client
* transport. */
if (_clientTransport == null)
base.NetworkManager.LogError($"ClientTransport in Multipass could not be set to the first transport. This can occur if no trnasports are specified or if the first entry is null.");
else
base.NetworkManager.LogError($"ClientTransport in Multipass is being automatically set to {_clientTransport.GetType()}. For production use SetClientTransport before attempting to access the ClientTransport.");
}
return _clientTransport;
}
private set => _clientTransport = value;
}
#endregion
#region Serialized.
///
///
///
[Tooltip("Transports to use.")]
[SerializeField]
private List _transports = new List();
///
/// Transports to use.
///
public IList Transports => _transports;
#endregion
#region Private.
///
/// Key is the transport connectionid, Value is the TransportIdData.
///
private Dictionary _multipassToTransport = new Dictionary();
///
/// Key is the Multipass connectionId, Value is the transport connectionId.
///
private List> _transportToMultipass = new List>();
///
/// Ids available to new connections.
///
private Queue _availableIds = new Queue();
#endregion
#region Const.
///
/// Id to use for client when acting as host.
///
internal const int CLIENT_HOST_ID = short.MaxValue;
#endregion
public override void Initialize(NetworkManager networkManager, int transportIndex)
{
base.Initialize(networkManager, transportIndex);
//Remove any null transports and warn.
for (int i = 0; i < _transports.Count; i++)
{
if (_transports[i] == null)
{
base.NetworkManager.LogWarning($"Transports contains a null entry on index {i}.");
_transports.RemoveAt(i);
i--;
}
}
//No transports to use.
if (_transports.Count == 0)
{
base.NetworkManager.LogError($"No transports are set within Multipass.");
return;
}
//Create transportsToMultipass.
for (int i = 0; i < _transports.Count; i++)
{
Dictionary dict = new Dictionary();
_transportToMultipass.Add(dict);
}
//Initialize each transport.
for (int i = 0; i < _transports.Count; i++)
{
_transports[i].Initialize(networkManager, i);
_transports[i].OnClientConnectionState += Multipass_OnClientConnectionState;
_transports[i].OnServerConnectionState += Multipass_OnServerConnectionState;
_transports[i].OnRemoteConnectionState += Multipass_OnRemoteConnectionState;
_transports[i].OnClientReceivedData += Multipass_OnClientReceivedData;
_transports[i].OnServerReceivedData += Multipass_OnServerReceivedData;
}
}
private void OnDestroy()
{
//Initialize each transport.
foreach (Transport t in _transports)
t.Shutdown();
}
#region ClientIds.
///
/// Clears ClientIds when appropriate.
///
private void TryResetClientIds(bool force)
{
//Can only clear when every transport server isnt connected.
if (!force)
{
foreach (Transport t in _transports)
{
//Cannot clear if a server is running still.
if (t.GetConnectionState(true) == LocalConnectionState.Started)
return;
}
}
_multipassToTransport.Clear();
foreach (Dictionary item in _transportToMultipass)
item.Clear();
CreateAvailableIds();
}
///
/// Gets the Multipass connectionId using a transport connectionid.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool GetMultipassId(int transportIndex, int transportId, out int multipassId)
{
Dictionary dict = _transportToMultipass[transportIndex];
if (!dict.TryGetValueIL2CPP(transportId, out multipassId))
{
multipassId = -1;
base.NetworkManager.LogError($"Multipass connectionId could not be found for transportIndex {transportIndex}, transportId of {transportId}.");
return false;
}
return true;
}
///
/// Gets the TransportIdData using a Multipass connectionId.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool GetTransportIdData(int multipassId, out TransportIdData data)
{
if (!_multipassToTransport.TryGetValueIL2CPP(multipassId, out data))
{
//Fall through.
base.NetworkManager.LogError($"TransportIdData could not be found for Multipass connectionId of {multipassId}.");
return false;
}
return true;
}
#endregion
#region ConnectionStates.
///
/// Gets the IP address of a remote connectionId.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override string GetConnectionAddress(int connectionId)
{
TransportIdData data;
if (!GetTransportIdData(connectionId, out data))
return string.Empty;
return _transports[data.TransportIndex].GetConnectionAddress(data.TransportId);
}
///
/// Called when a connection state changes for the local client.
///
public override event Action OnClientConnectionState;
///
/// Called when a connection state changes for the local server.
///
public override event Action OnServerConnectionState;
///
/// Called when a connection state changes for a remote client.
///
public override event Action OnRemoteConnectionState;
///
/// Gets the current local ConnectionState of the first transport.
///
/// True if getting ConnectionState for the server.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override LocalConnectionState GetConnectionState(bool server)
{
if (server)
{
base.NetworkManager.LogError($"This method is not supported for server. Use GetConnectionState(server, transportIndex) instead.");
return LocalConnectionState.Stopped;
}
if (IsClientTransportSetWithError("GetConnectionState"))
return GetConnectionState(server, ClientTransport.Index);
else
return LocalConnectionState.Stopped;
}
///
/// Gets the current local ConnectionState of the transport on index.
///
/// True if getting ConnectionState for the server.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public LocalConnectionState GetConnectionState(bool server, int index)
{
if (!IndexInRange(index, true))
return LocalConnectionState.Stopped;
return _transports[index].GetConnectionState(server);
}
///
/// Gets the current ConnectionState of a remote client on the server.
///
/// ConnectionId to get ConnectionState for.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RemoteConnectionState GetConnectionState(int connectionId)
{
TransportIdData data;
if (!GetTransportIdData(connectionId, out data))
return RemoteConnectionState.Stopped;
return _transports[data.TransportIndex].GetConnectionState(data.TransportId);
}
///
/// Gets the current ConnectionState of a remote client on the server of the transport on index.
///
/// ConnectionId to get ConnectionState for.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RemoteConnectionState GetConnectionState(int connectionId, int index)
{
if (!IndexInRange(index, true))
return RemoteConnectionState.Stopped;
return _transports[index].GetConnectionState(connectionId);
}
///
/// Handles a ConnectionStateArgs for the local client.
///
///
private void Multipass_OnClientConnectionState(ClientConnectionStateArgs connectionStateArgs)
{
OnClientConnectionState?.Invoke(connectionStateArgs);
}
///
/// Handles a ConnectionStateArgs for the local server.
///
///
private void Multipass_OnServerConnectionState(ServerConnectionStateArgs connectionStateArgs)
{
OnServerConnectionState?.Invoke(connectionStateArgs);
TryResetClientIds(false);
}
///
/// Handles a ConnectionStateArgs for a remote client.
///
///
private void Multipass_OnRemoteConnectionState(RemoteConnectionStateArgs connectionStateArgs)
{
/* When starting Multipass needs to get a new
* connectionId to be used within FN. This is the 'ClientId'
* that is passed around for ownership, rpcs, ect.
*
* The new connectionId will be linked with the connectionId
* from the transport, named transportConnectionid.
*
* When data arrives the transportStateId is used as a key
* in fromClientIds, where Multipass Id is returned. The argument values
* are then overwritten with the MultipassId.
*
* When data is being sent the same process is performed but reversed.
* The connectionId is looked up in toClientIds, where the transportConnectionId
* is output. Then as before the argument values are overwritten with the
* transportConnectionId. */
int transportIndex = connectionStateArgs.TransportIndex;
int transportId = connectionStateArgs.ConnectionId;
int multipassId;
Dictionary transportToMultipass = _transportToMultipass[transportIndex];
//Started.
if (connectionStateArgs.ConnectionState == RemoteConnectionState.Started)
{
multipassId = _availableIds.Dequeue();
transportToMultipass[transportId] = multipassId;
_multipassToTransport[multipassId] = new TransportIdData(transportId, transportIndex);
}
//Stopped.
else
{
if (!GetMultipassId(transportIndex, transportId, out multipassId))
return;
_availableIds.Enqueue(multipassId);
transportToMultipass.Remove(transportId);
_multipassToTransport.Remove(multipassId);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
//Remove packets held for connection from latency simulator.
base.NetworkManager.TransportManager.LatencySimulator.RemovePendingForConnection(multipassId);
#endif
}
connectionStateArgs.ConnectionId = multipassId;
OnRemoteConnectionState?.Invoke(connectionStateArgs);
}
#endregion
#region Iterating.
///
/// Processes data received by the socket.
///
/// True to process data received on the server.
public override void IterateIncoming(bool server)
{
foreach (Transport t in _transports)
t.IterateIncoming(server);
}
///
/// Processes data to be sent by the socket.
///
/// True to process data received on the server.
public override void IterateOutgoing(bool server)
{
foreach (Transport t in _transports)
t.IterateOutgoing(server);
}
#endregion
#region ReceivedData.
///
/// Called when client receives data.
///
public override event Action OnClientReceivedData;
///
/// Handles a ClientReceivedDataArgs.
///
///
private void Multipass_OnClientReceivedData(ClientReceivedDataArgs receivedDataArgs)
{
OnClientReceivedData?.Invoke(receivedDataArgs);
}
///
/// Called when server receives data.
///
public override event Action OnServerReceivedData;
///
/// Handles a ClientReceivedDataArgs.
///
///
private void Multipass_OnServerReceivedData(ServerReceivedDataArgs receivedDataArgs)
{
int multipassId;
if (!GetMultipassId(receivedDataArgs.TransportIndex, receivedDataArgs.ConnectionId, out multipassId))
return;
receivedDataArgs.ConnectionId = multipassId;
OnServerReceivedData?.Invoke(receivedDataArgs);
}
#endregion
#region Sending.
///
/// Sends to the server on ClientTransport.
///
/// Channel to use.
/// /// Data to send.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void SendToServer(byte channelId, ArraySegment segment)
{
if (ClientTransport != null)
ClientTransport.SendToServer(channelId, segment);
}
///
/// Sends data to a client.
///
///
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void SendToClient(byte channelId, ArraySegment segment, int connectionId)
{
TransportIdData data;
if (GetTransportIdData(connectionId, out data))
_transports[data.TransportIndex].SendToClient(channelId, segment, data.TransportId);
}
#endregion
#region Configuration.
///
/// Returns if GlobalServerActions is true and if not logs an error.
///
///
private bool UseGlobalServerActionsWithError(string methodText)
{
if (!GlobalServerActions)
{
base.NetworkManager.LogError($"Method {methodText} is not supported while GlobalServerActions is false.");
return false;
}
else
{
return true;
}
}
///
/// Returns if ClientTransport is set and if not logs an error.
///
///
///
private bool IsClientTransportSetWithError(string methodText)
{
if (ClientTransport == null)
{
base.NetworkManager.LogError($"ClientTransport is not set. Use SetClientTransport before calling {methodText}.");
return false;
}
else
{
return true;
}
}
///
/// Populates the availableIds collection.
///
private void CreateAvailableIds()
{
_availableIds.Clear();
for (int i = 0; i < short.MaxValue; i++)
_availableIds.Enqueue(i);
}
///
/// Sets the client transport to the first of type.
///
///
public void SetClientTransport()
{
int index = -1;
for (int i = 0; i < _transports.Count; i++)
{
if (_transports[i].GetType() == typeof(T))
{
index = i;
break;
}
}
SetClientTransport(index);
}
///
/// Sets the client transport to the first of type T.
///
///
public void SetClientTransport(Type type)
{
int index = -1;
for (int i = 0; i < _transports.Count; i++)
{
if (_transports[i].GetType() == type)
{
index = i;
break;
}
}
SetClientTransport(index);
}
///
/// Sets the client transport to the matching reference of transport.
///
///
public void SetClientTransport(Transport transport)
{
int index = -1;
for (int i = 0; i < _transports.Count; i++)
{
if (_transports[i] == transport)
{
index = i;
break;
}
}
SetClientTransport(index);
}
///
/// Sets the client transport to the transport on index.
///
///
public void SetClientTransport(int index)
{
if (!IndexInRange(index, true))
return;
ClientTransport = _transports[index];
}
///
/// Gets the Transport on index.
///
///
///
public Transport GetTransport(int index)
{
if (!IndexInRange(index, true))
return null;
return _transports[index];
}
///
/// Gets the Transport on of type T.
///
///
///
public T GetTransport()
{
foreach (Transport t in _transports)
{
if (t.GetType() == typeof(T))
return (T)(object)t;
}
return default(T);
}
///
/// Returns if the transport for connectionId is a local transport.
/// While true several security checks are disabled.
///
public override bool IsLocalTransport(int connectionid)
{
//If able to get transport data return value from transport.
if (GetTransportIdData(connectionid, out TransportIdData data))
return _transports[data.TransportIndex].IsLocalTransport(data.TransportId);
//Otherwise return false forcing checks.
else
return false;
}
///
/// Returns the maximum number of clients allowed to connect to the server. If the transport does not support this method the value -1 is returned.
/// The first transport is used.
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetMaximumClients()
{
base.NetworkManager.LogError($"This method is not supported. Use GetMaximumClients(transportIndex) instead.");
return -1;
}
///
/// Returns the maximum number of clients allowed to connect to the server. If the transport does not support this method the value -1 is returned.
/// The first transport is used.
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetMaximumClients(int transportIndex)
{
if (!IndexInRange(transportIndex, true))
return -1;
return _transports[transportIndex].GetMaximumClients();
}
///
/// Sets maximum number of clients allowed to connect to the server. If applied at runtime and clients exceed this value existing clients will stay connected but new clients may not connect.
/// This sets the value to the transport on the first index.
///
///
public override void SetMaximumClients(int value)
{
base.NetworkManager.LogError($"This method is not supported. Use SetMaximumClients(value, transportIndex) instead.");
}
///
/// Sets maximum number of clients allowed to connect to the server. If applied at runtime and clients exceed this value existing clients will stay connected but new clients may not connect.
/// This sets the value to the transport on index.
///
///
public void SetMaximumClients(int value, int transportIndex)
{
if (!IndexInRange(transportIndex, true))
return;
_transports[transportIndex].SetMaximumClients(value);
}
///
/// Sets which address the client will connect to.
/// This will set the address for every transport.
///
///
public override void SetClientAddress(string address)
{
foreach (Transport t in _transports)
t.SetClientAddress(address);
}
public override void SetServerBindAddress(string address, IPAddressType addressType)
{
base.NetworkManager.LogError($"This method is not supported. Use SetServerBindAddress(address, transportIndex) instead.");
}
/// Sets which address the server will bind to.
/// This is called on the transport of index.
///
///
public void SetServerBindAddress(string address, IPAddressType addressType, int index)
{
if (!IndexInRange(index, true))
return;
_transports[index].SetServerBindAddress(address, addressType);
}
///
/// Sets which port to use on the first transport.
///
public override void SetPort(ushort port)
{
base.NetworkManager.LogError($"This method is not supported. Use SetPort(port, transportIndex) instead.");
}
///
/// Sets which port to use on transport of index.
///
public void SetPort(ushort port, int index)
{
if (!IndexInRange(index, true))
return;
_transports[index].SetPort(port);
}
#endregion
#region Start and stop.
///
/// Starts the local server or client using configured settings on the first transport.
///
/// True to start server.
public override bool StartConnection(bool server)
{
//Server.
if (server)
{
if (!UseGlobalServerActionsWithError("StartConnection"))
return false;
bool success = true;
for (int i = 0; i < _transports.Count; i++)
{
if (!StartConnection(true, i))
success = false;
}
return success;
}
//Client.
else
{
if (IsClientTransportSetWithError("StartConnection"))
return StartConnection(false, ClientTransport.Index);
else
return false;
}
}
///
/// Starts the local server or client using configured settings on transport of index.
///
/// True to start server.
public bool StartConnection(bool server, int index)
{
if (server)
{
return StartServer(index);
}
else
{
if (IsClientTransportSetWithError("StartConnection"))
return StartClient();
else
return false;
}
}
///
/// Stops the local server or client on the first transport.
///
/// True to stop server.
public override bool StopConnection(bool server)
{
//Server
if (server)
{
if (!UseGlobalServerActionsWithError("StopConnection"))
return false;
bool success = true;
for (int i = 0; i < _transports.Count; i++)
{
if (!StopConnection(true, i))
success = false;
}
return success;
}
//Client.
else
{
if (IsClientTransportSetWithError("StopConnection"))
return StopConnection(false, ClientTransport.Index);
else
return false;
}
}
///
/// Stops the local server or client on transport of index.
///
/// True to stop server.
public bool StopConnection(bool server, int index)
{
if (server)
{
return StopServer(index);
}
else
{
if (IsClientTransportSetWithError("StopConnection"))
return StopClient();
else
return false;
}
}
///
/// Stops a remote client from the server, disconnecting the client.
///
/// ConnectionId of the client to disconnect.
/// True to abrutly stp the client socket without waiting socket thread.
public override bool StopConnection(int connectionId, bool immediately)
{
return StopClient(connectionId, immediately);
}
///
/// Stops the server connection on transportIndex.
///
/// True to send a disconnect message to connections before stopping them.
/// Index of transport to stop on.
public bool StopServerConnection(bool sendDisconnectMessage, int transportIndex)
{
if (sendDisconnectMessage)
{
//Get connectionIds as ServerManager knows them.
int[] multipassIds = _transportToMultipass[transportIndex].Keys.ToArray();
//Tell serve manager to write disconnect for those ids.
base.NetworkManager.ServerManager.SendDisconnectMessages(multipassIds);
//Iterate outgoing on transport which is being stopped.
_transports[transportIndex].IterateOutgoing(true);
}
return StopConnection(true, transportIndex);
}
///
/// Stops both client and server on all transports.
///
public override void Shutdown()
{
foreach (Transport t in _transports)
{
//Stops client then server connections.
t.StopConnection(false);
t.StopConnection(true);
}
}
#region Privates.
///
/// Starts server of transport on index.
///
/// True if there were no blocks. A true response does not promise a socket will or has connected.
private bool StartServer(int index)
{
if (!IndexInRange(index, true))
return false;
return _transports[index].StartConnection(true);
}
///
/// Stops server of transport on index.
///
private bool StopServer(int index)
{
if (!IndexInRange(index, true))
return false;
return _transports[index].StopConnection(true);
}
///
/// Starts the client on ClientTransport.
///
///
/// True if there were no blocks. A true response does not promise a socket will or has connected.
private bool StartClient()
{
return ClientTransport.StartConnection(false);
}
///
/// Stops the client on ClientTransport.
///
private bool StopClient()
{
return ClientTransport.StopConnection(false);
}
///
/// Stops a remote client on the server.
///
///
/// True to abrutly stp the client socket without waiting socket thread.
private bool StopClient(int connectionId, bool immediately)
{
TransportIdData data;
if (!GetTransportIdData(connectionId, out data))
return false;
return _transports[data.TransportIndex].StopConnection(data.TransportId, immediately);
}
#endregion
#endregion
#region Channels.
///
/// Gets the MTU for a channel on the first transport. This should take header size into consideration.
/// For example, if MTU is 1200 and a packet header for this channel is 10 in size, this method should return 1190.
///
///
///
public override int GetMTU(byte channel)
{
return GetMTU(channel, 0);
}
///
/// Gets the MTU for a channel of transport on index. This should take header size into consideration.
/// For example, if MTU is 1200 and a packet header for this channel is 10 in size, this method should return 1190.
///
///
///
public int GetMTU(byte channel, int index)
{
if (!IndexInRange(index, true))
return -1;
return _transports[index].GetMTU(channel);
}
#endregion
#region Misc.
///
/// Returns if an index is within range of the Transports collection.
///
private bool IndexInRange(int index, bool error)
{
if (index >= _transports.Count || index < 0)
{
if (error)
base.NetworkManager.LogError($"Index of {index} is out of Transports range.");
return false;
}
else
{
return true;
}
}
//perf change events to direct calls in transports.
public override void HandleServerConnectionState(ServerConnectionStateArgs connectionStateArgs) { }
public override void HandleRemoteConnectionState(RemoteConnectionStateArgs connectionStateArgs) { }
public override void HandleClientReceivedDataArgs(ClientReceivedDataArgs receivedDataArgs) { }
public override void HandleServerReceivedDataArgs(ServerReceivedDataArgs receivedDataArgs) { }
public override void HandleClientConnectionState(ClientConnectionStateArgs connectionStateArgs) { }
#endregion
}
}