746 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			746 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using FishNet.Connection;
 | |
| using FishNet.Documenting;
 | |
| using FishNet.Managing.Predicting;
 | |
| using FishNet.Managing.Timing;
 | |
| using FishNet.Object.Prediction;
 | |
| using FishNet.Object.Prediction.Delegating;
 | |
| using FishNet.Serializing;
 | |
| using FishNet.Serializing.Helping;
 | |
| using FishNet.Transporting;
 | |
| using FishNet.Utility.Constant;
 | |
| using FishNet.Utility.Extension;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Runtime.CompilerServices;
 | |
| using UnityEngine;
 | |
| using UnityScene = UnityEngine.SceneManagement.Scene;
 | |
| 
 | |
| [assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
 | |
| namespace FishNet.Object
 | |
| {
 | |
| 
 | |
|     public abstract partial class NetworkBehaviour : MonoBehaviour
 | |
|     {
 | |
|         #region Public.
 | |
|         /// <summary>
 | |
|         /// 
 | |
|         /// </summary>
 | |
|         private uint _lastReconcileTick;
 | |
|         /// <summary>
 | |
|         /// Gets the last tick this NetworkBehaviour reconciled with.
 | |
|         /// </summary>
 | |
|         public uint GetLastReconcileTick() => _lastReconcileTick;
 | |
| 
 | |
|         internal void SetLastReconcileTick(uint value, bool updateGlobals = true)
 | |
|         {
 | |
|             _lastReconcileTick = value;
 | |
|             if (updateGlobals)
 | |
|                 PredictionManager.LastReconcileTick = value;
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// 
 | |
|         /// </summary>
 | |
|         private uint _lastReplicateTick;
 | |
|         /// <summary>
 | |
|         /// Gets the last tick this NetworkBehaviour replicated with.
 | |
|         /// </summary>
 | |
|         public uint GetLastReplicateTick() => _lastReplicateTick;
 | |
|         /// <summary>
 | |
|         /// Sets the last tick this NetworkBehaviour replicated with.
 | |
|         /// For internal use only.
 | |
|         /// </summary>
 | |
|         private void SetLastReplicateTick(uint value, bool updateGlobals = true)
 | |
|         {
 | |
|             _lastReplicateTick = value;
 | |
|             if (updateGlobals)
 | |
|             {
 | |
|                 Owner.LocalReplicateTick = TimeManager.LocalTick;
 | |
|                 PredictionManager.LastReplicateTick = value;
 | |
|             }
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// True if this object is reconciling.
 | |
|         /// </summary>
 | |
|         public bool IsReconciling { get; internal set; }
 | |
|         #endregion
 | |
| 
 | |
|         #region Private.
 | |
|         /// <summary>
 | |
|         /// Registered Replicate methods.
 | |
|         /// </summary>
 | |
|         private readonly Dictionary<uint, ReplicateRpcDelegate> _replicateRpcDelegates = new Dictionary<uint, ReplicateRpcDelegate>();
 | |
|         /// <summary>
 | |
|         /// Registered Reconcile methods.
 | |
|         /// </summary>
 | |
|         private readonly Dictionary<uint, ReconcileRpcDelegate> _reconcileRpcDelegates = new Dictionary<uint, ReconcileRpcDelegate>();
 | |
|         /// <summary>
 | |
|         /// True if initialized compnents for prediction.
 | |
|         /// </summary>
 | |
|         private bool _predictionInitialized;
 | |
|         /// <summary>
 | |
|         /// Rigidbody found on this object. This is used for prediction.
 | |
|         /// </summary>
 | |
|         private Rigidbody _predictionRigidbody;
 | |
|         /// <summary>
 | |
|         /// Rigidbody2D found on this object. This is used for prediction.
 | |
|         /// </summary>
 | |
|         private Rigidbody2D _predictionRigidbody2d;
 | |
|         /// <summary>
 | |
|         /// Last position for TransformMayChange.
 | |
|         /// </summary>
 | |
|         private Vector3 _lastMayChangePosition;
 | |
|         /// <summary>
 | |
|         /// Last rotation for TransformMayChange.
 | |
|         /// </summary>
 | |
|         private Quaternion _lastMayChangeRotation;
 | |
|         /// <summary>
 | |
|         /// Last scale for TransformMayChange.
 | |
|         /// </summary>
 | |
|         private Vector3 _lastMayChangeScale;
 | |
|         /// <summary>
 | |
|         /// Number of resends which may occur. This could be for client resending replicates to the server or the server resending reconciles to the client.
 | |
|         /// </summary>
 | |
|         private int _remainingResends;
 | |
|         /// <summary>
 | |
|         /// Last enqueued replicate tick on the server.
 | |
|         /// </summary> 
 | |
|         private uint _lastReceivedReplicateTick;
 | |
|         /// <summary>
 | |
|         /// Last tick of a reconcile received from the server.
 | |
|         /// </summary>
 | |
|         private uint _lastReceivedReconcileTick;
 | |
|         /// <summary>
 | |
|         /// True if the client has cached reconcile 
 | |
|         /// </summary>
 | |
|         private bool _clientHasReconcileData;
 | |
|         #endregion
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Registers a RPC method.
 | |
|         /// Internal use.
 | |
|         /// </summary>
 | |
|         /// <param name="hash"></param>
 | |
|         /// <param name="del"></param>
 | |
|         [APIExclude]
 | |
|         [CodegenMakePublic]
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         protected internal void RegisterReplicateRpc_Internal(uint hash, ReplicateRpcDelegate del)
 | |
|         {
 | |
|             _replicateRpcDelegates[hash] = del;
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Registers a RPC method.
 | |
|         /// Internal use.
 | |
|         /// </summary>
 | |
|         /// <param name="hash"></param>
 | |
|         /// <param name="del"></param>
 | |
|         [APIExclude]
 | |
|         [CodegenMakePublic]
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         protected internal void RegisterReconcileRpc_Internal(uint hash, ReconcileRpcDelegate del)
 | |
|         {
 | |
|             _reconcileRpcDelegates[hash] = del;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when a replicate is received.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         internal void OnReplicateRpc(uint? methodHash, PooledReader reader, NetworkConnection sendingClient, Channel channel)
 | |
|         {
 | |
|             if (methodHash == null)
 | |
|                 methodHash = ReadRpcHash(reader);
 | |
| 
 | |
|             if (sendingClient == null)
 | |
|             {
 | |
|                 _networkObjectCache.NetworkManager.LogError($"NetworkConnection is null. Replicate {methodHash.Value} on {gameObject.name}, behaviour {GetType().Name} will not complete. Remainder of packet may become corrupt.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (_replicateRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ReplicateRpcDelegate del))
 | |
|                 del.Invoke(reader, sendingClient, channel);
 | |
|             else
 | |
|                 _networkObjectCache.NetworkManager.LogWarning($"Replicate not found for hash {methodHash.Value} on {gameObject.name}, behaviour {GetType().Name}. Remainder of packet may become corrupt.");
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when a reconcile is received.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         internal void OnReconcileRpc(uint? methodHash, PooledReader reader, Channel channel)
 | |
|         {
 | |
|             if (methodHash == null)
 | |
|                 methodHash = ReadRpcHash(reader);
 | |
| 
 | |
|             if (_reconcileRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ReconcileRpcDelegate del))
 | |
|                 del.Invoke(reader, channel);
 | |
|             else
 | |
|                 _networkObjectCache.NetworkManager.LogWarning($"Reconcile not found for hash {methodHash.Value}. Remainder of packet may become corrupt.");
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Clears cached replicates. This can be useful to call on server and client after teleporting.
 | |
|         /// </summary>
 | |
|         /// <param name="asServer">True to reset values for server, false to reset values for client.</param>
 | |
|         public void ClearReplicateCache(bool asServer)
 | |
|         {
 | |
|             ResetLastPredictionTicks();
 | |
|             ClearReplicateCache_Internal(asServer);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Clears cached replicates for server and client. This can be useful to call on server and client after teleporting.
 | |
|         /// </summary>
 | |
|         public void ClearReplicateCache()
 | |
|         {
 | |
|             ResetLastPredictionTicks();
 | |
|             ClearReplicateCache_Internal(true);
 | |
|             ClearReplicateCache_Internal(false);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Resets last predirection tick values.
 | |
|         /// </summary>
 | |
|         private void ResetLastPredictionTicks()
 | |
|         {
 | |
|             _lastSentReplicateTick = 0;
 | |
|             _lastReceivedReplicateTick = 0;
 | |
|             _lastReceivedReconcileTick = 0;
 | |
|             SetLastReconcileTick(0, false);
 | |
|             SetLastReplicateTick(0, false);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Clears cached replicates.
 | |
|         /// For internal use only.
 | |
|         /// </summary>
 | |
|         /// <param name="asServer"></param>
 | |
|         [CodegenMakePublic]
 | |
|         [APIExclude]
 | |
|         protected internal virtual void ClearReplicateCache_Internal(bool asServer) { }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes number of past inputs from buffer to writer and sends it to the server.
 | |
|         /// Internal use. 
 | |
|         /// </summary>
 | |
|         [CodegenMakePublic]
 | |
|         [APIExclude]
 | |
|         private void SendReplicateRpc<T>(uint hash, List<T> replicates, Channel channel) where T : IReplicateData
 | |
|         {
 | |
|             if (!IsSpawnedWithWarning())
 | |
|                 return;
 | |
| 
 | |
|             int bufferCount = replicates.Count;
 | |
|             int lastBufferIndex = (bufferCount - 1);
 | |
|             //Nothing to send; should never be possible.
 | |
|             if (lastBufferIndex < 0)
 | |
|                 return;
 | |
| 
 | |
|             //Number of past inputs to send.
 | |
|             int pastInputs = Mathf.Min(PredictionManager.GetRedundancyCount(), bufferCount);
 | |
|             /* Where to start writing from. When passed
 | |
|              * into the writer values from this offset
 | |
|              * and forward will be written. */
 | |
|             int offset = bufferCount - pastInputs;
 | |
|             if (offset < 0)
 | |
|                 offset = 0;
 | |
| 
 | |
|             uint lastReplicateTick = _lastSentReplicateTick;
 | |
|             if (lastReplicateTick > 0)
 | |
|             {
 | |
|                 uint diff = TimeManager.LocalTick - GetLastReplicateTick();
 | |
|                 offset += (int)diff - 1;
 | |
|                 if (offset >= replicates.Count)
 | |
|                     return;
 | |
|             }
 | |
| 
 | |
|             _lastSentReplicateTick = TimeManager.LocalTick;
 | |
| 
 | |
|             //Write history to methodWriter.
 | |
|             PooledWriter methodWriter = WriterPool.GetWriter(WriterPool.LENGTH_BRACKET);
 | |
|             methodWriter.WriteReplicate<T>(replicates, offset);
 | |
|             PooledWriter writer;
 | |
|             //if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
 | |
|             //writer = CreateLinkedRpc(link, methodWriter, Channel.Unreliable);
 | |
|             //else //todo add support for -> server rpc links.
 | |
| 
 | |
|             writer = CreateRpc(hash, methodWriter, PacketId.Replicate, channel);
 | |
|             NetworkManager.TransportManager.SendToServer((byte)channel, writer.GetArraySegment(), false);
 | |
| 
 | |
|             /* If being sent as reliable then clear buffer
 | |
|              * since we know it will get there. 
 | |
|              * Also reset remaining resends. */
 | |
|             if (channel == Channel.Reliable)
 | |
|             {
 | |
|                 replicates.Clear();
 | |
|                 _remainingResends = 0;
 | |
|             }
 | |
| 
 | |
|             methodWriter.DisposeLength();
 | |
|             writer.DisposeLength();
 | |
|         }
 | |
| 
 | |
|         private uint _lastSentReplicateTick;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends a RPC to target.
 | |
|         /// Internal use.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         [CodegenMakePublic]
 | |
|         [APIExclude]
 | |
|         internal void SendReconcileRpc<T>(uint hash, T reconcileData, Channel channel)
 | |
|         {
 | |
|             if (!IsSpawned)
 | |
|                 return;
 | |
|             if (!Owner.IsActive)
 | |
|                 return;
 | |
| 
 | |
|             PooledWriter methodWriter = WriterPool.GetWriter();
 | |
|             methodWriter.WriteUInt32(GetLastReplicateTick());
 | |
|             methodWriter.Write(reconcileData);
 | |
| 
 | |
|             PooledWriter writer;
 | |
| #if UNITY_EDITOR || DEVELOPMENT_BUILD
 | |
|             if (NetworkManager.DebugManager.ReconcileRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
 | |
| #else
 | |
|             if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
 | |
| #endif
 | |
|                 writer = CreateLinkedRpc(link, methodWriter, channel);
 | |
|             else
 | |
|                 writer = CreateRpc(hash, methodWriter, PacketId.Reconcile, channel);
 | |
| 
 | |
|             _networkObjectCache.NetworkManager.TransportManager.SendToClient((byte)channel, writer.GetArraySegment(), Owner);
 | |
| 
 | |
|             methodWriter.Dispose();
 | |
|             writer.Dispose();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns if there is a chance the transform may change after the tick.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         protected internal bool TransformMayChange()
 | |
|         {
 | |
|             if (!_predictionInitialized)
 | |
|             {
 | |
|                 _predictionInitialized = true;
 | |
|                 _predictionRigidbody = GetComponentInParent<Rigidbody>();
 | |
|                 _predictionRigidbody2d = GetComponentInParent<Rigidbody2D>();
 | |
|             }
 | |
| 
 | |
|             /* Use distance when checking if changed because rigidbodies can twitch
 | |
|              * or move an extremely small amount. These small moves are not worth
 | |
|              * resending over because they often fix themselves each frame. */
 | |
|             float changeDistance = 0.000004f;
 | |
| 
 | |
|             bool positionChanged = (transform.position - _lastMayChangePosition).sqrMagnitude > changeDistance;
 | |
|             bool rotationChanged = (transform.rotation.eulerAngles - _lastMayChangeRotation.eulerAngles).sqrMagnitude > changeDistance;
 | |
|             bool scaleChanged = (transform.localScale - _lastMayChangeScale).sqrMagnitude > changeDistance;
 | |
|             bool transformChanged = (positionChanged || rotationChanged || scaleChanged);
 | |
|             /* Returns true if transform.hasChanged, or if either
 | |
|              * of the rigidbodies have velocity. */
 | |
|             bool changed = (
 | |
|                 transformChanged ||
 | |
|                 (_predictionRigidbody != null && (_predictionRigidbody.velocity != Vector3.zero || _predictionRigidbody.angularVelocity != Vector3.zero)) ||
 | |
|                 (_predictionRigidbody2d != null && (_predictionRigidbody2d.velocity != Vector2.zero || _predictionRigidbody2d.angularVelocity != 0f))
 | |
|                 );
 | |
| 
 | |
|             //If transform changed update last values.
 | |
|             if (transformChanged)
 | |
|             {
 | |
|                 _lastMayChangePosition = transform.position;
 | |
|                 _lastMayChangeRotation = transform.rotation;
 | |
|                 _lastMayChangeScale = transform.localScale;
 | |
|             }
 | |
| 
 | |
|             return changed;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Checks conditions for a replicate.
 | |
|         /// </summary>
 | |
|         /// <param name="asServer">True if checking as server.</param>
 | |
|         /// <returns>Returns true if to exit the replicate early.</returns>
 | |
|         [CodegenMakePublic] //internal
 | |
|         [APIExclude]
 | |
|         public bool Replicate_ExitEarly_A_Internal(bool asServer, bool replaying)
 | |
|         {
 | |
|             bool isOwner = IsOwner;
 | |
|             //Server.
 | |
|             if (asServer)
 | |
|             {
 | |
|                 //No owner, do not try to replicate 'owner' input.
 | |
|                 if (!Owner.IsActive)
 | |
|                 {
 | |
|                     ClearReplicateCache(true);
 | |
|                     return true;
 | |
|                 }
 | |
|                 //Is client host, no need to use CSP; trust client.
 | |
|                 if (isOwner)
 | |
|                 {
 | |
|                     ClearReplicateCache();
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
|             //Client.
 | |
|             else
 | |
|             {
 | |
|                 //Server does not replay; this should never happen.
 | |
|                 if (replaying && IsServer)
 | |
|                     return true;
 | |
|                 //Spectators cannot replicate.
 | |
|                 if (!isOwner)
 | |
|                 {
 | |
|                     ClearReplicateCache(false);
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             //Checks pass.
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Clears a Queue.
 | |
|         /// This is used as a patch for Unity 2022 Burst compiler bugs.
 | |
|         /// Using Queue.Clear via codegen throws for an unknown reason.
 | |
|         /// </summary>
 | |
|         public void ClearQueue_Server_Internal<T>(Queue<T> q)
 | |
|         {
 | |
|             q.Clear();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Gets the next replicate in queue.
 | |
|         /// </summary>
 | |
|         [CodegenMakePublic] //internal
 | |
|         [APIExclude]
 | |
|         public void Replicate_Server_Internal<T>(ReplicateUserLogicDelegate<T> del, Queue<T> q, Channel channel) where T : IReplicateData
 | |
|         {
 | |
|             int count = q.Count;
 | |
|             if (count > 0)
 | |
|             {
 | |
|                 ReplicateData(q.Dequeue());
 | |
|                 count--;
 | |
| 
 | |
|                 PredictionManager pm = PredictionManager;
 | |
|                 bool consumeExcess = !pm.DropExcessiveReplicates;
 | |
|                 //Number of entries to leave in buffer when consuming.
 | |
|                 const int leaveInBuffer = 2;
 | |
|                 //Only consume if the queue count is over leaveInBuffer.
 | |
|                 if (consumeExcess && count > leaveInBuffer)
 | |
|                 {
 | |
|                     byte maximumAllowedConsumes = pm.GetMaximumConsumeCount();
 | |
|                     int maximumPossibleConsumes = (count - leaveInBuffer);
 | |
|                     int consumeAmount = Mathf.Min(maximumAllowedConsumes, maximumPossibleConsumes);
 | |
| 
 | |
|                     for (int i = 0; i < consumeAmount; i++)
 | |
|                         ReplicateData(q.Dequeue());
 | |
|                 }
 | |
| 
 | |
|                 void ReplicateData(T data)
 | |
|                 {
 | |
|                     uint tick = data.GetTick();
 | |
|                     SetLastReplicateTick(tick);
 | |
|                     del.Invoke(data, true, channel, false);
 | |
|                 }
 | |
| 
 | |
|                 _remainingResends = pm.GetRedundancyCount();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 del.Invoke(default, true, channel, false);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns the maximum amount of replicates when consuming multiple inputs per tick.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         private int GetMaximumReplicatesWhenConsuming() => (TimeManager.TickRate * 3);
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns if a replicates data changed and updates resends as well data tick.
 | |
|         /// </summary>
 | |
|         /// <param name="enqueueData">True to enqueue data for replaying.</param>
 | |
|         /// <returns>True if data has changed..</returns>
 | |
|         [CodegenMakePublic] //internal
 | |
|         [APIExclude]
 | |
|         public void Replicate_Client_Internal<T>(ReplicateUserLogicDelegate<T> del, uint methodHash, List<T> replicates, T data, Channel channel) where T : IReplicateData
 | |
|         {
 | |
|             //Only check to enqueu/send if not clientHost.
 | |
|             if (!IsServer)
 | |
|             {
 | |
|                 Func<T, bool> isDefaultDel = GeneratedComparer<T>.IsDefault;
 | |
|                 if (isDefaultDel == null)
 | |
|                 {
 | |
|                     NetworkManager.LogError($"ReplicateComparers not found for type {typeof(T).FullName}");
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 //If there's no datas then reset last replicate send tick.
 | |
|                 if (replicates.Count == 0)
 | |
|                     _lastSentReplicateTick = 0;
 | |
| 
 | |
|                 PredictionManager pm = NetworkManager.PredictionManager;
 | |
| 
 | |
|                 bool isDefault = isDefaultDel.Invoke(data);
 | |
|                 bool mayChange = TransformMayChange();
 | |
|                 bool resetResends = (pm.UsingRigidbodies || mayChange || !isDefault);
 | |
|                 /* If there is going to be a resend then enqueue data no matter what.
 | |
|                  * Then ensures there are no data gaps for ticks. EG, input may
 | |
|                  * look like this...
 | |
|                  * Move - tick 0.
 | |
|                  * Idle - tick 1.
 | |
|                  * Move - tick 2.
 | |
|                  * 
 | |
|                  * If there were no 'using rigidbodies' then resetResends may be false.
 | |
|                  * As result the queue would be filled like this...
 | |
|                  * Move - tick 0.
 | |
|                  * Move - tick 2.
 | |
|                  * 
 | |
|                  * The ticks are not sent per data, just once and incremented once per data.
 | |
|                  * Due to this the results would actually be...
 | |
|                  * Move - tick 0.
 | |
|                  * Move - tick 1 (should be tick 2!).
 | |
|                  * 
 | |
|                  * But by including data if there will be resends the defaults will become added. */
 | |
|                 if (resetResends)
 | |
|                     _remainingResends = pm.GetRedundancyCount();
 | |
| 
 | |
|                 bool enqueueData = (_remainingResends > 0);
 | |
|                 if (enqueueData)
 | |
|                 {
 | |
|                     /* Replicates will be limited to 1 second
 | |
|                      * worth on the client. That means the client
 | |
|                      * will only lose replays if they do not receive
 | |
|                      * a response back from the server for over a second.
 | |
|                      * When a client drops a replay it does not necessarily mean
 | |
|                      * they will be out of synchronization, but rather they
 | |
|                      * will not be able to reconcile that tick. */
 | |
|                     /* Even though limit is 1 second only remove entries if over 2 seconds
 | |
|                      * to prevent constant remove calls to the collection. */
 | |
|                     int maximumReplicates = (TimeManager.TickRate * 2);
 | |
|                     //If over then remove half the replicates.
 | |
|                     if (replicates.Count >= maximumReplicates)
 | |
|                     {
 | |
|                         int removeCount = (maximumReplicates / 2);
 | |
|                         //Dispose first.
 | |
|                         for (int i = 0; i < removeCount; i++)
 | |
|                             replicates[i].Dispose();
 | |
|                         //Then remove.
 | |
|                         replicates.RemoveRange(0, removeCount);
 | |
|                     }
 | |
| 
 | |
|                     uint localTick = TimeManager.LocalTick;
 | |
|                     //Update tick on the data to current.
 | |
|                     data.SetTick(localTick);
 | |
|                     //Add to collection.
 | |
|                     replicates.Add(data);
 | |
|                 }
 | |
| 
 | |
|                 //If theres resends left.
 | |
|                 if (_remainingResends > 0)
 | |
|                 {
 | |
|                     _remainingResends--;
 | |
|                     SendReplicateRpc<T>(methodHash, replicates, channel);
 | |
|                     //Update last replicate tick.
 | |
|                     SetLastReplicateTick(TimeManager.LocalTick);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             del.Invoke(data, false, channel, false);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Reads a replicate the client.
 | |
|         /// </summary>
 | |
|         public void Replicate_Reader_Internal<T>(PooledReader reader, NetworkConnection sender, T[] arrBuffer, Queue<T> replicates, Channel channel) where T : IReplicateData
 | |
|         {
 | |
|             int receivedReplicatesCount = reader.ReadReplicate<T>(ref arrBuffer, TimeManager.LastPacketTick);
 | |
|             if (!OwnerMatches(sender))
 | |
|                 return;
 | |
| 
 | |
|             PredictionManager pm = PredictionManager;
 | |
|             bool consumeExcess = !pm.DropExcessiveReplicates;
 | |
|             //Maximum number of replicates allowed to be queued at once.
 | |
|             int replicatesCountLimit = (consumeExcess) ?
 | |
|                 GetMaximumReplicatesWhenConsuming() : pm.GetMaximumServerReplicates();
 | |
| 
 | |
|             for (int i = 0; i < receivedReplicatesCount; i++)
 | |
|             {
 | |
|                 uint tick = arrBuffer[i].GetTick();
 | |
|                 if (tick > _lastReceivedReplicateTick)
 | |
|                 {
 | |
|                     //Cannot queue anymore, discard oldest.
 | |
|                     if (replicates.Count >= replicatesCountLimit)
 | |
|                     {
 | |
|                         T data = replicates.Dequeue();
 | |
|                         data.Dispose();
 | |
|                     }
 | |
| 
 | |
|                     replicates.Enqueue(arrBuffer[i]);
 | |
|                     _lastReceivedReplicateTick = tick;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Checks conditions for a reconcile.
 | |
|         /// </summary>
 | |
|         /// <param name="asServer">True if checking as server.</param>
 | |
|         /// <returns>Returns true if able to continue.</returns>
 | |
|         [CodegenMakePublic] //internal
 | |
|         [APIExclude]
 | |
|         public bool Reconcile_ExitEarly_A_Internal(bool asServer, out Channel channel)
 | |
|         {
 | |
|             channel = Channel.Unreliable;
 | |
|             //Server.
 | |
|             if (asServer)
 | |
|             {
 | |
|                 if (_remainingResends <= 0)
 | |
|                     return true;
 | |
| 
 | |
|                 _remainingResends--;
 | |
|                 if (_remainingResends == 0)
 | |
|                     channel = Channel.Reliable;
 | |
|             }
 | |
|             //Client.
 | |
|             else
 | |
|             {
 | |
|                 if (!_clientHasReconcileData)
 | |
|                     return true;
 | |
| 
 | |
|                 _clientHasReconcileData = false;
 | |
|                 /* If clientHost then invoke reconciles but
 | |
|                  * don't actually reconcile. This is done
 | |
|                  * because certain user code may
 | |
|                  * rely on those events running even as host. */
 | |
|                 if (IsServer)
 | |
|                 {
 | |
|                     PredictionManager.InvokeOnReconcile_Internal(this, true);
 | |
|                     PredictionManager.InvokeOnReconcile_Internal(this, false);
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             //Checks pass.
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Updates lastReconcileTick as though running asServer.
 | |
|         /// </summary>
 | |
|         /// <param name="ird">Data to set tick on.</param>
 | |
|         public void Reconcile_Server_Internal<T>(uint methodHash, T data, Channel channel) where T : IReconcileData
 | |
|         {
 | |
|             //Server always uses last replicate tick as reconcile tick.
 | |
|             uint tick = _lastReplicateTick;
 | |
|             data.SetTick(tick);
 | |
|             SetLastReconcileTick(tick);
 | |
| 
 | |
|             PredictionManager.InvokeServerReconcile(this, true);
 | |
|             SendReconcileRpc(methodHash, data, channel);
 | |
|             PredictionManager.InvokeServerReconcile(this, false);
 | |
| 
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Processes a reconcile for client.
 | |
|         /// </summary>
 | |
|         public void Reconcile_Client_Internal<T, T2>(ReconcileUserLogicDelegate<T> reconcileDel, ReplicateUserLogicDelegate<T2> replicateULDel, List<T2> replicates, T data, Channel channel) where T : IReconcileData where T2 : IReplicateData
 | |
|         {
 | |
|             uint tick = data.GetTick();
 | |
| 
 | |
|             /* If the first entry in cllection has a tick higher than
 | |
|              * the received tick then something went wrong, do not reconcile. */
 | |
|             if (replicates.Count > 0 && replicates[0].GetTick() > tick)
 | |
|                 return;
 | |
| 
 | |
|             UnityScene scene = gameObject.scene;
 | |
|             PhysicsScene ps = scene.GetPhysicsScene();
 | |
|             PhysicsScene2D ps2d = scene.GetPhysicsScene2D();
 | |
| 
 | |
|             //This must be set before reconcile is invoked.
 | |
|             SetLastReconcileTick(tick);
 | |
|             //Invoke that reconcile is starting.
 | |
|             PredictionManager.InvokeOnReconcile_Internal(this, true);
 | |
|             //Call reconcile user logic.
 | |
|             reconcileDel?.Invoke(data, false, channel);
 | |
| 
 | |
|             //True if the timemanager is handling physics simulations.
 | |
|             bool tmPhysics = (TimeManager.PhysicsMode == PhysicsMode.TimeManager);
 | |
|             //Sync transforms if using tm physics.
 | |
|             if (tmPhysics)
 | |
|             {
 | |
|                 Physics.SyncTransforms();
 | |
|                 Physics2D.SyncTransforms();
 | |
|             }
 | |
| 
 | |
|             //Remove excess from buffered inputs.
 | |
|             int queueIndex = -1;
 | |
|             for (int i = 0; i < replicates.Count; i++)
 | |
|             {
 | |
|                 if (replicates[i].GetTick() == tick)
 | |
|                 {
 | |
|                     queueIndex = i;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             //Now found, weird.
 | |
|             if (queueIndex == -1)
 | |
|                 replicates.Clear();
 | |
|             //Remove up to found, including it.
 | |
|             else
 | |
|                 replicates.RemoveRange(0, queueIndex + 1);
 | |
| 
 | |
|             //Number of replays which will be performed.
 | |
|             int replays = replicates.Count;
 | |
|             float tickDelta = (float)TimeManager.TickDelta;
 | |
| 
 | |
|             for (int i = 0; i < replays; i++)
 | |
|             {
 | |
|                 T2 rData = replicates[i];
 | |
|                 uint replayTick = rData.GetTick();
 | |
| 
 | |
|                 PredictionManager.InvokeOnReplicateReplay_Internal(scene, replayTick, ps, ps2d, true);
 | |
| 
 | |
|                 //Replay the data using the replicate logic delegate.
 | |
|                 replicateULDel.Invoke(rData, false, channel, true);
 | |
|                 if (tmPhysics)
 | |
|                 {
 | |
|                     ps.Simulate(tickDelta);
 | |
|                     ps2d.Simulate(tickDelta);
 | |
|                 }
 | |
| 
 | |
|                 PredictionManager.InvokeOnReplicateReplay_Internal(scene, replayTick, ps, ps2d, false);
 | |
|             }
 | |
| 
 | |
|             //Reconcile ended.
 | |
|             PredictionManager.InvokeOnReconcile_Internal(this, false);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Reads a reconcile the client.
 | |
|         /// </summary>
 | |
|         public void Reconcile_Reader_Internal<T>(PooledReader reader, ref T data, Channel channel) where T : IReconcileData
 | |
|         {
 | |
|             uint tick = reader.ReadUInt32();
 | |
|             T newData = reader.Read<T>();
 | |
| 
 | |
|             //Tick is old or already processed.
 | |
|             if (tick <= _lastReceivedReconcileTick)
 | |
|                 return;
 | |
|             //Only owner reconciles. Maybe ownership changed then packet arrived out of order.
 | |
|             if (!IsOwner)
 | |
|                 return;
 | |
| 
 | |
|             data = newData;
 | |
|             data.SetTick(tick);
 | |
|             _clientHasReconcileData = true;
 | |
|             _lastReceivedReconcileTick = tick;
 | |
|         }
 | |
| 
 | |
|     }
 | |
| } |