#if UNITY_EDITOR || DEVELOPMENT_BUILD #define DEVELOPMENT #endif using FishNet.Connection; using FishNet.Managing; using GameKit.Dependencies.Utilities; using System; using System.Collections.Generic; using UnityEngine; namespace FishNet.Transporting.Multipass { [AddComponentMenu("FishNet/Transport/Multipass")] public class Multipass : Transport { #region Types. public struct ClientTransportData : IEquatable { /// /// Transport index this connection is on. /// public int TransportIndex; /// /// ConnectionId assigned by the transport. /// public int TransportId; /// /// Connection Id assigned by multipass. This Id is the one communicated to the NetworkManager. /// public int MultipassId; /// /// Cached hashcode for values. /// private int _hashCode; public ClientTransportData(int transportIndex, int transportId, int multipassId) { TransportIndex = transportIndex; TransportId = transportId; MultipassId = multipassId; _hashCode = (transportIndex, transportId, multipassId).GetHashCode(); } public bool Equals(ClientTransportData other) { return (_hashCode == other._hashCode); } } #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(); /// /// Transports to use. /// public IReadOnlyList Transports => _transports; #endregion #region Private. /// /// An unset/invalid ClientTransportData. /// private readonly ClientTransportData INVALID_CLIENTTRANSPORTDATA = new(int.MinValue, int.MinValue, int.MinValue); /// /// MultipassId lookup. /// private Dictionary _multpassIdLookup = new(); /// /// TransportId lookup. Each index within the list is the same as the transport index. /// private List> _transportIdLookup = new(); /// /// Ids available to new connections. /// private Queue _availableMultipassIds = new(); /// /// Last Id added to availableMultipassIds. /// private int _lastAvailableMultipassId = 0; #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(); _transportIdLookup.Add(dict); //Initialize transports and callbacks. _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(); ResetLookupCollections(); } #region ClientIds. /// /// Resets lookup collections and caches potential garbage. /// private void ResetLookupCollections() { _multpassIdLookup.Clear(); for (int i = 0; i < _transportIdLookup.Count; i++) _transportIdLookup[i].Clear(); } /// /// 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; } } ResetLookupCollections(); CreateAvailableIds(true); } /// /// Gets the Multipass connectionId using a transport connectionid. /// private ClientTransportData GetDataFromTransportId(int transportIndex, int transportId, bool log) { Dictionary dict = _transportIdLookup[transportIndex]; if (dict.TryGetValueIL2CPP(transportId, out ClientTransportData ctd)) return ctd; //Fall through/fail. if (log) base.NetworkManager.LogError($"Multipass connectionId could not be found for transportIndex {transportIndex}, transportId of {transportId}."); return INVALID_CLIENTTRANSPORTDATA; } /// /// Gets the TransportIdData using a Multipass connectionId. /// private ClientTransportData GetDataFromMultipassId(int multipassId) { if (_multpassIdLookup.TryGetValueIL2CPP(multipassId, out ClientTransportData ctd)) return ctd; //Fall through/fail. base.NetworkManager.LogError($"TransportIdData could not be found for Multipass connectionId of {multipassId}."); return INVALID_CLIENTTRANSPORTDATA; } #endregion #region ConnectionStates. /// /// Gets the IP address of a remote connectionId. /// public override string GetConnectionAddress(int multipassId) { ClientTransportData ctd = GetDataFromMultipassId(multipassId); if (ctd.Equals(INVALID_CLIENTTRANSPORTDATA)) return string.Empty; return _transports[ctd.TransportIndex].GetConnectionAddress(ctd.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. 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. public LocalConnectionState GetConnectionState(bool server, int transportIndex) { if (!IndexInRange(transportIndex, true)) return LocalConnectionState.Stopped; return _transports[transportIndex].GetConnectionState(server); } /// /// Gets the current ConnectionState of a remote client on the server. /// /// ConnectionId to get ConnectionState for. public override RemoteConnectionState GetConnectionState(int multipassId) { ClientTransportData ctd = GetDataFromMultipassId(multipassId); if (ctd.Equals(INVALID_CLIENTTRANSPORTDATA)) return RemoteConnectionState.Stopped; return _transports[ctd.TransportIndex].GetConnectionState(ctd.TransportId); } /// /// Gets the current ConnectionState of a remote client on the server of the transport on index. /// /// ConnectionId to get ConnectionState for. 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 transportConnectionId = connectionStateArgs.ConnectionId; /* MultipassId is set to a new value when connecting * or discovered value when disconnecting. */ int multipassId; Dictionary transportToMultipass = _transportIdLookup[transportIndex]; //Started. if (connectionStateArgs.ConnectionState == RemoteConnectionState.Started) { if (_availableMultipassIds.Count == 0) { bool addedIds = CreateAvailableIds(false); if (!addedIds) { base.NetworkManager.Log($"There are no more available connectionIds to use. Connection {transportConnectionId} has been kicked."); _transports[transportIndex].StopConnection(transportConnectionId, true); return; } } //Get a multipassId for new connections. multipassId = _availableMultipassIds.Dequeue(); //Get and update a clienttransportdata. ClientTransportData ctd = new(transportIndex, transportConnectionId, multipassId); //Assign the lookup for transportId/index. transportToMultipass[transportConnectionId] = ctd; //Assign the lookup for multipassId. _multpassIdLookup[multipassId] = ctd; //Update args to use multipassId before invoking. connectionStateArgs.ConnectionId = multipassId; OnRemoteConnectionState?.Invoke(connectionStateArgs); } //Stopped. else { Transport transport = _transports[transportIndex]; //Only log if the server is started for the specified transport. bool log = (transport.GetConnectionState(server: true) == LocalConnectionState.Started); ClientTransportData ctd = GetDataFromTransportId(transportIndex, transportConnectionId, log); /* If CTD could not be found then the connection * is not stored/known. Nothing further can be done; the event cannot * invoke either since Id is unknown. */ if (ctd.Equals(INVALID_CLIENTTRANSPORTDATA)) return; //Add the multipassId back to the queue. _availableMultipassIds.Enqueue(ctd.MultipassId); transportToMultipass.Remove(transportConnectionId); _multpassIdLookup.Remove(ctd.MultipassId); #if DEVELOPMENT //Remove packets held for connection from latency simulator. base.NetworkManager.TransportManager.LatencySimulator.RemovePendingForConnection(ctd.MultipassId); #endif //Update args to use multipassId before invoking. connectionStateArgs.ConnectionId = ctd.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 asServer) { foreach (Transport t in _transports) t.IterateIncoming(asServer); } /// /// Processes data to be sent by the socket. /// /// True to send data from the local server to clients, false to send from the local client to server. public override void IterateOutgoing(bool asServer) { foreach (Transport t in _transports) t.IterateOutgoing(asServer); } #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) { ClientTransportData ctd = GetDataFromTransportId(receivedDataArgs.TransportIndex, receivedDataArgs.ConnectionId, log: true); if (ctd.Equals(INVALID_CLIENTTRANSPORTDATA)) return; receivedDataArgs.ConnectionId = ctd.MultipassId; OnServerReceivedData?.Invoke(receivedDataArgs); } #endregion #region Sending. /// /// Sends to the server on ClientTransport. /// /// Channel to use. /// /// Data to send. public override void SendToServer(byte channelId, ArraySegment segment) { if (ClientTransport != null) ClientTransport.SendToServer(channelId, segment); } /// /// Sends data to a client. /// public override void SendToClient(byte channelId, ArraySegment segment, int multipassId) { ClientTransportData ctd = GetDataFromMultipassId(multipassId); if (ctd.Equals(INVALID_CLIENTTRANSPORTDATA)) return; _transports[ctd.TransportIndex].SendToClient(channelId, segment, ctd.TransportId); } /// /// Sends data to a client. /// /// TransportIndex the client is using. public void SendToClient(byte channelId, ArraySegment segment, int transportId, int transportIndex) { _transports[transportIndex].SendToClient(channelId, segment, 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. /// /// True if at least 1 Id was added. private bool CreateAvailableIds(bool reset) { if (reset) { _lastAvailableMultipassId = 0; _availableMultipassIds.Clear(); } //Add in blocks of 1000. int added = 0; while ((_lastAvailableMultipassId <= NetworkConnection.MAXIMUM_CLIENTID_WITHOUT_SIMULATED_VALUE) && (added < 1000)) { added++; _availableMultipassIds.Enqueue(_lastAvailableMultipassId); _lastAvailableMultipassId++; } return (added > 0); } /// /// 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 first transport is a local transport, optionally checking against connectionId. /// While true several security checks are disabled. /// public override bool IsLocalTransport(int connectionId) { foreach (Transport item in _transports) return item.IsLocalTransport(connectionId); return false; } /// /// Returns if the transportId is a local transport, optionally checking against connectionId. /// While true several security checks are disabled. /// public bool IsLocalTransport(int transportId, int connectionId) { if (!IndexInRange(transportId, true)) return false; return _transports[transportId].IsLocalTransport(connectionId); } /// /// 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. /// This method is not supported. Use GetMaximumClients(transportIndex) instead. /// /// 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. /// /// 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 for every transport. /// /// public override void SetMaximumClients(int value) { foreach (Transport t in _transports) t.SetMaximumClients(value); } /// /// 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); } /// /// Sets which address the client will connect to. /// /// /// Transport index to set for. public void SetClientAddress(string address, int index) { if (!IndexInRange(index, true)) return; _transports[index].SetClientAddress(address); } /// /// Sets which address the server will bind to. /// This will set the address for every transport. /// public override void SetServerBindAddress(string address, IPAddressType addressType) { foreach (Transport t in _transports) t.SetServerBindAddress(address, addressType); } /// 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. /// This will set the port for every transport. /// public override void SetPort(ushort port) { foreach (Transport t in _transports) t.SetPort(port); } /// /// 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); } /// /// Gets the first transports port. /// /// public override ushort GetPort() { foreach (Transport t in _transports) return t.GetPort(); return base.GetPort(); } #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 dictionary for transportIndex. Dictionary dict = _transportIdLookup[transportIndex]; //Create an array containing all multipass Ids for transportIndex. int[] multipassIds = new int[dict.Count]; int index = 0; foreach (ClientTransportData item in dict.Values) multipassIds[index++] = item.MultipassId; //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(asServer: 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 multipassId, bool immediately) { ClientTransportData ctd = GetDataFromMultipassId(multipassId); if (ctd.Equals(INVALID_CLIENTTRANSPORTDATA)) return false; return _transports[ctd.TransportIndex].StopConnection(ctd.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 } }