diff --git a/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs b/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs index fb762212..bc3fb26b 100644 --- a/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs +++ b/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs @@ -162,6 +162,20 @@ public void InitializeState() #endregion #region Public. + /// + /// Called before reconcile cycle begin. + /// + public event ReconcileBeginDel OnReconcileBegin; + + public delegate void ReconcileBeginDel(); + + /// + /// Called after reconcile cycle end. + /// + public event ReconcileEndDel OnReconcileEnd; + + public delegate void ReconcileEndDel(); + /// /// Called before performing a reconcile. Contains the client and server tick the reconcile is for. /// @@ -322,8 +336,12 @@ public void SetStateOrder(ReplicateStateOrder stateOrder) * it from replicates queue. */ if (stateOrder == ReplicateStateOrder.Inserted && _networkManager.IsClientStarted) { - foreach (NetworkObject item in _networkManager.ClientManager.Objects.Spawned.Values) + using var enumerator = _networkManager.ClientManager.Objects.Spawned.Values.GetEnumerator(); + while (enumerator.MoveNext()) + { + NetworkObject item = enumerator.Current!; item.EmptyReplicatesQueueIntoHistory(); + } } } @@ -377,7 +395,7 @@ public void SetStateOrder(ReplicateStateOrder stateOrder) private uint _lastOrderedReadReconcileTick; /// /// - private NetworkTrafficStatistics _networkTrafficStatistics; + [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; /// /// /// @@ -393,12 +411,15 @@ public void SetStateOrder(ReplicateStateOrder stateOrder) #endregion #region Private Profiler Markers + private static readonly ProfilerMarker _pm_ReconcileToStates = new("PredictionManager.ReconcileToStates()"); + private static readonly ProfilerMarker _pm_OnReconcileBegin = new("PredictionManager.OnReconcileBegin()"); + private static readonly ProfilerMarker _pm_OnReconcileEnd = new("PredictionManager.OnReconcileEnd()"); private static readonly ProfilerMarker _pm_OnLateUpdate = new("PredictionManager.TimeManager_OnLateUpdate()"); private static readonly ProfilerMarker _pm_OnPreReconcile = new("PredictionManager.OnPreReconcile(uint, uint)"); private static readonly ProfilerMarker _pm_OnReconcile = new("PredictionManager.OnReconcile(uint, uint)"); private static readonly ProfilerMarker _pm_OnPrePhysicsTransformSync = new("PredictionManager.OnPrePhysicsTransformSync(uint, uint)"); - //private static readonly ProfilerMarker _pm_PhysicsSyncTransforms = new("PredictionManager.Physics.SyncTransforms()"); - //private static readonly ProfilerMarker _pm_Physics2DSyncTransforms = new("PredictionManager.Physics2D.SyncTransforms()"); + private static readonly ProfilerMarker _pm_PhysicsSyncTransforms = new("PredictionManager.Physics.SyncTransforms()"); + private static readonly ProfilerMarker _pm_Physics2DSyncTransforms = new("PredictionManager.Physics2D.SyncTransforms()"); private static readonly ProfilerMarker _pm_OnPostPhysicsTransformSync = new("PredictionManager.OnPostPhysicsTransformSync(uint, uint)"); private static readonly ProfilerMarker _pm_OnPostReconcileSyncTransforms = new("PredictionManager.OnPostReconcileSyncTransforms(uint, uint)"); private static readonly ProfilerMarker _pm_OnPreReplicateReplay = new("PredictionManager.OnPreReplicateReplay(uint, uint)"); @@ -551,198 +572,214 @@ private void TimeManager_OnLateUpdate() /// internal void ReconcileToStates() { - if (!_networkManager.IsClientStarted) - return; - - if (_reconcileStates.Count == 0) - return; + using (_pm_ReconcileToStates.Auto()) + { + if (!_networkManager.IsClientStarted) + return; - TimeManager tm = _networkManager.TimeManager; - uint localTick = tm.LocalTick; - uint lastLocalTickCompleted = localTick; + if (_reconcileStates.Count == 0) + return; - //Check the first state; if it cannot be run no need to continue. - if (!CanStateBeReconciled(_reconcileStates.Peek())) - return; + TimeManager tm = _networkManager.TimeManager; + uint localTick = tm.LocalTick; + uint lastLocalTickCompleted = localTick; - /* Discard reconciles which are not needed due to - * repetitiveness. */ - StatePacket statePacket; + //Check the first state; if it cannot be run no need to continue. + if (!CanStateBeReconciled(_reconcileStates.Peek())) + return; + + using (_pm_OnReconcileBegin.Auto()) + OnReconcileBegin?.Invoke(); + + /* Discard reconciles which are not needed due to + * repetitiveness. */ + StatePacket statePacket; + + /* Since Inserted only runs inputs on reconciles + * it must use the first state to ensure all + * possible inputs are run, starting at the oldest (first state). */ + if (StateOrder == ReplicateStateOrder.Inserted) + { + statePacket = _reconcileStates.Dequeue(); - /* Since Inserted only runs inputs on reconciles - * it must use the first state to ensure all - * possible inputs are run, starting at the oldest (first state). */ - if (StateOrder == ReplicateStateOrder.Inserted) - { - statePacket = _reconcileStates.Dequeue(); + int removeCount = 0; - int removeCount = 0; + //Start beyond the first state. + for (int i = 0; i < _reconcileStates.Count; i++) + { + if (CanStateBeReconciled(_reconcileStates[i])) + removeCount++; + else + break; + } - //Start beyond the first state. - for (int i = 0; i < _reconcileStates.Count; i++) + for (int i = 0; i < removeCount; i++) + DisposeOfStatePacket(_reconcileStates.Dequeue()); + } + /* Appended order must reconcile the oldest state no-matter what. + * This is for the scenario if a local player were to jump on perhaps tick 100, + * then due to latency receive reconciles 99 100 and 101 at once, we must reconcile + * to 99 should there be any corrections to the jump on 100. */ + else { - if (CanStateBeReconciled(_reconcileStates[i])) - removeCount++; - else - break; + /* The state to reconcile will always be + * the first entry and removing ones not needed. */ + statePacket = _reconcileStates.Dequeue(); } - for (int i = 0; i < removeCount; i++) - DisposeOfStatePacket(_reconcileStates.Dequeue()); - } - /* Appended order must reconcile the oldest state no-matter what. - * This is for the scenario if a local player were to jump on perhaps tick 100, - * then due to latency receive reconciles 99 100 and 101 at once, we must reconcile - * to 99 should there be any corrections to the jump on 100. */ - else - { - /* The state to reconcile will always be - * the first entry and removing ones not needed. */ - statePacket = _reconcileStates.Dequeue(); - } - - bool CanStateBeReconciled(StatePacket lStatePacket) - { - if (lStatePacket == null) - return false; - - /* In order for the client to be able to reconcile itself - * the clientTick must be <= the last tick run on the client (lastLocalTickCompleted). - * - * However, since spectated objects use StateInterpolation they cannot be reconciled - * unless the clientTick is <= the last tick run on the client, and interpolation. - * This ensures that the reconcile does not occur to a state of data which is - * beyond the interpolated amount. - * Eg: - * If client received a spectated replicate on spectated tick 100 then the replicate - * would not run until localTick 102. If the next client reconcile state were for 100 it would - * pass because 100 is <= lastLocalTickCompleted. The spectated object would then reconcile - * to the results of the server spectated tick 100, even though the client had not run that - * spectated replicate locally yet. A correction would occur, and there would possibly be - * graphical disturbance. - * - * This happens because clients and server run inputs immediately on - * owned objects; however, on an object the server spectated, there may not be any desync, - * but we must be considerate of all scenarios. - */ - bool isClientMet = lStatePacket.ClientTick < lastLocalTickCompleted; - bool isServerMet = lStatePacket.ServerTick < _networkManager.TimeManager.LastPacketTick.Value() - StateInterpolation - 1; - - return isClientMet && isServerMet; - } - - //If state is null no reconcile is available at this time. - if (statePacket == null) - return; - - bool dropReconcile = false; - uint clientTick = statePacket.ClientTick; - uint serverTick = statePacket.ServerTick; + bool CanStateBeReconciled(StatePacket lStatePacket) + { + if (lStatePacket == null) + return false; + + /* In order for the client to be able to reconcile itself + * the clientTick must be <= the last tick run on the client (lastLocalTickCompleted). + * + * However, since spectated objects use StateInterpolation they cannot be reconciled + * unless the clientTick is <= the last tick run on the client, and interpolation. + * This ensures that the reconcile does not occur to a state of data which is + * beyond the interpolated amount. + * Eg: + * If client received a spectated replicate on spectated tick 100 then the replicate + * would not run until localTick 102. If the next client reconcile state were for 100 it would + * pass because 100 is <= lastLocalTickCompleted. The spectated object would then reconcile + * to the results of the server spectated tick 100, even though the client had not run that + * spectated replicate locally yet. A correction would occur, and there would possibly be + * graphical disturbance. + * + * This happens because clients and server run inputs immediately on + * owned objects; however, on an object the server spectated, there may not be any desync, + * but we must be considerate of all scenarios. + */ + bool isClientMet = lStatePacket.ClientTick < lastLocalTickCompleted; + bool isServerMet = lStatePacket.ServerTick < + _networkManager.TimeManager.LastPacketTick.Value() - StateInterpolation - 1; + + return isClientMet && isServerMet; + } - //Check to throttle reconciles. - if (_reduceReconcilesWithFramerate && !_clientReconcileThrottler.TryReconcile(_minimumClientReconcileFramerate)) - dropReconcile = true; + //If state is null no reconcile is available at this time. + if (statePacket == null) + return; - if (!dropReconcile) - { - IsReconciling = true; + bool dropReconcile = false; + uint clientTick = statePacket.ClientTick; + uint serverTick = statePacket.ServerTick; - ClientStateTick = clientTick; - /* This is the tick which the reconcile is for. - * Since reconciles are performed after replicate, if - * the replicate was on tick 100 then this reconcile is the state - * on tick 100, after the replicate is performed. */ - ServerStateTick = serverTick; + //Check to throttle reconciles. + if (_reduceReconcilesWithFramerate && + !_clientReconcileThrottler.TryReconcile(_minimumClientReconcileFramerate)) + dropReconcile = true; - // Have the reader get processed. - foreach (StatePacket.IncomingData item in statePacket.Data) + if (!dropReconcile) { - PooledReader reader = ReaderPool.Retrieve(item.Data, _networkManager, Reader.DataSource.Server); - _networkManager.ClientManager.ParseReader(reader, item.Channel); - ReaderPool.Store(reader); - } + IsReconciling = true; - bool timeManagerPhysics = tm.PhysicsMode == PhysicsMode.TimeManager; - float tickDelta = (float)tm.TickDelta * _networkManager.TimeManager.GetPhysicsTimeScale(); + ClientStateTick = clientTick; + /* This is the tick which the reconcile is for. + * Since reconciles are performed after replicate, if + * the replicate was on tick 100 then this reconcile is the state + * on tick 100, after the replicate is performed. */ + ServerStateTick = serverTick; - // using (_pm_PhysicsSyncTransforms.Auto()) - // Physics.SyncTransforms(); - // using (_pm_Physics2DSyncTransforms.Auto()) - // Physics2D.SyncTransforms(); + // Have the reader get processed. + for (int i = 0; i < statePacket.Data.Count; i++) + { + var item = statePacket.Data[i]; + PooledReader reader = ReaderPool.Retrieve(item.Data, _networkManager, Reader.DataSource.Server); + _networkManager.ClientManager.ParseReader(reader, item.Channel); + ReaderPool.Store(reader); + } - using (_pm_OnPreReconcile.Auto()) - OnPreReconcile?.Invoke(ClientStateTick, ServerStateTick); - using (_pm_OnReconcile.Auto()) - OnReconcile?.Invoke(ClientStateTick, ServerStateTick); + bool timeManagerPhysics = tm.PhysicsMode == PhysicsMode.TimeManager; + float tickDelta = (float)tm.TickDelta * _networkManager.TimeManager.GetPhysicsTimeScale(); - if (timeManagerPhysics) - { - using (_pm_OnPrePhysicsTransformSync.Auto()) - OnPrePhysicsTransformSync?.Invoke(ClientStateTick, ServerStateTick); + // using (_pm_PhysicsSyncTransforms.Auto()) + // Physics.SyncTransforms(); + // using (_pm_Physics2DSyncTransforms.Auto()) + // Physics2D.SyncTransforms(); - Physics.SyncTransforms(); - Physics2D.SyncTransforms(); + using (_pm_OnPreReconcile.Auto()) + OnPreReconcile?.Invoke(ClientStateTick, ServerStateTick); + using (_pm_OnReconcile.Auto()) + OnReconcile?.Invoke(ClientStateTick, ServerStateTick); - using (_pm_OnPostPhysicsTransformSync.Auto()) - OnPostPhysicsTransformSync?.Invoke(ClientStateTick, ServerStateTick); - } + if (timeManagerPhysics) + { + using (_pm_OnPrePhysicsTransformSync.Auto()) + OnPrePhysicsTransformSync?.Invoke(ClientStateTick, ServerStateTick); - using (_pm_OnPostReconcileSyncTransforms.Auto()) - OnPostReconcileSyncTransforms?.Invoke(ClientStateTick, ServerStateTick); - - Physics.SyncTransforms(); - Physics2D.SyncTransforms(); - - /* Set first replicate to be the 1 tick - * after reconcile. This is because reconcile calcs - * should be performed after replicate has run. - * In result object will reconcile to data AFTER - * the replicate tick, and then run remaining replicates as replay. - * - * Replay up to localtick, excluding localtick. There will - * be no input for localtick since reconcile runs before - * OnTick. */ - ClientReplayTick = ClientStateTick + 1; - ServerReplayTick = ServerStateTick + 1; - - /* Only replay up to but excluding local tick. - * This prevents client from running 1 local tick into the future - * since the OnTick has not run yet. - * - * EG: if localTick is 100 replay will run up to 99, then OnTick - * will fire for 100. */ - while (ClientReplayTick < lastLocalTickCompleted) - { - using (_pm_OnPreReplicateReplay.Auto()) - OnPreReplicateReplay?.Invoke(ClientReplayTick, ServerReplayTick); - using (_pm_OnReplicateReplay.Auto()) - OnReplicateReplay?.Invoke(ClientReplayTick, ServerReplayTick); + using (_pm_PhysicsSyncTransforms.Auto()) + Physics.SyncTransforms(); + using (_pm_Physics2DSyncTransforms.Auto()) + Physics2D.SyncTransforms(); + + using (_pm_OnPostPhysicsTransformSync.Auto()) + OnPostPhysicsTransformSync?.Invoke(ClientStateTick, ServerStateTick); + } - if (timeManagerPhysics && tickDelta > 0f) + using (_pm_OnPostReconcileSyncTransforms.Auto()) + OnPostReconcileSyncTransforms?.Invoke(ClientStateTick, ServerStateTick); + + using (_pm_PhysicsSyncTransforms.Auto()) + Physics.SyncTransforms(); + using (_pm_Physics2DSyncTransforms.Auto()) + Physics2D.SyncTransforms(); + + /* Set first replicate to be the 1 tick + * after reconcile. This is because reconcile calcs + * should be performed after replicate has run. + * In result object will reconcile to data AFTER + * the replicate tick, and then run remaining replicates as replay. + * + * Replay up to localtick, excluding localtick. There will + * be no input for localtick since reconcile runs before + * OnTick. */ + ClientReplayTick = ClientStateTick + 1; + ServerReplayTick = ServerStateTick + 1; + + /* Only replay up to but excluding local tick. + * This prevents client from running 1 local tick into the future + * since the OnTick has not run yet. + * + * EG: if localTick is 100 replay will run up to 99, then OnTick + * will fire for 100. */ + while (ClientReplayTick < lastLocalTickCompleted) { - _networkManager.TimeManager.InvokeOnSimulation(preSimulation: true, tickDelta); - _networkManager.TimeManager.SimulatePhysics(tickDelta); - _networkManager.TimeManager.InvokeOnSimulation(preSimulation: false, tickDelta); + using (_pm_OnPreReplicateReplay.Auto()) + OnPreReplicateReplay?.Invoke(ClientReplayTick, ServerReplayTick); + using (_pm_OnReplicateReplay.Auto()) + OnReplicateReplay?.Invoke(ClientReplayTick, ServerReplayTick); + + if (timeManagerPhysics && tickDelta > 0f) + { + _networkManager.TimeManager.InvokeOnSimulation(preSimulation: true, tickDelta); + _networkManager.TimeManager.SimulatePhysics(tickDelta); + _networkManager.TimeManager.InvokeOnSimulation(preSimulation: false, tickDelta); + } + + using (_pm_OnPostReplicateReplay.Auto()) + OnPostReplicateReplay?.Invoke(ClientReplayTick, ServerReplayTick); + + ClientReplayTick++; + ServerReplayTick++; } - using (_pm_OnPostReplicateReplay.Auto()) - OnPostReplicateReplay?.Invoke(ClientReplayTick, ServerReplayTick); + using (_pm_OnPostReconcile.Auto()) + OnPostReconcile?.Invoke(ClientStateTick, ServerStateTick); - ClientReplayTick++; - ServerReplayTick++; + ClientStateTick = TimeManager.UNSET_TICK; + ServerStateTick = TimeManager.UNSET_TICK; + ClientReplayTick = TimeManager.UNSET_TICK; + ServerReplayTick = TimeManager.UNSET_TICK; + IsReconciling = false; } - using (_pm_OnPostReconcile.Auto()) - OnPostReconcile?.Invoke(ClientStateTick, ServerStateTick); - - ClientStateTick = TimeManager.UNSET_TICK; - ServerStateTick = TimeManager.UNSET_TICK; - ClientReplayTick = TimeManager.UNSET_TICK; - ServerReplayTick = TimeManager.UNSET_TICK; - IsReconciling = false; + DisposeOfStatePacket(statePacket); + + using (_pm_OnReconcileEnd.Auto()) + OnReconcileEnd?.Invoke(); } - - DisposeOfStatePacket(statePacket); } /// @@ -757,8 +794,10 @@ internal void SendStateUpdate() int headersWritten = 0; #endif - foreach (NetworkConnection nc in _networkManager.ServerManager.Clients.Values) + using var enumerator = _networkManager.ServerManager.Clients.Values.GetEnumerator(); + while (enumerator.MoveNext()) { + NetworkConnection nc = enumerator.Current!; uint lastReplicateTick; // If client has performed a replicate recently. if (!nc.ReplicateTick.IsUnset) @@ -778,8 +817,9 @@ internal void SendStateUpdate() lastReplicateTick = ncLocalTick; } - foreach (PooledWriter writer in nc.PredictionStateWriters) + for (var i = 0; i < nc.PredictionStateWriters.Count; i++) { + var writer = nc.PredictionStateWriters[i]; #if DEVELOPMENT && !UNITY_SERVER headersWritten++; #endif diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Prediction.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Prediction.cs index ffd213bf..7d67b385 100644 --- a/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Prediction.cs +++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Prediction.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using GameKit.Dependencies.Utilities.Types; +using Unity.Profiling; using UnityEngine; [assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)] @@ -188,6 +189,14 @@ public abstract partial class NetworkBehaviour : MonoBehaviour #endregion #region Private. + + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_OnReplicateRpc = + new("NetworkBehaviour.OnReplicateRpc()"); + private static readonly ProfilerMarker _pm_OnReconcileRpc = + new("NetworkBehaviour.OnReconcileRpc()"); + #endregion + /// /// Registered Replicate methods. /// @@ -283,23 +292,31 @@ internal void RegisterReconcileRpc(uint hash, ReconcileRpcDelegate del) /// internal void OnReplicateRpc(int readerPositionAfterDebug, uint? hash, PooledReader reader, NetworkConnection sendingClient, Channel channel) { - if (hash == null) - hash = ReadRpcHash(reader); + using (_pm_OnReplicateRpc.Auto()) + { + if (hash == null) + hash = ReadRpcHash(reader); - reader.NetworkManager = _networkObjectCache.NetworkManager; + reader.NetworkManager = _networkObjectCache.NetworkManager; - if (_replicateRpcDelegates.TryGetValueIL2CPP(hash.Value, out ReplicateRpcDelegate del)) - del.Invoke(reader, sendingClient, channel); - else - _networkObjectCache.NetworkManager.LogWarning($"Replicate not found for hash {hash.Value} on {gameObject.name}, behaviour {GetType().Name}. Remainder of packet may become corrupt."); + if (_replicateRpcDelegates.TryGetValueIL2CPP(hash.Value, out ReplicateRpcDelegate del)) + del.Invoke(reader, sendingClient, channel); + else + _networkObjectCache.NetworkManager.LogWarning( + $"Replicate not found for hash {hash.Value} on {gameObject.name}, behaviour {GetType().Name}. Remainder of packet may become corrupt."); - #if !UNITY_SERVER - if (_networkTrafficStatistics != null) - { - bool sendingClientIsValid = sendingClient != null && sendingClient.IsValid; - _networkTrafficStatistics.AddInboundPacketIdData(PacketId.Replicate, GetRpcName(PacketId.Replicate, hash.Value), reader.Position - readerPositionAfterDebug + Managing.Transporting.TransportManager.PACKETID_LENGTH, gameObject, asServer: sendingClientIsValid); + #if !UNITY_SERVER + if (_networkTrafficStatistics != null) + { + bool sendingClientIsValid = sendingClient != null && sendingClient.IsValid; + _networkTrafficStatistics.AddInboundPacketIdData(PacketId.Replicate, + GetRpcName(PacketId.Replicate, hash.Value), + reader.Position - readerPositionAfterDebug + + Managing.Transporting.TransportManager.PACKETID_LENGTH, gameObject, + asServer: sendingClientIsValid); + } + #endif } - #endif } /// @@ -307,20 +324,27 @@ internal void OnReplicateRpc(int readerPositionAfterDebug, uint? hash, PooledRea /// internal void OnReconcileRpc(int readerPositionAfterDebug, uint? hash, PooledReader reader, Channel channel) { - if (hash == null) - hash = ReadRpcHash(reader); + using (_pm_OnReconcileRpc.Auto()) + { + if (hash == null) + hash = ReadRpcHash(reader); - reader.NetworkManager = _networkObjectCache.NetworkManager; + reader.NetworkManager = _networkObjectCache.NetworkManager; - if (_reconcileRpcDelegates.TryGetValueIL2CPP(hash.Value, out ReconcileRpcDelegate del)) - del.Invoke(reader, channel); - else - _networkObjectCache.NetworkManager.LogWarning($"Reconcile not found for hash {hash.Value}. Remainder of packet may become corrupt."); + if (_reconcileRpcDelegates.TryGetValueIL2CPP(hash.Value, out ReconcileRpcDelegate del)) + del.Invoke(reader, channel); + else + _networkObjectCache.NetworkManager.LogWarning( + $"Reconcile not found for hash {hash.Value}. Remainder of packet may become corrupt."); - #if !UNITY_SERVER - if (_networkTrafficStatistics != null) - _networkTrafficStatistics.AddInboundPacketIdData(PacketId.Reconcile, GetRpcName(PacketId.Reconcile, hash.Value), reader.Position - readerPositionAfterDebug + Managing.Transporting.TransportManager.PACKETID_LENGTH, gameObject, asServer: false); - #endif + #if !UNITY_SERVER + if (_networkTrafficStatistics != null) + _networkTrafficStatistics.AddInboundPacketIdData(PacketId.Reconcile, + GetRpcName(PacketId.Reconcile, hash.Value), + reader.Position - readerPositionAfterDebug + + Managing.Transporting.TransportManager.PACKETID_LENGTH, gameObject, asServer: false); + #endif + } } /// @@ -478,11 +502,11 @@ private bool HasServerRigidbodyTransformChanged(bool updateLastValues) const float angleDistance = 0.2f; bool anyChanged = false; - anyChanged |= (transform.position - _lastCheckedTransformProperties.Position).sqrMagnitude > v3Distance; + anyChanged |= (transform.position - (Vector3)_lastCheckedTransformProperties.Position).sqrMagnitude > v3Distance; if (!anyChanged) anyChanged |= transform.rotation.Angle(_lastCheckedTransformProperties.Rotation, precise: true) > angleDistance; if (!anyChanged) - anyChanged |= (transform.localScale - _lastCheckedTransformProperties.Scale).sqrMagnitude > v3Distance; + anyChanged |= (transform.localScale - (Vector3)_lastCheckedTransformProperties.Scale).sqrMagnitude > v3Distance; // If transform changed update last values. if (updateLastValues && anyChanged) @@ -600,20 +624,21 @@ private bool HasServerRigidbodyTransformChanged(bool updateLastValues) /// /// private void Replicate_NonAuthoritative(ReplicateUserLogicDelegate del, BasicQueue> replicatesQueue, RingBuffer> replicatesHistory) where T : IReplicateData, new() - { PredictionManager predictionManager = PredictionManager; + { + PredictionManager predictionManager = PredictionManager; bool isServerStarted = _networkObjectCache.IsServerStarted; bool isServerWithoutOwner = isServerStarted && !Owner.IsValid; - /* Both owner and server when no owner should run * authoritative replicate. */ if (isServerWithoutOwner) return; + /* If not state forwarding and not server then exit method. * The server still needs to run inputs even if not authoritative. */ - if (!isServerStarted && !_networkObjectCache.EnableStateForwarding) + if (!_networkObjectCache.EnableStateForwarding) return; - + TimeManager tm = _networkObjectCache.TimeManager; uint localTick = tm.LocalTick; @@ -621,7 +646,7 @@ private bool HasServerRigidbodyTransformChanged(bool updateLastValues) * run default input and exit. With inserted order client only runs * during replays. Server never replays, so it still runs * as current even with inserted order. */ - if (!isServerStarted && !predictionManager.IsAppendedStateOrder) + if (!predictionManager.IsAppendedStateOrder) { ReplicateDefaultData(); return; @@ -787,6 +812,16 @@ internal virtual void Replicate_Replay_Start(uint replayTick) { } [MakePublic] private void Replicate_Replay_NonAuthoritative(uint replayTick, ReplicateUserLogicDelegate del, RingBuffer> replicatesHistory) where T : IReplicateData, new() { + //NOTESSTART + /* When inserting states only replay the first state after the reconcile. + * This prevents an inconsistency on running created states if other created states + * were to arrive late. Essentially the first state is considered 'current' and the rest + * are acting as a buffer against unsteady networking conditions. */ + + /* When appending states all created can be run. Appended states are only inserted after they've + * run at the end of the tick, which performs of it's own queue. Because of this, it's safe to assume + * if the state has been inserted into the past it has already passed it's buffer checks. */ + //NOTESEND ReplicateDataContainer dataContainer; ReplicateState state; bool isAppendedOrder = _networkObjectCache.PredictionManager.IsAppendedStateOrder; @@ -1284,8 +1319,6 @@ internal void Reconcile_Client_AddToLocalHistory(RingBuffer return; if (!_networkObjectCache.PredictionManager.CreateLocalStates) return; - if (!IsOwner && !_networkObjectCache.EnableStateForwarding) - return; /* This is called by the local client when creating * a local reconcile state. These states should always @@ -1335,8 +1368,6 @@ internal void Reconcile_Client_AddToLocalHistory(RingBuffer internal void Reconcile_Client(ReconcileUserLogicDelegate reconcileDel, RingBuffer> replicatesHistory, RingBuffer> reconcilesHistory, T data) where T : IReconcileData where T2 : IReplicateData, new() { bool isBehaviourReconciling = IsBehaviourReconciling; - if (!isBehaviourReconciling) - return; const long unsetHistoryIndex = -1; long historyIndex = unsetHistoryIndex; @@ -1371,6 +1402,19 @@ internal void Reconcile_Client_AddToLocalHistory(RingBuffer uint lrTick = reconcilesHistory[(int)historyIndex].Tick; if (lrTick != reconcileTick) historyIndex = unsetHistoryIndex; + + //If index is set and behaviour is not reconciling then apply data. + if (!isBehaviourReconciling && historyIndex != unsetHistoryIndex) + { + LocalReconcile localReconcile = reconcilesHistory[(int)historyIndex]; + //Before disposing get the writer and call reconcile reader so it's parsed. + PooledWriter reconcileWritten = localReconcile.Writer; + /* Although this is actually from the local client the datasource is being set to server since server + * is what typically sends reconciles. */ + PooledReader reader = ReaderPool.Retrieve(reconcileWritten.GetArraySegment(), _networkObjectCache.NetworkManager, Reader.DataSource.Server); + data = Reconcile_Reader_Local(localReconcile.Tick, reader); + ReaderPool.Store(reader); + } } } diff --git a/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Prediction.cs b/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Prediction.cs index fb97925d..c76dce96 100644 --- a/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Prediction.cs +++ b/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Prediction.cs @@ -46,6 +46,7 @@ internal enum RigidbodyLocalReconcileCorrectionType : byte /// Only reset the transform. /// TransformOnly = 1, + /* Velocities support will be available next release. * To support velocities as well PreReconcilingTransformProperties must * also store each rigidbody associated with the transform. This should not @@ -404,7 +405,7 @@ private void InvokeStartCallbacks_Prediction(bool asServer) if (!asServer) { TimeManager.OnUpdate += TimeManager_Update; - + if (PredictionSmoother != null) PredictionSmoother.OnStartClient(); } @@ -519,11 +520,11 @@ private void PredictionManager_OnReconcile(uint clientReconcileTick, uint server private void PredictionManager_OnPostReconcile(uint clientReconcileTick, uint serverReconcileTick) { - foreach (NetworkBehaviour nbb in _predictionBehaviours) - nbb.IsReconcileRemote = false; - using (_pm_OnPostReconcile.Auto()) { + foreach (NetworkBehaviour nbb in _predictionBehaviours) + nbb.IsReconcileRemote = false; + if (!IsClientInitialized) return; @@ -532,8 +533,9 @@ private void PredictionManager_OnPostReconcile(uint clientReconcileTick, uint se /* Check changes in transform for every transform * which utilizes prediction and a rigidbody, and * may have changed since preReconcile. */ - foreach (PreReconcilingTransformProperties prtp in _updatedPreReconcilingTransformProperties) + for (var i = 0; i < _updatedPreReconcilingTransformProperties.Count; i++) { + var prtp = _updatedPreReconcilingTransformProperties[i]; /* If transform has not changed enough to matter * then reset values as they were before the reconcile. */ if (!LHasTransformChanged()) @@ -544,9 +546,11 @@ bool LHasTransformChanged() const float v3Distance = 0.000025f; const float angleDistance = 0.2f; - bool hasChanged = (transform.position - prtp.Properties.Position).sqrMagnitude >= v3Distance; + bool hasChanged = (transform.position - (Vector3)prtp.Properties.Position).sqrMagnitude >= + v3Distance; if (!hasChanged) - hasChanged = transform.rotation.Angle(prtp.Properties.Rotation, precise: true) >= angleDistance; + hasChanged = transform.rotation.Angle(prtp.Properties.Rotation, precise: true) >= + angleDistance; return hasChanged; }