504 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			504 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using FishNet.Connection;
 | |
| using FishNet.Managing.Debugging;
 | |
| using FishNet.Managing.Logging;
 | |
| using FishNet.Managing.Server;
 | |
| using FishNet.Managing.Transporting;
 | |
| using FishNet.Serializing;
 | |
| using FishNet.Transporting;
 | |
| using FishNet.Utility.Extension;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using UnityEngine;
 | |
| 
 | |
| namespace FishNet.Managing.Client
 | |
| {
 | |
|     /// <summary>
 | |
|     /// A container for local client data and actions.
 | |
|     /// </summary>
 | |
|     [DisallowMultipleComponent]
 | |
|     [AddComponentMenu("FishNet/Manager/ClientManager")]
 | |
|     public sealed partial class ClientManager : MonoBehaviour
 | |
|     {
 | |
|         #region Public.
 | |
|         /// <summary>
 | |
|         /// Called after local client has authenticated.
 | |
|         /// </summary>
 | |
|         public event Action OnAuthenticated;
 | |
|         /// <summary>
 | |
|         /// Called after the local client connection state changes.
 | |
|         /// </summary>
 | |
|         public event Action<ClientConnectionStateArgs> OnClientConnectionState;
 | |
|         /// <summary>
 | |
|         /// Called when a client other than self connects.
 | |
|         /// This is only available when using ServerManager.ShareIds.
 | |
|         /// </summary>
 | |
|         public event Action<RemoteConnectionStateArgs> OnRemoteConnectionState;
 | |
|         /// <summary>
 | |
|         /// True if the client connection is connected to the server.
 | |
|         /// </summary>
 | |
|         public bool Started { get; private set; }
 | |
|         /// <summary>
 | |
|         /// NetworkConnection the local client is using to send data to the server.
 | |
|         /// </summary>
 | |
|         public NetworkConnection Connection = NetworkManager.EmptyConnection;
 | |
|         /// <summary>
 | |
|         /// Handling and information for objects known to the local client.
 | |
|         /// </summary>
 | |
|         public ClientObjects Objects { get; private set; }
 | |
|         /// <summary>
 | |
|         /// All currently connected clients. This field only contains data while ServerManager.ShareIds is enabled.
 | |
|         /// </summary>
 | |
|         public Dictionary<int, NetworkConnection> Clients = new Dictionary<int, NetworkConnection>();
 | |
|         /// <summary>
 | |
|         /// NetworkManager for client.
 | |
|         /// </summary>
 | |
|         [HideInInspector]
 | |
|         public NetworkManager NetworkManager { get; private set; }
 | |
|         #endregion
 | |
| 
 | |
|         #region Serialized.
 | |
|         /// <summary>
 | |
|         /// True to automatically set the frame rate when the client connects.
 | |
|         /// </summary>
 | |
|         [Tooltip("True to automatically set the frame rate when the client connects.")]
 | |
|         [SerializeField]
 | |
|         private bool _changeFrameRate = true;
 | |
|         /// <summary>
 | |
|         /// 
 | |
|         /// </summary>
 | |
|         [Tooltip("Maximum frame rate the client may run at. When as host this value runs at whichever is higher between client and server.")]
 | |
|         [Range(1, NetworkManager.MAXIMUM_FRAMERATE)]
 | |
|         [SerializeField]
 | |
|         private ushort _frameRate = NetworkManager.MAXIMUM_FRAMERATE;
 | |
|         /// <summary>
 | |
|         /// Maximum frame rate the client may run at. When as host this value runs at whichever is higher between client and server.
 | |
|         /// </summary>
 | |
|         internal ushort FrameRate => (_changeFrameRate) ? _frameRate : (ushort)0;
 | |
|         #endregion
 | |
| 
 | |
|         #region Private.
 | |
|         /// <summary>
 | |
|         /// Used to read splits.
 | |
|         /// </summary>
 | |
|         private SplitReader _splitReader = new SplitReader();
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|         /// <summary>
 | |
|         /// Logs data about parser to help debug.
 | |
|         /// </summary>
 | |
|         private ParseLogger _parseLogger = new ParseLogger();
 | |
| #endif
 | |
|         #endregion
 | |
| 
 | |
|         private void OnDestroy()
 | |
|         {
 | |
|             Objects?.SubscribeToSceneLoaded(false);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Initializes this script for use.
 | |
|         /// </summary>
 | |
|         /// <param name="manager"></param>
 | |
|         internal void InitializeOnce_Internal(NetworkManager manager)
 | |
|         {
 | |
|             NetworkManager = manager;
 | |
|             Objects = new ClientObjects(manager);
 | |
|             Objects.SubscribeToSceneLoaded(true);
 | |
|             /* Unsubscribe before subscribing.
 | |
|              * Shouldn't be an issue but better safe than sorry. */
 | |
|             SubscribeToEvents(false);
 | |
|             SubscribeToEvents(true);
 | |
|             //Listen for client connections from server.
 | |
|             RegisterBroadcast<ClientConnectionChangeBroadcast>(OnClientConnectionBroadcast);
 | |
|             RegisterBroadcast<ConnectedClientsBroadcast>(OnConnectedClientsBroadcast);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when the server sends a connection state change for any client.
 | |
|         /// </summary>
 | |
|         /// <param name="args"></param>
 | |
|         private void OnClientConnectionBroadcast(ClientConnectionChangeBroadcast args)
 | |
|         {
 | |
|             //If connecting invoke after added to clients, otherwise invoke before removed.
 | |
|             RemoteConnectionStateArgs rcs = new RemoteConnectionStateArgs((args.Connected) ? RemoteConnectionState.Started : RemoteConnectionState.Stopped, args.Id, -1);
 | |
| 
 | |
|             if (args.Connected)
 | |
|             {
 | |
|                 Clients[args.Id] = new NetworkConnection(NetworkManager, args.Id, false);
 | |
|                 OnRemoteConnectionState?.Invoke(rcs);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 OnRemoteConnectionState?.Invoke(rcs);
 | |
|                 if (Clients.TryGetValue(args.Id, out NetworkConnection c))
 | |
|                 {
 | |
|                     c.Dispose();
 | |
|                     Clients.Remove(args.Id);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when the server sends all currently connected clients.
 | |
|         /// </summary>
 | |
|         /// <param name="args"></param>
 | |
|         private void OnConnectedClientsBroadcast(ConnectedClientsBroadcast args)
 | |
|         {
 | |
|             NetworkManager.ClearClientsCollection(Clients);
 | |
| 
 | |
|             List<int> collection = args.ListCache.Collection;// args.Ids;
 | |
|             //No connected clients except self.
 | |
|             if (collection == null)
 | |
|                 return;
 | |
| 
 | |
|             int count = collection.Count;
 | |
|             for (int i = 0; i < count; i++)
 | |
|             {
 | |
|                 int id = collection[i];
 | |
|                 Clients[id] = new NetworkConnection(NetworkManager, id, false);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Changes subscription status to transport.
 | |
|         /// </summary>
 | |
|         /// <param name="subscribe"></param>
 | |
|         private void SubscribeToEvents(bool subscribe)
 | |
|         {
 | |
|             if (NetworkManager == null || NetworkManager.TransportManager == null || NetworkManager.TransportManager.Transport == null)
 | |
|                 return;
 | |
| 
 | |
|             if (subscribe)
 | |
|             {
 | |
|                 NetworkManager.TransportManager.OnIterateIncomingEnd += TransportManager_OnIterateIncomingEnd;
 | |
|                 NetworkManager.TransportManager.Transport.OnClientReceivedData += Transport_OnClientReceivedData;
 | |
|                 NetworkManager.TransportManager.Transport.OnClientConnectionState += Transport_OnClientConnectionState;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 NetworkManager.TransportManager.OnIterateIncomingEnd -= TransportManager_OnIterateIncomingEnd;
 | |
|                 NetworkManager.TransportManager.Transport.OnClientReceivedData -= Transport_OnClientReceivedData;
 | |
|                 NetworkManager.TransportManager.Transport.OnClientConnectionState -= Transport_OnClientConnectionState;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Stops the local client connection.
 | |
|         /// </summary>
 | |
|         public void StopConnection()
 | |
|         {
 | |
|             NetworkManager.TransportManager.Transport.StopConnection(false);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Starts the local client connection.
 | |
|         /// </summary>
 | |
|         public void StartConnection()
 | |
|         {
 | |
|             NetworkManager.TransportManager.Transport.StartConnection(false);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sets the transport address and starts the local client connection.
 | |
|         /// </summary>
 | |
|         public void StartConnection(string address)
 | |
|         {
 | |
|             StartConnection(address, NetworkManager.TransportManager.Transport.GetPort());
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Sets the transport address and port, and starts the local client connection.
 | |
|         /// </summary>
 | |
|         public void StartConnection(string address, ushort port)
 | |
|         {
 | |
|             NetworkManager.TransportManager.Transport.SetClientAddress(address);
 | |
|             NetworkManager.TransportManager.Transport.SetPort(port);
 | |
|             StartConnection();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when a connection state changes for the local client.
 | |
|         /// </summary>
 | |
|         /// <param name="args"></param>
 | |
|         private void Transport_OnClientConnectionState(ClientConnectionStateArgs args)
 | |
|         {
 | |
|             LocalConnectionState state = args.ConnectionState;
 | |
|             Started = (state == LocalConnectionState.Started);
 | |
|             Objects.OnClientConnectionState(args);
 | |
| 
 | |
|             //Clear connection after so objects can update using current Connection value.
 | |
|             if (!Started)
 | |
|             {
 | |
|                 Connection = NetworkManager.EmptyConnection;
 | |
|                 NetworkManager.ClearClientsCollection(Clients);
 | |
|             }
 | |
| 
 | |
|             if (NetworkManager.CanLog(LoggingType.Common))
 | |
|             {
 | |
|                 Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex);
 | |
|                 string tName = (t == null) ? "Unknown" : t.GetType().Name;
 | |
|                 Debug.Log($"Local client is {state.ToString().ToLower()} for {tName}.");
 | |
|             }
 | |
| 
 | |
|             NetworkManager.UpdateFramerate();
 | |
|             OnClientConnectionState?.Invoke(args);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when a socket receives data.
 | |
|         /// </summary>
 | |
|         private void Transport_OnClientReceivedData(ClientReceivedDataArgs args)
 | |
|         {
 | |
|             args.Data = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, true);
 | |
|             ParseReceived(args);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called after IterateIncoming has completed.
 | |
|         /// </summary>
 | |
|         private void TransportManager_OnIterateIncomingEnd(bool server)
 | |
|         {
 | |
|             /* Should the last packet received be a spawn or despawn
 | |
|              * then the cache won't yet be iterated because it only
 | |
|              * iterates when a packet is anything but those two. Because
 | |
|              * of such if any object caches did come in they must be iterated
 | |
|              * at the end of the incoming cycle. This isn't as clean as I'd
 | |
|              * like but it does ensure there will be no missing network object
 | |
|              * references on spawned objects. */
 | |
|             if (Started && !server)
 | |
|                 Objects.IterateObjectCache();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses received data.
 | |
|         /// </summary>
 | |
|         private void ParseReceived(ClientReceivedDataArgs args)
 | |
|         {
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|             _parseLogger.Reset();
 | |
| #endif
 | |
| 
 | |
|             ArraySegment<byte> segment = args.Data;
 | |
|             NetworkManager.StatisticsManager.NetworkTraffic.LocalClientReceivedData((ulong)segment.Count);
 | |
|             if (segment.Count <= TransportManager.TICK_BYTES)
 | |
|                 return;
 | |
| 
 | |
|             PacketId packetId = PacketId.Unset;
 | |
| #if !UNITY_EDITOR && !DEVELOPMENT_BUILD
 | |
|             try
 | |
|             {
 | |
| #endif
 | |
|             using (PooledReader reader = ReaderPool.GetReader(segment, NetworkManager))
 | |
|             {
 | |
|                 NetworkManager.TimeManager.LastPacketTick = reader.ReadUInt32(AutoPackType.Unpacked);
 | |
|                 /* This is a special condition where a message may arrive split.
 | |
|                 * When this occurs buffer each packet until all packets are
 | |
|                 * received. */
 | |
|                 if (reader.PeekPacketId() == PacketId.Split)
 | |
|                 {
 | |
|                     //Skip packetId.
 | |
|                     reader.ReadPacketId();
 | |
|                     int expectedMessages;
 | |
|                     _splitReader.GetHeader(reader, out expectedMessages);
 | |
|                     _splitReader.Write(NetworkManager.TimeManager.LastPacketTick, reader, expectedMessages);
 | |
|                     /* If fullMessage returns 0 count then the split
 | |
|                      * has not written fully yet. Otherwise, if there is
 | |
|                      * data within then reinitialize reader with the
 | |
|                      * full message. */
 | |
|                     ArraySegment<byte> fullMessage = _splitReader.GetFullMessage();
 | |
|                     if (fullMessage.Count == 0)
 | |
|                         return;
 | |
| 
 | |
|                     //Initialize reader with full message.
 | |
|                     reader.Initialize(fullMessage, NetworkManager);
 | |
|                 }
 | |
| 
 | |
|                 while (reader.Remaining > 0)
 | |
|                 {
 | |
|                     packetId = reader.ReadPacketId();
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|                     _parseLogger.AddPacket(packetId);
 | |
| #endif
 | |
|                     bool spawnOrDespawn = (packetId == PacketId.ObjectSpawn || packetId == PacketId.ObjectDespawn);
 | |
|                     /* Length of data. Only available if using unreliable. Unreliable packets
 | |
|                      * can arrive out of order which means object orientated messages such as RPCs may
 | |
|                      * arrive after the object for which they target has already been destroyed. When this happens
 | |
|                      * on lesser solutions they just dump the entire packet. However, since FishNet batches data.
 | |
|                      * it's very likely a packet will contain more than one packetId. With this mind, length is
 | |
|                      * sent as well so if any reason the data does have to be dumped it will only be dumped for
 | |
|                      * that single packetId  but not the rest. Broadcasts don't need length either even if unreliable
 | |
|                      * because they are not object bound. */
 | |
| 
 | |
|                     //Is spawn or despawn; cache packet.
 | |
|                     if (spawnOrDespawn)
 | |
|                     {
 | |
|                         if (packetId == PacketId.ObjectSpawn)
 | |
|                             Objects.CacheSpawn(reader);
 | |
|                         else if (packetId == PacketId.ObjectDespawn)
 | |
|                             Objects.CacheDespawn(reader);
 | |
|                     }
 | |
|                     //Not spawn or despawn.
 | |
|                     else
 | |
|                     {
 | |
|                         /* Iterate object cache should any of the
 | |
|                          * incoming packets rely on it. Objects
 | |
|                          * in cache will always be received before any messages
 | |
|                          * that use them. */
 | |
|                         Objects.IterateObjectCache();
 | |
|                         //Then process packet normally.
 | |
|                         if ((ushort)packetId >= NetworkManager.StartingRpcLinkIndex)
 | |
|                         {
 | |
|                             Objects.ParseRpcLink(reader, (ushort)packetId, args.Channel);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.Reconcile)
 | |
|                         {
 | |
|                             Objects.ParseReconcileRpc(reader, args.Channel);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.ObserversRpc)
 | |
|                         {
 | |
|                             Objects.ParseObserversRpc(reader, args.Channel);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.TargetRpc)
 | |
|                         {
 | |
|                             Objects.ParseTargetRpc(reader, args.Channel);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.Broadcast)
 | |
|                         {
 | |
|                             ParseBroadcast(reader, args.Channel);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.PingPong)
 | |
|                         {
 | |
|                             ParsePingPong(reader);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.SyncVar)
 | |
|                         {
 | |
|                             Objects.ParseSyncType(reader, false, args.Channel);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.SyncObject)
 | |
|                         {
 | |
|                             Objects.ParseSyncType(reader, true, args.Channel);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.PredictedSpawnResult)
 | |
|                         {
 | |
|                             Objects.ParsePredictedSpawnResult(reader);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.TimingUpdate)
 | |
|                         {
 | |
|                             NetworkManager.TimeManager.ParseTimingUpdate();
 | |
|                         }
 | |
|                         else if (packetId == PacketId.OwnershipChange)
 | |
|                         {
 | |
|                             Objects.ParseOwnershipChange(reader);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.Authenticated)
 | |
|                         {
 | |
|                             ParseAuthenticated(reader);
 | |
|                         }
 | |
|                         else if (packetId == PacketId.Disconnect)
 | |
|                         {
 | |
|                             reader.Clear();
 | |
|                             StopConnection();
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
| 
 | |
|                             NetworkManager.LogError($"Client received an unhandled PacketId of {(ushort)packetId}. Remaining data has been purged.");
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|                             _parseLogger.Print(NetworkManager);
 | |
| #endif
 | |
|                             return;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 /* Iterate cache when reader is emptied.
 | |
|                  * This is incase the last packet received
 | |
|                  * was a spawned, which wouldn't trigger
 | |
|                  * the above iteration. There's no harm
 | |
|                  * in doing this check multiple times as there's
 | |
|                  * an exit early check. */
 | |
|                 Objects.IterateObjectCache();
 | |
|             }
 | |
| #if !UNITY_EDITOR && !DEVELOPMENT_BUILD
 | |
|             }
 | |
|             catch (Exception e)
 | |
|             {
 | |
|                 if (NetworkManager.CanLog(LoggingType.Error))
 | |
|                     Debug.LogError($"Client encountered an error while parsing data for packetId {packetId}. Message: {e.Message}.");
 | |
|             }
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses a PingPong packet.
 | |
|         /// </summary>
 | |
|         /// <param name="reader"></param>
 | |
|         private void ParsePingPong(PooledReader reader)
 | |
|         {
 | |
|             uint clientTick = reader.ReadUInt32(AutoPackType.Unpacked);
 | |
|             NetworkManager.TimeManager.ModifyPing(clientTick);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Parses a received connectionId. This is received before client receives connection state change.
 | |
|         /// </summary>
 | |
|         /// <param name="reader"></param>
 | |
|         private void ParseAuthenticated(PooledReader reader)
 | |
|         {
 | |
|             NetworkManager networkManager = NetworkManager;
 | |
|             int connectionId = reader.ReadNetworkConnectionId();
 | |
|             //If only a client then make a new connection.
 | |
|             if (!networkManager.IsServer)
 | |
|             {
 | |
|                 Clients.TryGetValueIL2CPP(connectionId, out Connection);
 | |
|             }
 | |
|             /* If also the server then use the servers connection
 | |
|              * for the connectionId. This is to resolve host problems
 | |
|              * where LocalConnection for client differs from the server Connection
 | |
|              * reference, which results in different field values. */
 | |
|             else
 | |
|             {
 | |
|                 if (networkManager.ServerManager.Clients.TryGetValueIL2CPP(connectionId, out NetworkConnection conn))
 | |
|                 {
 | |
|                     Connection = conn;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     networkManager.LogError($"Unable to lookup LocalConnection for {connectionId} as host.");
 | |
|                     Connection = new NetworkConnection(networkManager, connectionId, false);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             //If predicted spawning is enabled also get reserved Ids.
 | |
|             if (NetworkManager.PredictionManager.GetAllowPredictedSpawning())
 | |
|             {
 | |
|                 byte count = reader.ReadByte();
 | |
|                 Queue<int> q = Connection.PredictedObjectIds;
 | |
|                 for (int i = 0; i < count; i++)
 | |
|                     q.Enqueue(reader.ReadNetworkObjectId());
 | |
|             }
 | |
| 
 | |
|             /* Set the TimeManager tick to lastReceivedTick.
 | |
|              * This still doesn't account for latency but
 | |
|              * it's the best we can do until the client gets
 | |
|              * a ping response. */
 | |
|             networkManager.TimeManager.Tick = networkManager.TimeManager.LastPacketTick;
 | |
| 
 | |
|             //Mark as authenticated.
 | |
|             Connection.ConnectionAuthenticated();
 | |
|             OnAuthenticated?.Invoke();
 | |
|             /* Register scene objects for all scenes
 | |
|              * after being authenticated. This is done after
 | |
|              * authentication rather than when the connection
 | |
|              * is started because if also as server an online
 | |
|              * scene may already be loaded on server, but not
 | |
|              * for client. This means the sceneLoaded unity event
 | |
|              * won't fire, and since client isn't authenticated
 | |
|             * at the connection start phase objects won't be added. */
 | |
|             Objects.RegisterAndDespawnSceneObjects();
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
| }
 |