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;
}