diff --git a/Assets/FishNet/CodeGenerating/Helpers/NetworkBehaviourHelper.cs b/Assets/FishNet/CodeGenerating/Helpers/NetworkBehaviourHelper.cs
index c1bc89c45..e6f08fcbd 100644
--- a/Assets/FishNet/CodeGenerating/Helpers/NetworkBehaviourHelper.cs
+++ b/Assets/FishNet/CodeGenerating/Helpers/NetworkBehaviourHelper.cs
@@ -90,8 +90,10 @@ public override bool ImportReferences()
foreach (MethodInfo mi in networkBehaviourType.GetMethods((BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic)))
{
+ if (mi.Name == nameof(NetworkBehaviour.GetIsNetworked))
+ IsNetworked_MethodRef = base.ImportReference(mi);
//CreateDelegates.
- if (mi.Name == nameof(NetworkBehaviour.RegisterServerRpc))
+ else if (mi.Name == nameof(NetworkBehaviour.RegisterServerRpc))
RegisterServerRpc_MethodRef = base.ImportReference(mi);
else if (mi.Name == nameof(NetworkBehaviour.RegisterObserversRpc))
RegisterObserversRpc_MethodRef = base.ImportReference(mi);
@@ -152,8 +154,6 @@ public override bool ImportReferences()
IsHost_MethodRef = base.ImportReference(pi.GetMethod);
else if (pi.Name == nameof(NetworkBehaviour.IsOwner))
IsOwner_MethodRef = base.ImportReference(pi.GetMethod);
- else if (pi.Name == nameof(NetworkBehaviour.IsNetworked))
- IsNetworked_MethodRef = base.ImportReference(pi.GetMethod);
//Owner.
else if (pi.Name == nameof(NetworkBehaviour.Owner))
Owner_MethodRef = base.ImportReference(pi.GetMethod);
diff --git a/Assets/FishNet/CodeGenerating/ILCore/FishNetILPP.cs b/Assets/FishNet/CodeGenerating/ILCore/FishNetILPP.cs
index 1869a1ad1..f4b8fcf34 100644
--- a/Assets/FishNet/CodeGenerating/ILCore/FishNetILPP.cs
+++ b/Assets/FishNet/CodeGenerating/ILCore/FishNetILPP.cs
@@ -63,6 +63,8 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
if (!session.Initialize(assemblyDef.MainModule))
return null;
+
+
bool modified = false;
bool fnAssembly = IsFishNetAssembly(compiledAssembly);
diff --git a/Assets/FishNet/Runtime/Connection/NetworkConnection.cs b/Assets/FishNet/Runtime/Connection/NetworkConnection.cs
index fc305aba2..be87141b3 100644
--- a/Assets/FishNet/Runtime/Connection/NetworkConnection.cs
+++ b/Assets/FishNet/Runtime/Connection/NetworkConnection.cs
@@ -89,8 +89,9 @@ public bool LoadedStartScenes(bool asServer)
///
/// TransportIndex this connection is on.
/// For security reasons this value will be unset on clients if this is not their connection.
+ /// This is not yet used.
///
- public int TransportIndex { get; internal set; } = -1;
+ public int TransportIndex { get; private set; } = -1;
///
/// True if this connection is authenticated. Only available to server.
///
diff --git a/Assets/FishNet/Runtime/Editor/PrefabCollectionGenerator/Generator.cs b/Assets/FishNet/Runtime/Editor/PrefabCollectionGenerator/Generator.cs
index ae1408633..5eeed76a3 100644
--- a/Assets/FishNet/Runtime/Editor/PrefabCollectionGenerator/Generator.cs
+++ b/Assets/FishNet/Runtime/Editor/PrefabCollectionGenerator/Generator.cs
@@ -634,7 +634,7 @@ private static void OnPostprocessAllAssets(string[] importedAssets, string[] del
///
private static bool CanAddNetworkObject(NetworkObject networkObject, PrefabGeneratorConfigurations settings)
{
- return networkObject != null && (networkObject.IsSpawnable || !settings.SpawnableOnly);
+ return networkObject != null && (networkObject.GetIsSpawnable() || !settings.SpawnableOnly);
}
}
}
diff --git a/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs b/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs
index 10909ceab..7b5a32fcd 100644
--- a/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs
+++ b/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs
@@ -12,6 +12,7 @@
using GameKit.Dependencies.Utilities;
using System;
using System.Collections.Generic;
+using FishNet.Managing.Timing;
using UnityEngine;
using UnityEngine.Scripting;
using static FishNet.Object.NetworkObject;
@@ -46,12 +47,16 @@ private struct ReceivedClientData
/// Channel the current data arrived on.
///
public Channel Channel;
+ ///
+ /// LocalTick of the side receiving this update, typically the server.
+ ///
+ public uint LocalTick;
///
/// Updates current values.
///
/// True to set all HasData to true.
- public void Update(ArraySegment data, Channel channel, bool updateHasData)
+ public void Update(ArraySegment data, Channel channel, bool updateHasData, uint localTick)
{
if (Writer == null)
Writer = WriterPool.Retrieve();
@@ -59,6 +64,7 @@ public void Update(ArraySegment data, Channel channel, bool updateHasData)
Writer.Reset();
Writer.WriteArraySegment(data);
Channel = channel;
+ LocalTick = localTick;
if (updateHasData)
HasData = true;
@@ -576,14 +582,10 @@ public void SetSendToOwner(bool value)
/// Number of intervals remaining before synchronization.
///
private short _intervalsRemaining;
-
///
/// Last sent transform data.
///
private TransformData _lastSentTransformData;
-
- private TransformData _ll;
-
///
/// Writers for changed data.
///
@@ -681,6 +683,7 @@ public override void OnOwnershipServer(NetworkConnection prevOwner)
public override void OnOwnershipClient(NetworkConnection prevOwner)
{
+ ConfigureComponents();
_intervalsRemaining = 0;
/* If newOwner is self then client
@@ -1500,9 +1503,9 @@ private void MoveToTarget(float delta)
float multiplier = 1f;
int queueCount = _goalDataQueue.Count;
- //For every entry past interpolation increase move rate.
+ //Increase move rate slightly if over queue count.
if (queueCount > (_interpolation + 1))
- multiplier += (0.05f * queueCount);
+ multiplier += 0.05f;
//Rate to update. Changes per property.
float rate;
@@ -1590,23 +1593,30 @@ private void SendToClients()
* If owner is clientHost just send current server values. */
if (clientAuthoritativeWithOwner && !base.Owner.IsLocalClient)
{
- //Check to set hasData if does not, and hasn't sent reliably yet.
+ /* If there is not new data yet and the last received was not reliable
+ * then a packet maybe did not arrive when expected. See if we need
+ * to force a reliable with the last data based on ticks passed since
+ * last update.*/
if (!_authoritativeClientData.HasData && _authoritativeClientData.Channel != Channel.Reliable)
- _authoritativeClientData.SendReliably();
+ {
+ /* If ticks have passed beyond interpolation then force
+ * to send reliably. */
+ uint maxPassedTicks = (uint)(1 + _interpolation + _extrapolation);
+ uint localTick = base.TimeManager.LocalTick;
+ if ((localTick - _authoritativeClientData.LocalTick) > maxPassedTicks)
+ _authoritativeClientData.SendReliably();
+ //Not enough time to send reliably, just don't need update.
+ else
+ return;
+ }
if (_authoritativeClientData.HasData)
{
_changedSinceStart = true;
//Resend data from clients.
ObserversUpdateClientAuthoritativeTransform(_authoritativeClientData.Writer.GetArraySegment(), _authoritativeClientData.Channel);
-
- /* If has not yet sent reliably then make it so
- * for the next send. If new data comes in this will be
- * overwritten with whatever channel is used on the new data. */
- if (_authoritativeClientData.Channel != Channel.Reliable)
- _authoritativeClientData.SendReliably();
- else
- _authoritativeClientData.HasData = false;
+ //Now being sent data can unset.
+ _authoritativeClientData.HasData = false;
}
}
//Sending server transform state.
@@ -1645,7 +1655,7 @@ private void SendToClients()
lastSentData.Update(0, t.localPosition, t.localRotation, t.localScale, t.localPosition, ParentBehaviour);
SerializeChanged(changed, writer);
}
-
+
ObserversUpdateClientAuthoritativeTransform(writer.GetArraySegment(), channel);
}
}
@@ -2020,7 +2030,7 @@ private void SetCalculatedRates(TransformData prevTd, RateData prevRd, GoalData
}
rd.Update(positionRate, rotationRate, scaleRate, unalteredPositionRate, tickDifference, timePassed);
-
+
//Returns if whole contains part.
bool ChangedFullContains(ChangedFull whole, ChangedFull part)
{
@@ -2138,13 +2148,14 @@ private void ServerUpdateTransform(ArraySegment data, Channel channel)
return;
}
+ TimeManager tm = base.TimeManager;
//Not new data.
- uint lastPacketTick = base.TimeManager.LastPacketTick.LastRemoteTick;
+ uint lastPacketTick = tm.LastPacketTick.LastRemoteTick;
if (lastPacketTick <= _lastServerRpcTick)
return;
_lastServerRpcTick = lastPacketTick;
- _authoritativeClientData.Update(data, channel, true);
+ _authoritativeClientData.Update(data, channel, true, tm.LocalTick);
DataReceived(data, channel, true);
}
@@ -2163,7 +2174,7 @@ private void DataReceived(ArraySegment data, Channel channel, bool asServe
GoalData nextGd = ResettableObjectCaches.Retrieve();
TransformData nextTd = nextGd.Transforms;
UpdateTransformData(data, prevTd, nextTd, ref changedFull);
-
+
OnDataReceived?.Invoke(prevTd, nextTd);
SetExtrapolation(prevTd, nextTd, channel);
@@ -2384,11 +2395,7 @@ private void ResetState()
ObjectCaches.StoreAndDefault(ref _authoritativeClientData.Writer);
- if (_toClientChangedWriter != null)
- {
- WriterPool.Store(_toClientChangedWriter);
- ObjectCaches.StoreAndDefault(ref _toClientChangedWriter);
- }
+ WriterPool.StoreAndDefault(ref _toClientChangedWriter);
ObjectCaches.StoreAndDefault(ref _authoritativeClientData.HasData);
ObjectCaches.StoreAndDefault(ref _serverChangedSinceReliable);
@@ -2409,8 +2416,7 @@ private void ResetState()
private void ResetState_OnDestroy()
{
ResettableObjectCaches.StoreAndDefault(ref _lastSentTransformData);
- if (_toClientChangedWriter != null)
- WriterPool.Store(_toClientChangedWriter);
+ WriterPool.StoreAndDefault(ref _toClientChangedWriter);
}
}
}
\ No newline at end of file
diff --git a/Assets/FishNet/Runtime/Generated/Component/Spawning/PlayerSpawner.cs b/Assets/FishNet/Runtime/Generated/Component/Spawning/PlayerSpawner.cs
index 53a86b334..3397894a9 100644
--- a/Assets/FishNet/Runtime/Generated/Component/Spawning/PlayerSpawner.cs
+++ b/Assets/FishNet/Runtime/Generated/Component/Spawning/PlayerSpawner.cs
@@ -30,6 +30,11 @@ public class PlayerSpawner : MonoBehaviour
[SerializeField]
private NetworkObject _playerPrefab;
///
+ /// Sets the PlayerPrefab to use.
+ ///
+ ///
+ public void SetPlayerPrefab(NetworkObject nob) => _playerPrefab = nob;
+ ///
/// True to add player to the active scene when no global scenes are specified through the SceneManager.
///
[Tooltip("True to add player to the active scene when no global scenes are specified through the SceneManager.")]
diff --git a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs
index abf9dee12..2fa26ceb5 100644
--- a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs
+++ b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs
@@ -281,7 +281,8 @@ public bool StartConnection()
///
public bool StartConnection(string address)
{
- return StartConnection(address, NetworkManager.TransportManager.Transport.GetPort());
+ NetworkManager.TransportManager.Transport.SetClientAddress(address);
+ return StartConnection();
}
///
/// Sets the transport address and port, and starts the local client connection.
diff --git a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs
index 985c91332..152347f20 100644
--- a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs
+++ b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs
@@ -95,6 +95,9 @@ internal void OnClientConnectionState(ClientConnectionStateArgs args)
{
foreach (NetworkObject n in Spawned.Values)
{
+ if (!n.CanDeinitialize(asServer: false))
+ continue;
+
n.InvokeStopCallbacks(false, true);
n.SetInitializedStatus(false, false);
}
@@ -174,7 +177,7 @@ internal void PredictedSpawn(NetworkObject networkObject, NetworkConnection owne
}
else
{
- networkObject.Deinitialize(false);
+ networkObject.Deinitialize(asServer: false);
NetworkManager.StorePooledInstantiated(networkObject, false);
}
@@ -191,8 +194,8 @@ internal void PredictedDespawn(NetworkObject networkObject)
base.NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment());
writer.Store();
- networkObject.Deinitialize(false);
- NetworkManager.StorePooledInstantiated(networkObject, false);
+ networkObject.Deinitialize(asServer: false);
+ NetworkManager.StorePooledInstantiated(networkObject, asServer: false);
}
///
@@ -233,9 +236,9 @@ private void RegisterAndDespawnSceneObjects(Scene s)
//Only set initialized values if not server, as server would have already done so.
if (!isServerStarted)
- nob.SetInitializedValues(null);
+ nob.SetInitializedValues(parentNob: null);
- if (nob.IsNetworked)
+ if (nob.GetIsNetworked())
{
base.AddToSceneObjects(nob);
//Only run if not also server, as this already ran on server.
@@ -333,7 +336,7 @@ internal void ParsePredictedSpawnResult(PooledReader reader)
{
if (Spawned.TryGetValueIL2CPP(usedObjectId, out NetworkObject nob))
{
- nob.Deinitialize(false);
+ nob.Deinitialize(asServer: false);
NetworkManager.StorePooledInstantiated(nob, false);
}
}
diff --git a/Assets/FishNet/Runtime/Managing/NetworkManager.cs b/Assets/FishNet/Runtime/Managing/NetworkManager.cs
index 10ccda0f9..517ab3e87 100644
--- a/Assets/FishNet/Runtime/Managing/NetworkManager.cs
+++ b/Assets/FishNet/Runtime/Managing/NetworkManager.cs
@@ -212,7 +212,7 @@ public static IReadOnlyList Instances
///
/// Version of this release.
///
- public const string FISHNET_VERSION = "4.4.7";
+ public const string FISHNET_VERSION = "4.5.0";
///
/// Maximum framerate allowed.
///
@@ -322,7 +322,7 @@ internal void UpdateFramerate()
{
bool clientStarted = ClientManager.Started;
bool serverStarted = ServerManager.Started;
-
+
int frameRate = 0;
//If both client and server are started then use whichever framerate is higher.
if (clientStarted && serverStarted)
@@ -335,8 +335,8 @@ internal void UpdateFramerate()
/* Make sure framerate isn't set to max on server.
* If it is then default to tick rate. If framerate is
* less than tickrate then also set to tickrate. */
-#if UNITY_SERVER
- ushort minimumServerFramerate = (ushort)(TimeManager.TickRate + 1);
+#if UNITY_SERVER && !UNITY_EDITOR
+ ushort minimumServerFramerate = (ushort)(TimeManager.TickRate + 15);
if (frameRate == MAXIMUM_FRAMERATE)
frameRate = minimumServerFramerate;
else if (frameRate < TimeManager.TickRate)
diff --git a/Assets/FishNet/Runtime/Managing/Prediction/Editor/PredictionManagerEditor.cs b/Assets/FishNet/Runtime/Managing/Prediction/Editor/PredictionManagerEditor.cs
index f8d08c72c..3b60633d8 100644
--- a/Assets/FishNet/Runtime/Managing/Prediction/Editor/PredictionManagerEditor.cs
+++ b/Assets/FishNet/Runtime/Managing/Prediction/Editor/PredictionManagerEditor.cs
@@ -4,8 +4,6 @@
namespace FishNet.Managing.Predicting.Editing
{
-
-
[CustomEditor(typeof(PredictionManager), true)]
[CanEditMultipleObjects]
public class PredictionManagerEditor : Editor
@@ -14,6 +12,7 @@ public class PredictionManagerEditor : Editor
private SerializedProperty _dropExcessiveReplicates;
private SerializedProperty _maximumServerReplicates;
private SerializedProperty _maximumConsumeCount;
+ private SerializedProperty _createLocalStates;
private SerializedProperty _stateInterpolation;
private SerializedProperty _stateOrder;
@@ -22,6 +21,7 @@ protected virtual void OnEnable()
_dropExcessiveReplicates = serializedObject.FindProperty(nameof(_dropExcessiveReplicates));
_maximumServerReplicates = serializedObject.FindProperty(nameof(_maximumServerReplicates));
_maximumConsumeCount = serializedObject.FindProperty(nameof(_maximumConsumeCount));
+ _createLocalStates = serializedObject.FindProperty(nameof(_createLocalStates));
_stateInterpolation = serializedObject.FindProperty(nameof(_stateInterpolation));
_stateOrder = serializedObject.FindProperty(nameof(_stateOrder));
}
@@ -37,6 +37,9 @@ public override void OnInspectorGUI()
EditorGUILayout.LabelField("Client", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
+#if !FISHNET_STABLE_MODE
+ EditorGUILayout.PropertyField(_createLocalStates);
+#endif
if (_stateInterpolation.intValue == 0)
EditorGUILayout.HelpBox($"With interpolation set at 0 states will run as they are received, rather than create an interpolation buffer. Using 0 interpolation drastically increases the chance of Created states arriving out of order.", MessageType.Warning);
EditorGUILayout.PropertyField(_stateInterpolation);
@@ -60,8 +63,6 @@ public override void OnInspectorGUI()
serializedObject.ApplyModifiedProperties();
}
-
}
}
-#endif
-
+#endif
\ No newline at end of file
diff --git a/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs b/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs
index 2f40df5c3..09dfa955c 100644
--- a/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs
+++ b/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs
@@ -174,9 +174,19 @@ public void SetMaximumServerReplicates(byte value)
/// No more than this value of replicates should be stored as a buffer.
///
internal ushort MaximumPastReplicates => (ushort)(_networkManager.TimeManager.TickRate * 5);
+
///
- ///
+ /// True for the client to create local reconcile states. Enabling this feature allows reconciles to be sent less frequently and provides data to use for reconciles when packets are lost.
///
+ internal bool CreateLocalStates => _createLocalStates;
+ [FormerlySerializedAs("_localStates")]
+ [Tooltip("True for the client to create local reconcile states. Enabling this feature allows reconciles to be sent less frequently and provides data to use for reconciles when packets are lost.")]
+ [SerializeField]
+ private bool _createLocalStates = true;
+ ///
+ /// How many states to try and hold in a buffer before running them. Larger values add resilience against network issues at the cost of running states later.
+ ///
+ internal byte StateInterpolation => _stateInterpolation;
[Tooltip("How many states to try and hold in a buffer before running them on clients. Larger values add resilience against network issues at the cost of running states later.")]
[Range(0, MAXIMUM_PAST_INPUTS)]
[FormerlySerializedAs("_redundancyCount")] //Remove on V5.
@@ -184,20 +194,13 @@ public void SetMaximumServerReplicates(byte value)
[SerializeField]
private byte _stateInterpolation = 1;
///
- /// How many states to try and hold in a buffer before running them. Larger values add resilience against network issues at the cost of running states later.
- ///
- internal byte StateInterpolation => _stateInterpolation;
- ///
- ///
+ /// The order in which states are run. Future favors performance and does not depend upon reconciles, while Past favors accuracy but clients must reconcile every tick.
///
+ public ReplicateStateOrder StateOrder => _stateOrder;
[Tooltip("The order in which clients run states. Future favors performance and does not depend upon reconciles, while Past favors accuracy but clients must reconcile every tick.")]
[SerializeField]
private ReplicateStateOrder _stateOrder = ReplicateStateOrder.Appended;
///
- /// The order in which states are run. Future favors performance and does not depend upon reconciles, while Past favors accuracy but clients must reconcile every tick.
- ///
- public ReplicateStateOrder StateOrder => _stateOrder;
- ///
/// True if StateOrder is set to future.
///
internal bool IsAppendedStateOrder => (_stateOrder == ReplicateStateOrder.Appended);
@@ -391,7 +394,7 @@ internal void ReconcileToStates()
return;
//Creates a local state update if one is not available in reconcile states.
- // CreateLocalStateUpdate();
+ // CreateLocalStateUpdate();
//If there are no states then guestimate the next state.
if (_reconcileStates.Count == 0)
diff --git a/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs b/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs
index a96dd2aed..655d510ac 100644
--- a/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs
+++ b/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs
@@ -782,7 +782,7 @@ private bool CanMoveNetworkObject(NetworkObject nob, bool warn)
if (nob == null)
return WarnAndReturnFalse($"NetworkObject is null.");
//Not networked.
- if (!nob.IsNetworked)
+ if (!nob.GetIsNetworked())
return WarnAndReturnFalse($"NetworkObject {nob.name} cannot be moved as it is not networked.");
//Not spawned.
if (!nob.IsSpawned)
diff --git a/Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.Observers.cs b/Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.Observers.cs
index 042e215e0..ecfb4e3ec 100644
--- a/Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.Observers.cs
+++ b/Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.Observers.cs
@@ -37,6 +37,10 @@ public partial class ServerObjects : ManagedObjects
/// Used to write spawns for everyone. This writer will exclude owner only information.
///
private PooledWriter _writer = new();
+ ///
+ /// Indexes within TimedNetworkObservers which are unset.
+ ///
+ private Queue _emptiedTimedIndexes = new();
#endregion
///
@@ -62,7 +66,7 @@ private void UpdateTimedObservers()
return;
/* Try to iterate all timed observers every half a second.
- * This value will increase as there's more observers or timed conditions. */
+ * This value will increase as there's more observers or timed conditions. */
float timeMultiplier = 1f + (float)((base.NetworkManager.ServerManager.Clients.Count * 0.005f) + (_timedNetworkObservers.Count * 0.0005f));
//Check cap this way for readability.
float completionTime = Mathf.Min((0.5f * timeMultiplier), base.NetworkManager.ObserverManager.MaximumTimedObserversDuration);
@@ -81,7 +85,10 @@ private void UpdateTimedObservers()
{
if (_nextTimedObserversIndex >= _timedNetworkObservers.Count)
_nextTimedObserversIndex = 0;
- nobCache.Add(_timedNetworkObservers[_nextTimedObserversIndex++]);
+
+ NetworkObject nob = _timedNetworkObservers[_nextTimedObserversIndex++];
+ if (nob != null)
+ nobCache.Add(nob);
}
RebuildObservers(nobCache, connCache, true);
@@ -96,7 +103,10 @@ private void UpdateTimedObservers()
/// NetworkObject to be updated.
public void AddTimedNetworkObserver(NetworkObject networkObject)
{
- _timedNetworkObservers.Add(networkObject);
+ if (_emptiedTimedIndexes.TryDequeue(out int index))
+ _timedNetworkObservers[index] = networkObject;
+ else
+ _timedNetworkObservers.Add(networkObject);
}
///
@@ -105,7 +115,29 @@ public void AddTimedNetworkObserver(NetworkObject networkObject)
/// NetworkObject to be updated.
public void RemoveTimedNetworkObserver(NetworkObject networkObject)
{
- _timedNetworkObservers.Remove(networkObject);
+ int index = _timedNetworkObservers.IndexOf(networkObject);
+ if (index == -1)
+ return;
+
+ _emptiedTimedIndexes.Enqueue(index);
+ _timedNetworkObservers[index] = null;
+
+ //If there's a decent amount missing then rebuild the collection.
+ if (_emptiedTimedIndexes.Count > 20)
+ {
+ List newLst = CollectionCaches.RetrieveList();
+ foreach (NetworkObject nob in _timedNetworkObservers)
+ {
+ if (nob == null)
+ continue;
+
+ newLst.Add(nob);
+ }
+
+ CollectionCaches.Store(_timedNetworkObservers);
+ _timedNetworkObservers = newLst;
+ _emptiedTimedIndexes.Clear();
+ }
}
///
@@ -128,69 +160,24 @@ private List RetrieveAuthenticatedConnections()
/// Gets all spawned objects with root objects first.
///
///
-
private List RetrieveOrderedSpawnedObjects()
{
List cache = CollectionCaches.RetrieveList();
- bool initializationOrderChanged = false;
//First order root objects.
foreach (NetworkObject item in Spawned.Values)
- OrderRootByInitializationOrder(item, cache, ref initializationOrderChanged);
+ {
+ if (item.IsNested)
+ continue;
+
+ cache.AddOrdered(item);
+ }
OrderNestedByInitializationOrder(cache);
return cache;
}
- ///
- /// Orders a NetworkObject into a cache based on it's initialization order.
- /// Only non-nested NetworkObjects will be added.
- ///
- /// NetworkObject to check.
- /// Cache to sort into.
- /// Boolean to indicate if initialization order is specified for one or more objects.
- private void OrderRootByInitializationOrder(NetworkObject nob, List cache, ref bool initializationOrderChanged)
- {
- if (nob.IsNested)
- return;
-
- sbyte currentItemInitOrder = nob.GetInitializeOrder();
- initializationOrderChanged |= (currentItemInitOrder != 0);
- int count = cache.Count;
-
- /* If initialize order has not changed or count is
- * 0 then add normally. */
- if (!initializationOrderChanged || count == 0)
- {
- cache.Add(nob);
- }
- else
- {
- /* If current item init order is larger or equal than
- * the last entry in copy then add to the end.
- * Otherwise check where to add from the beginning. */
- if (currentItemInitOrder >= cache[count - 1].GetInitializeOrder())
- {
- cache.Add(nob);
- }
- else
- {
- for (int i = 0; i < count; i++)
- {
- /* If item being sorted is lower than the one in already added.
- * then insert it before the one already added. */
- if (currentItemInitOrder <= cache[i].GetInitializeOrder())
- {
- cache.Insert(i, nob);
- break;
- }
- }
- }
- }
- }
-
-
///
/// Orders nested NetworkObjects of cache by initialization order.
///
@@ -202,7 +189,7 @@ private void OrderNestedByInitializationOrder(List cache)
{
NetworkObject nob = cache[i];
//Skip root.
- if (nob.IsNested)
+ if (!nob.IsNested)
continue;
int startingIndex = i;
@@ -219,8 +206,6 @@ void AddChildNetworkObjects(NetworkObject n, ref int index)
}
}
-
-
///
/// Removes a connection from observers without synchronizing changes.
///
@@ -243,7 +228,6 @@ private void RemoveFromObserversWithoutSynchronization(NetworkConnection connect
///
/// Rebuilds observers on all NetworkObjects for all connections.
///
-
public void RebuildObservers(bool timedOnly = false)
{
List nobCache = RetrieveOrderedSpawnedObjects();
@@ -255,11 +239,9 @@ public void RebuildObservers(bool timedOnly = false)
CollectionCaches.Store(connCache);
}
-
///
/// Rebuilds observers for all connections for a NetworkObject.
///
-
public void RebuildObservers(NetworkObject nob, bool timedOnly = false)
{
List nobCache = CollectionCaches.RetrieveList(nob);
@@ -270,10 +252,10 @@ public void RebuildObservers(NetworkObject nob, bool timedOnly = false)
CollectionCaches.Store(nobCache);
CollectionCaches.Store(connCache);
}
+
///
/// Rebuilds observers on all NetworkObjects for a connection.
///
-
public void RebuildObservers(NetworkConnection connection, bool timedOnly = false)
{
List nobCache = RetrieveOrderedSpawnedObjects();
@@ -288,7 +270,6 @@ public void RebuildObservers(NetworkConnection connection, bool timedOnly = fals
///
/// Rebuilds observers on NetworkObjects.
///
-
public void RebuildObservers(IList nobs, bool timedOnly = false)
{
List conns = RetrieveAuthenticatedConnections();
@@ -297,10 +278,10 @@ public void RebuildObservers(IList nobs, bool timedOnly = false)
CollectionCaches.Store(conns);
}
+
///
/// Rebuilds observers on all objects for connections.
///
-
public void RebuildObservers(IList connections, bool timedOnly = false)
{
List nobCache = RetrieveOrderedSpawnedObjects();
@@ -313,7 +294,6 @@ public void RebuildObservers(IList connections, bool timedOnl
///
/// Rebuilds observers on NetworkObjects for connections.
///
-
public void RebuildObservers(IList nobs, NetworkConnection conn, bool timedOnly = false)
{
List connCache = CollectionCaches.RetrieveList(conn);
@@ -326,7 +306,6 @@ public void RebuildObservers(IList nobs, NetworkConnection conn,
///
/// Rebuilds observers for connections on NetworkObject.
///
-
public void RebuildObservers(NetworkObject networkObject, IList connections, bool timedOnly = false)
{
List nobCache = CollectionCaches.RetrieveList(networkObject);
@@ -339,7 +318,6 @@ public void RebuildObservers(NetworkObject networkObject, IList
/// Rebuilds observers on NetworkObjects for connections.
///
-
public void RebuildObservers(IList nobs, IList conns, bool timedOnly = false)
{
List nobCache = CollectionCaches.RetrieveList();
@@ -358,8 +336,7 @@ public void RebuildObservers(IList nobs, IList
//Send if change.
if (_writer.Length > 0)
{
- NetworkManager.TransportManager.SendToClient(
- (byte)Channel.Reliable, _writer.GetArraySegment(), nc);
+ NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, _writer.GetArraySegment(), nc);
_writer.Reset();
foreach (NetworkObject n in nobCache)
@@ -370,11 +347,9 @@ public void RebuildObservers(IList nobs, IList
CollectionCaches.Store(nobCache);
}
-
///
/// Rebuilds observers for a connection on NetworkObject.
///
-
public void RebuildObservers(NetworkObject nob, NetworkConnection conn, bool timedOnly = false)
{
if (ApplicationState.IsQuitting())
@@ -391,9 +366,7 @@ public void RebuildObservers(NetworkObject nob, NetworkConnection conn, bool tim
else
return;
- NetworkManager.TransportManager.SendToClient(
- (byte)Channel.Reliable,
- _writer.GetArraySegment(), conn);
+ NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, _writer.GetArraySegment(), conn);
/* If spawning then also invoke server
* start events, such as buffer last
@@ -409,7 +382,6 @@ public void RebuildObservers(NetworkObject nob, NetworkConnection conn, bool tim
///
/// Rebuilds observers for a connection on NetworkObject.
///
-
internal void RebuildObservers(NetworkObject nob, NetworkConnection conn, List addedNobs, bool timedOnly = false)
{
if (ApplicationState.IsQuitting())
@@ -437,14 +409,11 @@ internal void RebuildObservers(NetworkObject nob, NetworkConnection conn, List sceneNobs = CollectionCaches.RetrieveList();
- Scenes.GetSceneNetworkObjects(s, false, true, true, ref sceneNobs);
+ Scenes.GetSceneNetworkObjects(s, firstOnly: false, errorOnDuplicates: true, ignoreUnsetSceneIds: true, ref sceneNobs);
_loadedSceneNetworkObjects.Add((Time.frameCount, sceneNobs));
+
+ InitializeRootNetworkObjects(sceneNobs);
+ }
+
+ ///
+ /// Sets initial values for NetworkObjects.
+ ///
+ ///
+ private void InitializeRootNetworkObjects(List nobs)
+ {
+ //Initialize sceneNobs cache, but do not invoke callbacks till next frame.
+ foreach (NetworkObject nob in nobs)
+ {
+ if (nob.IsSceneObject && !nob.IsNested)
+ nob.SetInitializedValues(parentNob: null);
+ }
}
///
@@ -387,9 +403,10 @@ private void SetupSceneObjects(Scene s)
return;
List sceneNobs = CollectionCaches.RetrieveList();
- Scenes.GetSceneNetworkObjects(s, false, true, true, ref sceneNobs);
+ Scenes.GetSceneNetworkObjects(s, firstOnly: false, errorOnDuplicates: true, ignoreUnsetSceneIds: true, ref sceneNobs);
SetupSceneObjects(sceneNobs);
+
CollectionCaches.Store(sceneNobs);
}
@@ -399,16 +416,20 @@ private void SetupSceneObjects(Scene s)
///
private void SetupSceneObjects(List sceneNobs)
{
- //Initialize the objects if not yet done.
- foreach (NetworkObject obj in sceneNobs)
- obj.SetInitializedValues(null);
+ InitializeRootNetworkObjects(sceneNobs);
- int startCount = sceneNobs.Count;
//Sort the nobs based on initialization order.
- bool initializationOrderChanged = false;
List cache = CollectionCaches.RetrieveList();
+
+ //First order root objects.
foreach (NetworkObject item in sceneNobs)
- OrderRootByInitializationOrder(item, cache, ref initializationOrderChanged);
+ {
+ if (item.IsNested)
+ continue;
+
+ cache.AddOrdered(item);
+ }
+
OrderNestedByInitializationOrder(cache);
//Store sceneNobs.
CollectionCaches.Store(sceneNobs);
@@ -419,7 +440,7 @@ private void SetupSceneObjects(List sceneNobs)
{
NetworkObject nob = cache[i];
//Only setup if a scene object and not initialzied.
- if (nob.IsNetworked && nob.IsSceneObject && nob.IsDeinitializing)
+ if (nob.GetIsNetworked() && nob.IsSceneObject && nob.IsDeinitializing)
{
base.AddToSceneObjects(nob);
/* If was active in the editor (before hitting play), or currently active
@@ -447,7 +468,7 @@ private void SetupSceneObjects(List sceneNobs)
/// Override ObjectId to use.
private void SetupWithoutSynchronization(NetworkObject nob, NetworkConnection ownerConnection = null, int? objectId = null)
{
- if (nob.IsNetworked)
+ if (nob.GetIsNetworked())
{
if (objectId == null)
objectId = GetNextNetworkObjectId();
diff --git a/Assets/FishNet/Runtime/Managing/Server/ServerManager.QOL.cs b/Assets/FishNet/Runtime/Managing/Server/ServerManager.QOL.cs
index 171c743d7..e00ddd502 100644
--- a/Assets/FishNet/Runtime/Managing/Server/ServerManager.QOL.cs
+++ b/Assets/FishNet/Runtime/Managing/Server/ServerManager.QOL.cs
@@ -111,7 +111,7 @@ public void Spawn(GameObject go, NetworkConnection ownerConnection = null, Unity
/// Connection to give ownership to.
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
{
- if (!nob.IsSpawnable)
+ if (!nob.GetIsSpawnable())
{
NetworkManager.LogWarning($"NetworkObject {nob} cannot be spawned because it is not marked as spawnable.");
return;
diff --git a/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs b/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs
index 121495842..a424fc25e 100644
--- a/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs
+++ b/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs
@@ -503,6 +503,8 @@ private void Transport_OnServerConnectionState(ServerConnectionStateArgs args)
MatchCondition.StoreCollections(NetworkManager);
//Despawn without synchronizing network objects.
Objects.DespawnWithoutSynchronization(true);
+ //Clear all clients.
+ Clients.Clear();
}
Objects.OnServerConnectionState(args);
diff --git a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs
index 5e6a3e6ed..df0207aa3 100644
--- a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs
+++ b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs
@@ -757,11 +757,16 @@ public double GetTickPercentAsDouble()
if (NetworkManager == null)
return 0d;
- double delta = (NetworkManager.IsServerStarted) ? TickDelta : _adjustedTickDelta;
- double percent = (_elapsedTickTime / delta);
+ double percent = (_elapsedTickTime / TickDelta);
return percent;
}
+ ///
+ /// Returns the current elapsed amount for the next tick.
+ ///
+ ///
+ public double GetTickElapsedAsDouble() => _elapsedTickTime;
+
///
/// Returns the percentage of how far the TimeManager is into the next tick.
/// Value will return between 0 and 100.
diff --git a/Assets/FishNet/Runtime/Object/Attributes.cs b/Assets/FishNet/Runtime/Object/Attributes.cs
index 3e9e387f0..b19c647c5 100644
--- a/Assets/FishNet/Runtime/Object/Attributes.cs
+++ b/Assets/FishNet/Runtime/Object/Attributes.cs
@@ -165,7 +165,7 @@ public class SyncObjectAttribute : PropertyAttribute
/// Synchronizes a variable from server to clients automatically.
/// Value must be changed on server.
///
- [Obsolete("This no longer functions. See console errors and Break Solutions in the documentation for resolution.")]
+ [Obsolete("This no longer functions. Use SyncVar instead. See console errors and Break Solutions in the documentation for resolution.")]
[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
public class SyncVarAttribute : PropertyAttribute
{
diff --git a/Assets/FishNet/Runtime/Object/Editor/NetworkObjectEditor.cs b/Assets/FishNet/Runtime/Object/Editor/NetworkObjectEditor.cs
index 1c49302cb..a69b4bb00 100644
--- a/Assets/FishNet/Runtime/Object/Editor/NetworkObjectEditor.cs
+++ b/Assets/FishNet/Runtime/Object/Editor/NetworkObjectEditor.cs
@@ -31,8 +31,8 @@ public class NetworkObjectEditor : Editor
private SerializedProperty _enableTeleport;
private SerializedProperty _teleportThreshold;
-
-
+ private int _tabIndex;
+
protected virtual void OnEnable()
{
_isNetworked = serializedObject.FindProperty(nameof(_isNetworked));
@@ -65,20 +65,36 @@ public override void OnInspectorGUI()
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(nob), typeof(NetworkObject), false);
GUI.enabled = true;
-
- EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
- EditorGUI.indentLevel++;
- EditorGUILayout.PropertyField(_isNetworked);
- EditorGUILayout.PropertyField(_isSpawnable);
- EditorGUILayout.PropertyField(_isGlobal);
- EditorGUILayout.PropertyField(_initializeOrder);
- EditorGUILayout.PropertyField(_defaultDespawnType);
- EditorGUI.indentLevel--;
EditorGUILayout.Space();
- EditorGUILayout.LabelField("Prediction", EditorStyles.boldLabel);
- EditorGUI.indentLevel++;
- EditorGUILayout.PropertyField(_enablePrediction);
+ _tabIndex = GUILayout.Toolbar (_tabIndex, new string[] {"Settings", "Prediction"});
+ EditorGUILayout.Space();
+ switch (_tabIndex)
+ {
+ case 0:
+ ShowSettingsTab();
+ break;
+ case 1:
+ ShowPredictionTab();
+ break;
+ default:
+ ShowSettingsTab();
+ break;
+ }
+
+
+ void ShowSettingsTab()
+ {
+ EditorGUILayout.PropertyField(_isNetworked);
+ EditorGUILayout.PropertyField(_isSpawnable);
+ EditorGUILayout.PropertyField(_isGlobal);
+ EditorGUILayout.PropertyField(_initializeOrder);
+ EditorGUILayout.PropertyField(_defaultDespawnType);
+ }
+ void ShowPredictionTab()
+ {
+
+ EditorGUILayout.PropertyField(_enablePrediction);
if (_enablePrediction.boolValue == true)
{
EditorGUI.indentLevel++;
@@ -136,8 +152,8 @@ public override void OnInspectorGUI()
EditorGUI.indentLevel--;
}
- EditorGUI.indentLevel--;
-
+
+ }
serializedObject.ApplyModifiedProperties();
}
diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour.Callbacks.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour.Callbacks.cs
index 75e953f43..2eac0ad07 100644
--- a/Assets/FishNet/Runtime/Object/NetworkBehaviour.Callbacks.cs
+++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour.Callbacks.cs
@@ -60,6 +60,8 @@ internal void InvokeSyncTypeOnStartCallbacks(bool asServer)
///
internal void InvokeSyncTypeOnStopCallbacks(bool asServer)
{
+ // if (_syncTypes == null)
+ // return;
foreach (SyncBase item in _syncTypes.Values)
item.OnStopCallback(asServer);
}
diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs
index 3a24f5bde..72de8bfc8 100644
--- a/Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs
+++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs
@@ -1,4 +1,6 @@
-#if UNITY_EDITOR || DEVELOPMENT_BUILD
+#if !FISHNET_STABLE_MODE
+
+#if UNITY_EDITOR || DEVELOPMENT_BUILD
#define DEVELOPMENT
#endif
using FishNet.CodeGenerating;
@@ -18,7 +20,6 @@
using GameKit.Dependencies.Utilities;
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Runtime.CompilerServices;
using GameKit.Dependencies.Utilities.Types;
using UnityEngine;
@@ -379,9 +380,7 @@ protected internal void ClearReplicateCache_Internal(BasicQueue replic
replicatesHistory[i].Dispose();
replicatesHistory.Clear();
- // for (int i = 0; i < reconcilesHistory.Count; i++)
- // reconcilesHistory[i].Dispose();
- // reconcilesHistory.Clear();
+ ClearReconcileHistory(reconcilesHistory);
}
///
@@ -673,7 +672,7 @@ protected internal void Replicate_NonAuthoritative(ReplicateUserLogicDelegate
_remainingReconcileResends = pm.RedundancyCount;
ReplicateData(queueEntry, ReplicateState.CurrentCreated);
-
+
//Update count since old entries were dropped and one replicate run.
count = replicatesQueue.Count;
@@ -686,7 +685,7 @@ protected internal void Replicate_NonAuthoritative(ReplicateUserLogicDelegate
const byte maximumAllowedConsumes = 1;
int maximumPossibleConsumes = (count - leaveInBuffer);
int consumeAmount = Mathf.Min(maximumAllowedConsumes, maximumPossibleConsumes);
-
+
for (int i = 0; i < consumeAmount; i++)
ReplicateData(replicatesQueue.Dequeue(), ReplicateState.CurrentCreated);
}
@@ -1227,34 +1226,35 @@ protected internal virtual void Reconcile_Client_Start() { }
[MakePublic]
protected internal void Reconcile_Client_Local(RingBuffer> reconcilesHistory, T data) where T : IReconcileData
{
- // //Server does not need to store these locally.
- // if (_networkObjectCache.IsServerStarted)
- // return;
- //
- // /* This is called by the local client when creating
- // * a local reconcile state. These states should always
- // * be in order, so we will add data to the end
- // * of the collection. */
- //
- // /* These datas are used to fill missing reconciles
- // * be it the packet dropped, server doesnt need to send,
- // * or if the player is throttling reconciles. */
- //
- // uint tick = _networkObjectCache.PredictionManager.GetCreateReconcileTick(_networkObjectCache.IsOwner);
- // //Tick couldn't be retrieved.
- // if (tick == TimeManager.UNSET_TICK)
- // return;
- //
- // data.SetTick(tick);
- //
- // //Build LocalReconcile.
- // LocalReconcile lr = new();
- // lr.Initialize(tick, data);
- //
- // reconcilesHistory.Add(lr);
- }
-
- //private bool _skip = false;
+ //Server does not need to store these locally.
+ if (_networkObjectCache.IsServerStarted)
+ return;
+ if (!_networkObjectCache.PredictionManager.CreateLocalStates)
+ return;
+
+ /* This is called by the local client when creating
+ * a local reconcile state. These states should always
+ * be in order, so we will add data to the end
+ * of the collection. */
+
+ /* These datas are used to fill missing reconciles
+ * be it the packet dropped, server doesnt need to send,
+ * or if the player is throttling reconciles. */
+
+ uint tick = _networkObjectCache.PredictionManager.GetCreateReconcileTick(_networkObjectCache.IsOwner);
+ //Tick couldn't be retrieved.
+ if (tick == TimeManager.UNSET_TICK)
+ return;
+
+ data.SetTick(tick);
+
+ //Build LocalReconcile.
+ LocalReconcile lr = new();
+ lr.Initialize(tick, data);
+
+ reconcilesHistory.Add(lr);
+ }
+
///
/// Processes a reconcile for client.
///
@@ -1262,92 +1262,100 @@ protected internal void Reconcile_Client_Local(RingBuffer>
[MakePublic]
protected internal void Reconcile_Client(ReconcileUserLogicDelegate reconcileDel, RingBuffer replicatesHistory, RingBuffer> reconcilesHistory, T data) where T : IReconcileData where T2 : IReplicateData
{
+ bool isBehaviourReconciling = IsBehaviourReconciling;
- if (!IsBehaviourReconciling)
+ const long unsetHistoryIndex = -1;
+ long historyIndex = unsetHistoryIndex;
+
+ /* There should always be entries, except when the object
+ * first spawns.
+ *
+ * Find the history index associated with the reconcile tick. */
+ if (reconcilesHistory.Count > 0)
{
- Debug.Log("Not reconciling.");
- return;
+ //If reconcile data received then use that tick, otherwise get estimated tick for this reconcile.
+ uint reconcileTick = (isBehaviourReconciling) ? data.GetTick() : _networkObjectCache.PredictionManager.GetReconcileStateTick(_networkObjectCache.IsOwner);
+
+ uint firstHistoryTick = reconcilesHistory[0].Tick;
+ historyIndex = ((long)reconcileTick - (long)firstHistoryTick);
+
+ /* If difference is negative then
+ * the first history is beyond the tick being reconciled.
+ * EG: if history index 0 is 100 and reconcile tick is 90 then
+ * (90 - 100) = -10.
+ * This should only happen when first connecting and data hasn't been made yet. */
+ if (!IsHistoryIndexValid((int)historyIndex))
+ {
+ historyIndex = unsetHistoryIndex;
+ ClearReconcileHistory(reconcilesHistory);
+ }
+ //Valid history index.
+ else
+ {
+ //Get the tick at the index.
+ uint lrTick = reconcilesHistory[(int)historyIndex].Tick;
+ /* Since we store reconcile data every tick moving ahead a set number of ticks
+ * should usually match up to the reconcile tick. There are exceptions where the tick
+ * used to locally create the reconcile was for non owner, so using the server tick,
+ * and there is a slight misalignment in the server tick. This is not unusual as the
+ * client corrects it's tick timing regularly, but such an alignment could make this not line up. */
+ /* If the history tick does not match the reconcile tick try to find
+ * the correct history tick. This should rarely happen but since these reconciles
+ * are created locally and client timing can vary slightly it's still possible. */
+ if (lrTick != reconcileTick)
+ {
+ /* Get the difference between what tick is stored vs reconcile tick.
+ * Adjust the index based on this difference. */
+ long tickDifference = ((long)reconcileTick - (long)lrTick);
+
+ /* Add difference onto history index and again validate that it
+ * is in range of the collection. */
+ historyIndex += tickDifference;
+ //Invalid.
+ if (!IsHistoryIndexValid((int)historyIndex))
+ {
+ /* This shouldn't ever happen. Something went very wrong if here.
+ * When this does happen clear out the entire history collection
+ * and start over. */
+ ClearReconcileHistory(reconcilesHistory);
+ //Unset index.
+ 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(), null, Reader.DataSource.Server);
+ data = Reconcile_Reader_Local(localReconcile.Tick, reader);
+ ReaderPool.Store(reader);
+ }
+
+ }
+ }
+
+ //Returns if a history index can be within history collection.
+ bool IsHistoryIndexValid(int index) => (index >= 0 && (index < reconcilesHistory.Count));
+
+ //Dispose of old reconcile histories.
+ if (historyIndex != unsetHistoryIndex)
+ {
+ int index = (int)historyIndex;
+ //If here everything is good, remove up to used index.
+ for (int i = 0; i < index; i++)
+ reconcilesHistory[i].Dispose();
+
+ reconcilesHistory.RemoveRange(true, (int)historyIndex);
}
- //
- // if (_skip)
- // IsBehaviourReconciling = false;
- //
- // _skip = !_skip;
- // /* If there is no data to reconcile from then
- // * try to pull from the reconcilesHistory; this is the
- // * collection the client had made locally.*/
- // if (!IsBehaviourReconciling)
- // {
- // //Should not ever happen, but cannot proceed without entries.
- // if (reconcilesHistory.Count == 0)
- // return;
- //
- // uint firstHistoryTick = reconcilesHistory[0].Tick;
- // uint reconcileTick = _networkObjectCache.PredictionManager.GetReconcileStateTick(_networkObjectCache.IsOwner); //(_lastReconcileTick + 1);
- //
- // long historyIndex = ((long)reconcileTick - (long)firstHistoryTick);
- //
- // /* If difference is negative then negative then
- // * the first history is beyond the tick being reconciled.
- // * EG: if history index 0 is 100 and reconcile tick is 90 then
- // * (90 - 100) = -10.
- // * This should only happen when first connecting and data hasn't been made yet. */
- // if (historyIndex < 0)
- // return;
- //
- // //Not enough histories to grab local reconcile form.
- // if (reconcilesHistory.Count <= historyIndex)
- // return;
- //
- // LocalReconcile localReconcile = reconcilesHistory[(int)historyIndex];
- // uint lrTick = localReconcile.Tick;
- // /* Since we store reconcile data every tick moving ahead a set number of ticks
- // * should usually match up to the reconcile tick. There are exceptions where the tick
- // * used to locally create the reconcile was for non owner, so using the server tick,
- // * and there is a slight misalignment in the server tick. This is not unusual as the
- // * client corrects it's tick timing regularly, but such an alignment could make this not line up. */
- // if (lrTick != reconcileTick)
- // {
- // //When tick doesn't match still allow use of the entry if it's within 3 ticks.
- // long mismatchDifference = Math.Abs((long)lrTick - (long)reconcileTick);
- // Debug.LogError($"Match difference of {mismatchDifference}");
- // if (mismatchDifference > 3)
- // return;
- // }
- // //Before disposing get the writer and call reconcile reader so it's parsed.
- // PooledWriter reconcileWritten = reconcilesHistory[(int)historyIndex].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(), null, Reader.DataSource.Server);
- // data = Reconcile_Reader(lrTick, reader);
- // ReaderPool.Store(reader);
- //
- // //If here everything is good, remove up to used index.
- // for (int i = 0; i < historyIndex; i++)
- // reconcilesHistory[i].Dispose();
- //
- // reconcilesHistory.RemoveRange(true, (int)historyIndex);
- //
- // //Uses iteration to try and find the tick.
- // bool FindTickThroughIteration(uint tickToFind, out long index)
- // {
- // for (int i = 0; i < reconcilesHistory.Count; i++)
- // {
- // uint historyTick = reconcilesHistory[i].Tick;
- // //Exact match.
- // if (historyTick == tickToFind)
- // {
- // index = i;
- // return true;
- // }
- // }
- //
- // //Failed to find.
- // index = -1;
- // return false;
- // }
- // }
+ //If does not have data still then exit method.
+ if (!IsBehaviourReconciling)
+ return;
//Set on the networkObject that a reconcile can now occur.
_networkObjectCache.IsObjectReconciling = true;
@@ -1415,6 +1423,17 @@ internal void Reconcile_Client_End()
IsBehaviourReconciling = false;
}
+ ///
+ /// Disposes and clears LocalReconciles.
+ ///
+ private void ClearReconcileHistory(RingBuffer> reconcilesHistory) where T : IReconcileData
+ {
+ foreach (LocalReconcile localReconcile in reconcilesHistory)
+ localReconcile.Dispose();
+
+ reconcilesHistory.Clear();
+ }
+
///
/// Reads a reconcile from the server.
///
@@ -1439,18 +1458,1356 @@ public void Reconcile_Reader(PooledReader reader, ref T lastReconciledata, Ch
_lastReadReconcileRemoteTick = tick;
}
- // ///
- // /// Reads a local reconcile from the client.
- // ///
- // public T Reconcile_Reader(uint tick, PooledReader reader) where T : IReconcileData
- // {
- // T newData = reader.ReadReconcile();
- // newData.SetTick(tick);
- //
- // IsBehaviourReconciling = true;
- //
- // return newData;
- // }
+ ///
+ /// Reads a local reconcile from the client.
+ ///
+ public T Reconcile_Reader_Local(uint tick, PooledReader reader) where T : IReconcileData
+ {
+ T newData = reader.ReadReconcile();
+ newData.SetTick(tick);
+
+ IsBehaviourReconciling = true;
+
+ return newData;
+ }
+
+ ///
+ /// Sets the last tick this NetworkBehaviour replicated with.
+ ///
+ /// True to set unordered value, false to set ordered.
+ private void SetReplicateTick(uint value, bool createdReplicate)
+ {
+ _lastOrderedReplicatedTick = value;
+ _networkObjectCache.SetReplicateTick(value, createdReplicate);
+ }
+ }
+}
+
+#else
+#if UNITY_EDITOR || DEVELOPMENT_BUILD
+#define DEVELOPMENT
+#endif
+using FishNet.CodeGenerating;
+using FishNet.Connection;
+using FishNet.Documenting;
+using FishNet.Managing;
+using FishNet.Managing.Logging;
+using FishNet.Managing.Predicting;
+using FishNet.Managing.Server;
+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;
+using GameKit.Dependencies.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using GameKit.Dependencies.Utilities.Types;
+using UnityEngine;
+
+[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
+
+namespace FishNet.Object
+{
+ #region Types.
+ internal static class ReplicateTickFinder
+ {
+ public enum DataPlacementResult
+ {
+ ///
+ /// Something went wrong; this should never be returned.
+ ///
+ Error,
+ ///
+ /// Tick was found on an index.
+ ///
+ Exact,
+ ///
+ /// Tick was not found because it is lower than any of the replicates.
+ /// This is also used when there are no datas.
+ ///
+ InsertBeginning,
+ ///
+ /// Tick was not found but can be inserted in the middle of the collection.
+ ///
+ InsertMiddle,
+ ///
+ /// Tick was not found because it is larger than any of the replicates.
+ ///
+ InsertEnd,
+ }
+
+ ///
+ /// Gets the index in replicates where the tick matches.
+ ///
+ public static int GetReplicateHistoryIndex(uint tick, RingBuffer replicatesHistory, out DataPlacementResult findResult) where T : IReplicateData
+ {
+ int replicatesCount = replicatesHistory.Count;
+ if (replicatesCount == 0)
+ {
+ findResult = DataPlacementResult.InsertBeginning;
+ return 0;
+ }
+
+ uint firstTick = replicatesHistory[0].GetTick();
+
+ //Try to find by skipping ahead the difference between tick and start.
+ int diff = (int)(tick - firstTick);
+ /* If the difference is larger than replicatesCount
+ * then that means the replicates collection is missing
+ * entries. EG if replicates values were 4, 7, 10 and tick were
+ * 10 the difference would be 6. While replicates does contain the value
+ * there is no way it could be found by pulling index 'diff' since that
+ * would be out of bounds. This should never happen under normal conditions, return
+ * missing if it does. */
+ //Do not need to check less than 0 since we know if here tick is larger than first entry.
+ if (diff >= replicatesCount)
+ {
+ //Try to return value using brute force.
+ int index = FindIndexBruteForce(out findResult);
+ return index;
+ }
+ else if (diff < 0)
+ {
+ findResult = DataPlacementResult.InsertBeginning;
+ return 0;
+ }
+ else
+ {
+ /* If replicatesHistory contained the ticks
+ * of 1 2 3 4 5, and the tick is 3, then the difference
+ * would be 2 (because 3 - 1 = 2). As we can see index
+ * 2 of replicatesHistory does indeed return the proper tick. */
+ //Expected diff to be result but was not.
+ if (replicatesHistory[diff].GetTick() != tick)
+ {
+ //Try to return value using brute force.
+ int index = FindIndexBruteForce(out findResult);
+ return index;
+ }
+ //Exact was found, this is the most ideal situation.
+ else
+ {
+ findResult = DataPlacementResult.Exact;
+ return diff;
+ }
+ }
+
+ //Tries to find the index by brute forcing the collection.
+ int FindIndexBruteForce(out DataPlacementResult result)
+ {
+ /* Some quick exits to save perf. */
+ //If tick is lower than first then it must be inserted at the beginning.
+ if (tick < firstTick)
+ {
+ result = DataPlacementResult.InsertBeginning;
+ return 0;
+ }
+ //If tick is larger the last then it must be inserted at the end.
+ else if (tick > replicatesHistory[replicatesCount - 1].GetTick())
+ {
+ result = DataPlacementResult.InsertEnd;
+ return replicatesCount;
+ }
+ else
+ {
+ //Brute check.
+ for (int i = 0; i < replicatesCount; i++)
+ {
+ uint lTick = replicatesHistory[i].GetTick();
+ //Exact match found.
+ if (lTick == tick)
+ {
+ result = DataPlacementResult.Exact;
+ return i;
+ }
+ /* The checked data is greater than
+ * what was being searched. This means
+ * to insert right before it. */
+ else if (lTick > tick)
+ {
+ result = DataPlacementResult.InsertMiddle;
+ return i;
+ }
+ }
+
+ //Should be impossible to get here.
+ result = DataPlacementResult.Error;
+ return -1;
+ }
+ }
+ }
+ }
+ #endregion
+
+ public abstract partial class NetworkBehaviour : MonoBehaviour
+ {
+ #region Public.
+ // ///
+ // /// True if this Networkbehaviour implements prediction methods.
+ // ///
+ // [APIExclude]
+ // [MakePublic]
+ // protected internal bool UsesPrediction;
+ ///
+ /// True if this NetworkBehaviour is reconciling.
+ /// If this NetworkBehaviour does not implemnent prediction methods this value will always be false.
+ /// Value will be false if there is no data to reconcile to, even if the PredictionManager IsReconciling.
+ /// Data may be missing if it were intentionally not sent, or due to packet loss.
+ ///
+ public bool IsBehaviourReconciling { get; internal set; }
+ #endregion
+
+ #region Private.
+ ///
+ /// Registered Replicate methods.
+ ///
+ private Dictionary _replicateRpcDelegates;
+ ///
+ /// Registered Reconcile methods.
+ ///
+ private Dictionary _reconcileRpcDelegates;
+ ///
+ /// Number of replicate resends which may occur.
+ ///
+ private int _remainingReplicateResends;
+ ///
+ /// Number of reconcile resends which may occur.
+ ///
+ private int _remainingReconcileResends;
+ ///
+ /// Last replicate tick read from remote. This can be the server reading a client or the other way around.
+ ///
+ private uint _lastReplicateReadRemoteTick = TimeManager.UNSET_TICK;
+ ///
+ /// Tick when replicates should begun to run. This is set and used when inputs are just received and need to queue to create a buffer.
+ ///
+ private uint _replicateStartTick = TimeManager.UNSET_TICK;
+ ///
+ /// Last tick to replicate which was not replayed.
+ ///
+ private uint _lastOrderedReplicatedTick = TimeManager.UNSET_TICK;
+ ///
+ /// Last tick read for a replicate.
+ ///
+ private uint _lastReadReplicateTick = TimeManager.UNSET_TICK;
+ ///
+ /// Ticks of replicates that have been read and not reconciled past.
+ /// This is only used on non-authoritative objects.
+ ///
+ private List _readReplicateTicks;
+ ///
+ /// Last tick read for a reconcile. This is only set on the client.
+ ///
+ private uint _lastReadReconcileRemoteTick = TimeManager.UNSET_TICK;
+ ///
+ /// Last tick this object reconciled on.
+ ///
+ private uint _lastReconcileTick = TimeManager.UNSET_TICK;
+ ///
+ /// Last tick when created data was replicated.
+ /// Do not read this value directly other than when being used within GetLastCreatedTick().
+ ///
+ private uint _lastCreatedTick = TimeManager.UNSET_TICK;
+ ///
+ /// Last values when checking for transform changes since previous tick.
+ ///
+ private Vector3 _lastTransformPosition;
+ ///
+ /// Last values when checking for transform changes since previous tick.
+ ///
+ private Quaternion _lastTransformRotation;
+ ///
+ /// Last values when checking for transform changes since previous tick.
+ ///
+ private Vector3 _lastTransformScale;
+ #endregion
+
+ #region Consts.
+ ///
+ /// Default minimum number of entries to allow in the replicates queue which are beyond expected count.
+ ///
+ private const sbyte REPLICATES_ALLOWED_OVER_BUFFER = 1;
+ #endregion
+
+ ///
+ /// Initializes the NetworkBehaviour for prediction.
+ ///
+ internal void Preinitialize_Prediction(bool asServer)
+ {
+ if (!asServer)
+ {
+ _readReplicateTicks = CollectionCaches.RetrieveList();
+ }
+ }
+
+ ///
+ /// Deinitializes the NetworkBehaviour for prediction.
+ ///
+ internal void Deinitialize_Prediction(bool asServer)
+ {
+ CollectionCaches.StoreAndDefault(ref _readReplicateTicks);
+ }
+
+ ///
+ /// Called when the object is destroyed.
+ ///
+ internal void OnDestroy_Prediction()
+ {
+ CollectionCaches.StoreAndDefault(ref _replicateRpcDelegates);
+ CollectionCaches.StoreAndDefault(ref _reconcileRpcDelegates);
+ }
+
+ ///
+ /// Registers a RPC method.
+ /// Internal use.
+ ///
+ ///
+ ///
+ [MakePublic]
+ internal void RegisterReplicateRpc(uint hash, ReplicateRpcDelegate del)
+ {
+ if (_replicateRpcDelegates == null)
+ _replicateRpcDelegates = CollectionCaches.RetrieveDictionary();
+ _replicateRpcDelegates[hash] = del;
+ }
+
+ ///
+ /// Registers a RPC method.
+ /// Internal use.
+ ///
+ ///
+ ///
+ [MakePublic]
+ internal void RegisterReconcileRpc(uint hash, ReconcileRpcDelegate del)
+ {
+ if (_reconcileRpcDelegates == null)
+ _reconcileRpcDelegates = CollectionCaches.RetrieveDictionary();
+ _reconcileRpcDelegates[hash] = del;
+ }
+
+ ///
+ /// Called when a replicate is received.
+ ///
+ internal void OnReplicateRpc(uint? methodHash, PooledReader reader, NetworkConnection sendingClient, Channel channel)
+ {
+ if (methodHash == null)
+ methodHash = ReadRpcHash(reader);
+
+ 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.");
+ }
+
+ ///
+ /// Called when a reconcile is received.
+ ///
+ 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.");
+ }
+
+ ///
+ /// Resets cached ticks used by prediction, such as last read and replicate tick.
+ /// This is generally used when the ticks will be different then what was previously used; eg: when ownership changes.
+ ///
+ internal void ResetState_Prediction(bool asServer)
+ {
+ if (!asServer)
+ {
+ if (_readReplicateTicks != null)
+ _readReplicateTicks.Clear();
+ _lastReadReconcileRemoteTick = TimeManager.UNSET_TICK;
+ _lastReconcileTick = TimeManager.UNSET_TICK;
+ }
+
+ _lastOrderedReplicatedTick = TimeManager.UNSET_TICK;
+ _lastReplicateReadRemoteTick = TimeManager.UNSET_TICK;
+ _lastReadReplicateTick = TimeManager.UNSET_TICK;
+ _lastCreatedTick = TimeManager.UNSET_TICK;
+
+ ClearReplicateCache();
+ }
+
+ ///
+ /// Clears cached replicates for server and client. This can be useful to call on server and client after teleporting.
+ ///
+ public virtual void ClearReplicateCache() { }
+
+ ///
+ /// Clears cached replicates and histories.
+ ///
+ [MakePublic]
+ [APIExclude]
+ protected internal void ClearReplicateCache_Internal(BasicQueue replicatesQueue, RingBuffer replicatesHistory, RingBuffer> reconcilesHistory, ref T lastFirstReadReplicate) where T : IReplicateData where T2 : IReconcileData
+ {
+ while (replicatesQueue.Count > 0)
+ {
+ T data = replicatesQueue.Dequeue();
+ data.Dispose();
+ }
+
+ lastFirstReadReplicate.Dispose();
+ lastFirstReadReplicate = default;
+
+ for (int i = 0; i < replicatesHistory.Count; i++)
+ replicatesHistory[i].Dispose();
+ replicatesHistory.Clear();
+ }
+
+ ///
+ /// Sends a RPC to target.
+ /// Internal use.
+ ///
+ [MakePublic]
+ [APIExclude]
+ protected internal void Server_SendReconcileRpc(uint hash, ref T lastReconcileData, T reconcileData, Channel channel) where T : IReconcileData
+ {
+ if (!IsSpawned)
+ return;
+
+ //No more redundancy left. Check if transform may have changed.
+ if (_remainingReconcileResends == 0)
+ return;
+ else
+ _remainingReconcileResends--;
+
+ //No owner and no state forwarding, nothing to do.
+ bool stateForwarding = _networkObjectCache.EnableStateForwarding;
+ if (!Owner.IsValid && !stateForwarding)
+ return;
+
+ /* Set the channel for Rpcs to reliable to that the length
+ * is written. The data does not actually send reliable, unless
+ * the channel is of course that to start. */
+ /* This is a temporary solution to resolve an issue which was
+ * causing parsing problems due to states sending unreliable and reliable
+ * headers being written, or sending reliably and unreliable headers being written.
+ * Using an extra byte to write length is more preferred than always forcing reliable
+ * until properly resolved. */
+ const Channel rpcChannel = Channel.Reliable;
+
+ PooledWriter methodWriter = WriterPool.Retrieve();
+ /* Tick does not need to be written because it will always
+ * be the localTick of the server. For the clients, this will
+ * be the LastRemoteTick of the packet.
+ *
+ * The exception is for the owner, which we send the last replicate
+ * tick so the owner knows which to roll back to. */
+
+//#if !FISHNET_STABLE_MODE
+#if DO_NOT_USE
+ methodWriter.WriteDeltaReconcile(lastReconcileData, reconcileData, GetDeltaSerializeOption());
+#else
+ methodWriter.WriteReconcile(reconcileData);
+#endif
+ lastReconcileData = reconcileData;
+
+ PooledWriter writer;
+#if DEVELOPMENT
+ if (NetworkManager.DebugManager.ReconcileRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
+#else
+ if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
+#endif
+ writer = CreateLinkedRpc(link, methodWriter, rpcChannel);
+ else
+ writer = CreateRpc(hash, methodWriter, PacketId.Reconcile, rpcChannel);
+
+ //If state forwarding is not enabled then only send to owner.
+ if (!stateForwarding)
+ {
+ Owner.WriteState(writer);
+ }
+ //State forwarding, send to all.
+ else
+ {
+ foreach (NetworkConnection nc in Observers)
+ nc.WriteState(writer);
+ }
+
+ methodWriter.Store();
+ writer.Store();
+ }
+
+ ///
+ /// Returns if there is a chance the transform may change after the tick.
+ ///
+ ///
+ private bool TransformChanged()
+ {
+ if (TimeManager.PhysicsMode == PhysicsMode.Disabled)
+ return false;
+
+ /* 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 anyChanged = false;
+ anyChanged |= (transform.position - _lastTransformPosition).sqrMagnitude > changeDistance;
+ if (!anyChanged)
+ anyChanged |= (transform.rotation.eulerAngles - _lastTransformRotation.eulerAngles).sqrMagnitude > changeDistance;
+ if (!anyChanged)
+ anyChanged |= (transform.localScale - _lastTransformScale).sqrMagnitude > changeDistance;
+
+ //If transform changed update last values.
+ if (anyChanged)
+ {
+ _lastTransformPosition = transform.position;
+ _lastTransformRotation = transform.rotation;
+ _lastTransformScale = transform.localScale;
+ }
+
+ return anyChanged;
+ }
+
+ ///
+ /// Returns if the tick provided is the last tick to provide created data.
+ ///
+ /// Tick to check if is last created for this object.
+ ///
+ //private bool IsLastCreated(uint tick) => (tick == _lastCreatedTick);
+
+ ///
+ /// Called internally when an input from localTick should be replayed.
+ ///
+ internal virtual void Replicate_Replay_Start(uint replayTick) { }
+
+ ///
+ /// Replays inputs from replicates.
+ ///
+ protected internal void Replicate_Replay(uint replayTick, ReplicateUserLogicDelegate del, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData
+ {
+ //Reconcile data was not received so cannot replay.
+ if (!IsBehaviourReconciling)
+ return;
+
+ if (_networkObjectCache.IsOwner)
+ Replicate_Replay_Authoritative(replayTick, del, replicatesHistory, channel);
+ else
+ Replicate_Replay_NonAuthoritative(replayTick, del, replicatesHistory, channel);
+ }
+
+ ///
+ /// Replays an input for authoritative entity.
+ ///
+ protected internal void Replicate_Replay_Authoritative(uint replayTick, ReplicateUserLogicDelegate del, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData
+ {
+ ReplicateTickFinder.DataPlacementResult findResult;
+ int replicateIndex = ReplicateTickFinder.GetReplicateHistoryIndex(replayTick, replicatesHistory, out findResult);
+
+ T data;
+ ReplicateState state;
+ //If found then the replicate has been received by the server.
+ if (findResult == ReplicateTickFinder.DataPlacementResult.Exact)
+ {
+ data = replicatesHistory[replicateIndex];
+ state = ReplicateState.ReplayedCreated;
+
+ //SetReplicateTick(data.GetTick(), true);
+ del.Invoke(data, state, channel);
+ }
+ }
+
+ ///
+ /// Replays an input for non authoritative entity.
+ ///
+ protected internal void Replicate_Replay_NonAuthoritative(uint replayTick, ReplicateUserLogicDelegate del, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData
+ {
+ //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
+ T data;
+ ReplicateState state;
+ bool isAppendedOrder = _networkObjectCache.PredictionManager.IsAppendedStateOrder;
+ //If the first replay.
+ if (isAppendedOrder || replayTick == (_networkObjectCache.PredictionManager.ServerStateTick + 1))
+ {
+ ReplicateTickFinder.DataPlacementResult findResult;
+ int replicateIndex = ReplicateTickFinder.GetReplicateHistoryIndex(replayTick, replicatesHistory, out findResult);
+ //If not found then something went wrong.
+ if (findResult == ReplicateTickFinder.DataPlacementResult.Exact)
+ {
+ data = replicatesHistory[replicateIndex];
+ //state = ReplicateState.ReplayedCreated;
+ state = (_readReplicateTicks.Contains(replayTick)) ? ReplicateState.ReplayedCreated : ReplicateState.ReplayedFuture;
+ }
+ else
+ {
+ SetDataToDefault();
+ }
+ }
+ //Not the first replay tick.
+ else
+ {
+ SetDataToDefault();
+ }
+
+ //Debug.LogError($"Update lastCreatedTick as needed here.");
+
+ void SetDataToDefault()
+ {
+ data = default;
+ data.SetTick(replayTick);
+ state = ReplicateState.ReplayedFuture;
+ }
+
+ //uint dataTick = data.GetTick();
+ //SetReplicateTick(dataTick, true);
+ del.Invoke(data, state, channel);
+ }
+
+ ///
+ /// This is overriden by codegen to call EmptyReplicatesQueueIntoHistory().
+ /// This should only be called when client only.
+ ///
+ protected internal virtual void EmptyReplicatesQueueIntoHistory_Start() { }
+
+ ///
+ /// Replicates which are enqueued will be removed from the queue and put into replicatesHistory.
+ /// This should only be called when client only.
+ ///
+ [MakePublic]
+ protected internal void EmptyReplicatesQueueIntoHistory(BasicQueue replicatesQueue, RingBuffer replicatesHistory) where T : IReplicateData
+ {
+ while (replicatesQueue.TryDequeue(out T data))
+ InsertIntoReplicateHistory(data.GetTick(), data, replicatesHistory);
+ }
+
+ ///
+ /// Gets the next replicate in perform when server or non-owning client.
+ ///
+ ///
+ [MakePublic]
+ [APIExclude]
+ protected internal void Replicate_NonAuthoritative(ReplicateUserLogicDelegate del, BasicQueue replicatesQueue, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData
+ {
+ bool serverStarted = _networkObjectCache.IsServerStarted;
+ bool ownerlessAndServer = (!Owner.IsValid && serverStarted);
+ if (IsOwner || ownerlessAndServer)
+ return;
+ /* Still need to run inputs if server, even if forwarding
+ * is not enabled.*/
+ if (!_networkObjectCache.EnableStateForwarding && !serverStarted)
+ return;
+
+ TimeManager tm = _networkObjectCache.TimeManager;
+ PredictionManager pm = _networkObjectCache.PredictionManager;
+ uint localTick = tm.LocalTick;
+ bool isServer = _networkObjectCache.IsServerStarted;
+ bool isAppendedOrder = pm.IsAppendedStateOrder;
+
+ //Server is initialized or appended state order.
+ if (isServer || isAppendedOrder)
+ {
+ int count = replicatesQueue.Count;
+ /* If count is 0 then data must be set default
+ * and as predicted. */
+ if (count == 0)
+ {
+ uint tick = (GetDefaultedLastReplicateTick() + 1);
+ T data = default(T);
+ data.SetTick(tick);
+ ReplicateData(data, ReplicateState.CurrentFuture);
+ }
+ //Not predicted, is user created.
+ else
+ {
+ //Check to unset start tick, which essentially voids it resulting in inputs being run immediately.
+ if (localTick >= _replicateStartTick)
+ _replicateStartTick = TimeManager.UNSET_TICK;
+ /* As said above, if start tick is unset then replicates
+ * can run. When still set that means the start condition has
+ * not been met yet. */
+ if (_replicateStartTick == TimeManager.UNSET_TICK)
+ {
+ T queueEntry;
+ bool queueEntryValid = false;
+ while (replicatesQueue.TryDequeue(out queueEntry))
+ {
+ if (queueEntry.GetTick() > _lastReconcileTick)
+ {
+ queueEntryValid = true;
+ break;
+ }
+ }
+
+ if (queueEntryValid)
+ {
+ _remainingReconcileResends = pm.RedundancyCount;
+
+ ReplicateData(queueEntry, ReplicateState.CurrentCreated);
+
+ //Update count since old entries were dropped and one replicate run.
+ count = replicatesQueue.Count;
+
+ bool consumeExcess = (!pm.DropExcessiveReplicates || IsClientOnlyStarted);
+ int leaveInBuffer = _networkObjectCache.PredictionManager.StateInterpolation;
+
+ //Only consume if the queue count is over leaveInBuffer.
+ if (consumeExcess && count > leaveInBuffer)
+ {
+ const byte maximumAllowedConsumes = 1;
+ int maximumPossibleConsumes = (count - leaveInBuffer);
+ int consumeAmount = Mathf.Min(maximumAllowedConsumes, maximumPossibleConsumes);
+
+ for (int i = 0; i < consumeAmount; i++)
+ ReplicateData(replicatesQueue.Dequeue(), ReplicateState.CurrentCreated);
+ }
+ }
+ }
+ }
+ }
+ //Is client only and not using future state order.
+ else
+ {
+ uint tick = (GetDefaultedLastReplicateTick() + 1);
+ T data = default(T);
+ data.SetTick(tick);
+ ReplicateData(data, ReplicateState.CurrentFuture);
+ }
+
+ void ReplicateData(T data, ReplicateState state)
+ {
+ uint tick = data.GetTick();
+ SetReplicateTick(tick, (state == ReplicateState.CurrentCreated));
+ /* If server or appended state order then insert/add to history when run
+ * within this method.
+ * Whether data is inserted/added into the past (replicatesHistory) depends on
+ * if client only && and state order.
+ *
+ * Server only adds onto the history after running the inputs. This is so
+ * the server can send past inputs with redundancy.
+ *
+ * Client inserts into the history under two scenarios:
+ * - If state order is using inserted. This is done when the data is read so it
+ * can be iterated during the next reconcile, since the data is not added to
+ * a queue otherwise. This is what causes the requirement to reconcile to run
+ * datas.
+ * - If the state order if using append, and the state just ran. This is so that
+ * the reconcile does not replay data which hasn't yet run. But, the data should still
+ * be inserted at point of run so reconciles can correct to the state at the right
+ * point in history.*/
+
+ //Server always adds.
+ if (isServer)
+ {
+ AddReplicatesHistory(replicatesHistory, data);
+ }
+ //If client insert value into history.
+ else
+ {
+ InsertIntoReplicateHistory(tick, data, replicatesHistory);
+ if (state == ReplicateState.CurrentCreated)
+ _readReplicateTicks.Add(tick);
+ }
+
+ del.Invoke(data, state, channel);
+ }
+
+ //Debug.LogError($"Update lastCreatedTick as needed here.");
+ //Returns a replicate tick for when data is not created.
+ uint GetDefaultedLastReplicateTick()
+ {
+ if (_lastOrderedReplicatedTick == TimeManager.UNSET_TICK)
+ _lastOrderedReplicatedTick = (tm.LastPacketTick.Value() + pm.StateInterpolation);
+
+ return _lastOrderedReplicatedTick;
+ }
+ }
+
+ ///
+ /// Returns if a replicates data changed and updates resends as well data tick.
+ ///
+ /// True to enqueue data for replaying.
+ /// True if data has changed..
+ [MakePublic] //internal
+ [APIExclude]
+ protected internal void Replicate_Authoritative(ReplicateUserLogicDelegate del, uint methodHash, BasicQueue replicatesQueue, RingBuffer replicatesHistory, T data, Channel channel) where T : IReplicateData
+ {
+ bool ownerlessAndServer = (!Owner.IsValid && IsServerStarted);
+ if (!IsOwner && !ownerlessAndServer)
+ return;
+
+ Func isDefaultDel = PublicPropertyComparer.IsDefault;
+ if (isDefaultDel == null)
+ {
+ NetworkManager.LogError($"{nameof(PublicPropertyComparer)} not found for type {typeof(T).FullName}");
+ return;
+ }
+
+ PredictionManager pm = NetworkManager.PredictionManager;
+ uint dataTick = TimeManager.LocalTick;
+
+ /* The following code is to remove replicates from replicatesHistory
+ * which exceed the buffer allowance. Replicates are kept for up to
+ * x seconds to clients can re-run them during a reconcile. The reconcile
+ * method removes old histories but given the server does not reconcile,
+ * it will never perform that operation.
+ * The server would not actually need to keep replicates history except
+ * when it is also client(clientHost). This is because the clientHost must
+ * send redundancies to other clients still, therefor that redundancyCount
+ * must be the allowance when clientHost. */
+ if (IsHostStarted)
+ {
+ int replicatesHistoryCount = replicatesHistory.Count;
+ int maxCount = pm.RedundancyCount;
+ //Number to remove which is over max count.
+ int removeCount = (replicatesHistoryCount - maxCount);
+ //If there are any to remove.
+ if (removeCount > 0)
+ {
+ //Dispose first.
+ for (int i = 0; i < removeCount; i++)
+ replicatesHistory[i].Dispose();
+
+ //Then remove range.
+ replicatesHistory.RemoveRange(true, removeCount);
+ }
+ }
+
+ data.SetTick(dataTick);
+ AddReplicatesHistory(replicatesHistory, data);
+
+ //Check to reset resends.
+ bool isDefault = isDefaultDel.Invoke(data);
+ bool resetResends = (!isDefault || TransformChanged());
+
+ byte redundancyCount = PredictionManager.RedundancyCount;
+
+ //Standard delta serialize option.
+ //+1 to redundancy so lastFirstRead is pushed out to the last actual input when server reads.
+ if (resetResends)
+ {
+ _remainingReplicateResends = redundancyCount;
+ _remainingReconcileResends = redundancyCount;
+ }
+
+ bool sendData = (_remainingReplicateResends > 0);
+ if (sendData)
+ {
+ /* If not server then send to server.
+ * If server then send to clients. */
+ bool toServer = !IsServerStarted;
+ Replicate_SendAuthoritative(toServer, methodHash, redundancyCount, replicatesHistory, dataTick, channel, GetDeltaSerializeOption());
+ _remainingReplicateResends--;
+ }
+
+ _lastCreatedTick = dataTick;
+ SetReplicateTick(dataTick, createdReplicate: true);
+
+ //Owner always replicates with new data.
+ del.Invoke(data, ReplicateState.CurrentCreated, channel);
+ }
+
+ ///
+ /// Returns the DeltaSerializeOption to use for the tick.
+ ///
+ ///
+ ///
+ internal DeltaSerializerOption GetDeltaSerializeOption()
+ {
+ uint localTick = _networkObjectCache.TimeManager.LocalTick;
+ ushort tickRate = _networkObjectCache.TimeManager.TickRate;
+ /* New observers so send a full serialize next replicate.
+ * This could go out to only the newly added observers, but it
+ * would generate a lot more complexity to save presumably
+ * a small amount of occasional bandwidth. */
+ if (_networkObjectCache.ObserverAddedTick == localTick)
+ return DeltaSerializerOption.FullSerialize;
+ //Send full every half a second.
+ //else if (localTick % tickRate == 0 || localTick % (tickRate / 2) == 0)
+ // return DeltaSerializerOption.FullSerialize;
+ //Send full every second.
+ else if (localTick % tickRate == 0)
+ return DeltaSerializerOption.FullSerialize;
+ //Otherwise return rootSerialize, the default for sending the child most data.
+ else
+ return DeltaSerializerOption.RootSerialize;
+ }
+
+ ///
+ /// Sends a Replicate to server or clients.
+ ///
+ private void Replicate_SendAuthoritative(bool toServer, uint hash, int pastInputs, RingBuffer replicatesHistory, uint queuedTick, Channel channel, DeltaSerializerOption deltaOption) where T : IReplicateData
+ {
+ /* Do not use IsSpawnedWithWarning because the server
+ * will still call this a tick or two as clientHost when
+ * an owner disconnects. This comes from calling Replicate(default)
+ * for the server-side processing in NetworkBehaviours. */
+ if (!IsSpawned)
+ return;
+
+ int historyCount = replicatesHistory.Count;
+ //Nothing to send; should never be possible.
+ if (historyCount <= 0)
+ return;
+
+ //Number of past inputs to send.
+ if (historyCount < pastInputs)
+ pastInputs = historyCount;
+ /* Where to start writing from. When passed
+ * into the writer values from this offset
+ * and forward will be written.
+ * Always write up to past inputs. */
+ int offset = (historyCount - pastInputs);
+
+ //Write history to methodWriter.
+ PooledWriter methodWriter = WriterPool.Retrieve(WriterPool.LENGTH_BRACKET);
+ /* If going to clients from the server then
+ * write the queueTick. */
+ if (!toServer)
+ methodWriter.WriteTickUnpacked(queuedTick);
+//#if !FISHNET_STABLE_MODE
+#if DO_NOT_USE
+ methodWriter.WriteDeltaReplicate(replicatesHistory, offset, deltaOption);
+#else
+ methodWriter.WriteReplicate(replicatesHistory, offset);
+#endif
+ _transportManagerCache.CheckSetReliableChannel(methodWriter.Length + MAXIMUM_RPC_HEADER_SIZE, ref channel);
+ PooledWriter writer = CreateRpc(hash, methodWriter, PacketId.Replicate, channel);
+
+ /* toServer will never be true if clientHost.
+ * When clientHost and here replicates will
+ * always just send to clients, while
+ * excluding clientHost. */
+ if (toServer)
+ {
+ NetworkManager.TransportManager.SendToServer((byte)channel, writer.GetArraySegment(), false);
+ }
+ else
+ {
+ /* If going to clients from server, then only send
+ * if state forwarding is enabled. */
+ if (_networkObjectCache.EnableStateForwarding)
+ {
+ //Exclude owner and if clientHost, also localClient.
+ _networkConnectionCache.Clear();
+ _networkConnectionCache.Add(Owner);
+ if (IsClientStarted)
+ _networkConnectionCache.Add(ClientManager.Connection);
+
+ NetworkManager.TransportManager.SendToClients((byte)channel, writer.GetArraySegment(), Observers, _networkConnectionCache, false);
+ }
+ }
+
+ /* If sending as reliable there is no reason
+ * to perform resends, so clear remaining resends. */
+ if (channel == Channel.Reliable)
+ _remainingReplicateResends = 0;
+
+ methodWriter.StoreLength();
+ writer.StoreLength();
+ }
+
+ ///
+ /// Reads a replicate the client.
+ ///
+ [MakePublic]
+ internal void Replicate_Reader(uint hash, PooledReader reader, NetworkConnection sender, ref T lastReadReplicate, ref T[] arrBuffer, BasicQueue replicatesQueue, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData
+ {
+ /* This will never be received on owner, except in the condition
+ * the server is the owner and also a client. In such condition
+ * the method is exited after data is parsed. */
+ PredictionManager pm = _networkObjectCache.PredictionManager;
+ TimeManager tm = _networkObjectCache.TimeManager;
+ bool fromServer = (reader.Source == Reader.DataSource.Server);
+
+ uint tick;
+ /* If coming from the server then read the tick. Server sends tick
+ * if authority or if relaying from another client. The tick which
+ * arrives will be the tick the replicate will run on the server. */
+ if (fromServer)
+ tick = reader.ReadTickUnpacked();
+ /* When coming from a client it will always be owner.
+ * Client sends out replicates soon as they are run.
+ * It's safe to use the LastRemoteTick from the client
+ * in addition to QueuedInputs. */
+ else
+ tick = (tm.LastPacketTick.LastRemoteTick);
+
+ int receivedReplicatesCount;
+//#if !FISHNET_STABLE_MODE
+#if DO_NOT_USE
+ receivedReplicatesCount = reader.ReadDeltaReplicate(lastReadReplicate, ref arrBuffer, tick);
+#else
+ receivedReplicatesCount = reader.ReadReplicate(ref arrBuffer, tick);
+#endif
+ //Update first read if able.
+ if (receivedReplicatesCount > 0)
+ {
+ lastReadReplicate.Dispose();
+ lastReadReplicate = arrBuffer[receivedReplicatesCount - 1];
+ }
+
+ //If received on clientHost simply ignore after parsing data.
+ if (fromServer && IsHostStarted)
+ return;
+
+ /* Replicate rpc readers relay to this method and
+ * do not have an owner check in the generated code.
+ * Only server needs to check for owners. Clients
+ * should accept the servers data regardless.
+ *
+ * If coming from a client and that client is not owner then exit. */
+ if (!fromServer && !OwnerMatches(sender))
+ return;
+ //Early exit if old data.
+ if (TimeManager.LastPacketTick.LastRemoteTick < _lastReplicateReadRemoteTick)
+ return;
+ _lastReplicateReadRemoteTick = TimeManager.LastPacketTick.LastRemoteTick;
+
+ //If from a client that is not clientHost do some safety checks.
+ if (!fromServer && !Owner.IsLocalClient)
+ {
+ if (receivedReplicatesCount > pm.RedundancyCount)
+ {
+ sender.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection {sender.ToString()} sent too many past replicates. Connection will be kicked immediately.");
+ return;
+ }
+ }
+
+ Replicate_EnqueueReceivedReplicate(receivedReplicatesCount, arrBuffer, replicatesQueue, replicatesHistory, channel);
+ Replicate_SendNonAuthoritative(hash, replicatesQueue, channel);
+ }
+
+ ///
+ /// Sends data from a reader which only contains the replicate packet.
+ ///
+ [MakePublic]
+ internal void Replicate_SendNonAuthoritative(uint hash, BasicQueue replicatesQueue, Channel channel) where T : IReplicateData
+ {
+ if (!IsServerStarted)
+ return;
+ if (!_networkObjectCache.EnableStateForwarding)
+ return;
+
+ int queueCount = replicatesQueue.Count;
+ //None to send.
+ if (queueCount == 0)
+ return;
+
+ //If the only observer is the owner then there is no need to write.
+ int observersCount = Observers.Count;
+ //Quick exit for no observers other than owner.
+ if (observersCount == 0 || (Owner.IsValid && observersCount == 1))
+ return;
+
+ PooledWriter methodWriter = WriterPool.Retrieve(WriterPool.LENGTH_BRACKET);
+
+ uint localTick = _networkObjectCache.TimeManager.LocalTick;
+ /* Write when the last entry will run.
+ *
+ * Typically, the last entry will run on localTick + (queueCount - 1).
+ * 1 is subtracted from queueCount because in most cases the first entry
+ * is going to run same tick.
+ * An exception is when the replicateStartTick is set, then there is going
+ * to be a delayed based on start tick difference. */
+ uint runTickOflastEntry = localTick + ((uint)queueCount - 1);
+ //If start tick is set then add on the delay.
+ if (_replicateStartTick != TimeManager.UNSET_TICK)
+ runTickOflastEntry += (_replicateStartTick - TimeManager.LocalTick);
+ //Write the run tick now.
+ methodWriter.WriteTickUnpacked(runTickOflastEntry);
+ //Write the replicates.
+ int redundancyCount = (int)Mathf.Min(_networkObjectCache.PredictionManager.RedundancyCount, queueCount);
+//#if !FISHNET_STABLE_MODE
+#if DO_NOT_USE
+ methodWriter.WriteDeltaReplicate(replicatesQueue, redundancyCount, GetDeltaSerializeOption());
+#else
+ methodWriter.WriteReplicate(replicatesQueue, redundancyCount);
+#endif
+ PooledWriter writer = CreateRpc(hash, methodWriter, PacketId.Replicate, channel);
+
+ //Exclude owner and if clientHost, also localClient.
+ _networkConnectionCache.Clear();
+ if (Owner.IsValid)
+ _networkConnectionCache.Add(Owner);
+ if (IsClientStarted && !Owner.IsLocalClient)
+ _networkConnectionCache.Add(ClientManager.Connection);
+ NetworkManager.TransportManager.SendToClients((byte)channel, writer.GetArraySegment(), Observers, _networkConnectionCache, false);
+
+ methodWriter.StoreLength();
+ writer.StoreLength();
+ }
+
+ ///
+ /// Handles a received replicate packet.
+ ///
+ private void Replicate_EnqueueReceivedReplicate(int receivedReplicatesCount, T[] arrBuffer, BasicQueue replicatesQueue, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData
+ {
+ int startQueueCount = replicatesQueue.Count;
+ /* Owner never gets this for their own object so
+ * this can be processed under the assumption data is only
+ * handled on unowned objects. */
+ PredictionManager pm = PredictionManager;
+
+ bool isServer = _networkObjectCache.IsServerStarted;
+ bool isAppendedOrder = pm.IsAppendedStateOrder;
+
+ //Maximum number of replicates allowed to be queued at once.
+ int maximmumReplicates = (IsServerStarted) ? pm.GetMaximumServerReplicates() : pm.MaximumPastReplicates;
+ for (int i = 0; i < receivedReplicatesCount; i++)
+ {
+ T entry = arrBuffer[i];
+ uint tick = entry.GetTick();
+
+ //Skip if old data.
+ if (tick <= _lastReadReplicateTick)
+ {
+ entry.Dispose();
+ continue;
+ }
+
+ _lastReadReplicateTick = tick;
+
+ if (!IsServerStarted && !isAppendedOrder)
+ _readReplicateTicks.Add(tick);
+ //Cannot queue anymore, discard oldest.
+ if (replicatesQueue.Count >= maximmumReplicates)
+ {
+ T data = replicatesQueue.Dequeue();
+ data.Dispose();
+ }
+
+ /* Check if replicate is already in history.
+ * This can occur when the replicate method has a predicted
+ * state for the tick, but a user created replicate comes
+ * through afterwards.
+ *
+ * Only perform this check if not the server, since server
+ * does not reconcile it will never use replicatesHistory.
+ *
+ * When clients are also using ReplicateStateOrder.Future the replicates
+ * do not need to be put into the past, as they're always added onto
+ * the end of the queue.
+ *
+ * The server also does not predict replicates in the same way
+ * a client does. When an owner sends a replicate to the server
+ * the server only uses the owner tick to check if it's an old replicate.
+ * But when running the replicate, the server applies it's local tick and
+ * sends that to spectators. */
+ //Add automatically if server or future order.
+ if (isServer || isAppendedOrder)
+ replicatesQueue.Enqueue(entry);
+ //Run checks to replace data if not server.
+ else
+ InsertIntoReplicateHistory(tick, entry, replicatesHistory);
+ }
+
+ /* If entries are being added after nothing then
+ * start the queued inputs delay. Only the server needs
+ * to do this since clients implement the queue delay
+ * by holding reconcile x ticks rather than not running received
+ * x ticks. */
+ if ((isServer || isAppendedOrder) && startQueueCount == 0 && replicatesQueue.Count > 0)
+ _replicateStartTick = (_networkObjectCache.TimeManager.LocalTick + pm.StateInterpolation);
+ }
+
+ ///
+ /// Inserts data into the replicatesHistory collection.
+ /// This should only be called when client only.
+ ///
+ private void InsertIntoReplicateHistory(uint tick, T data, RingBuffer replicatesHistory) where T : IReplicateData
+ {
+ /* See if replicate tick is in history. Keep in mind
+ * this is the localTick from the server, not the localTick of
+ * the client which is having their replicate relayed. */
+ ReplicateTickFinder.DataPlacementResult findResult;
+ int index = ReplicateTickFinder.GetReplicateHistoryIndex(tick, replicatesHistory, out findResult);
+
+ /* Exact entry found. This is the most likely
+ * scenario. Client would have already run the tick
+ * in the future, and it's now being replaced with
+ * the proper data. */
+ if (findResult == ReplicateTickFinder.DataPlacementResult.Exact)
+ {
+ T prevEntry = replicatesHistory[index];
+ prevEntry.Dispose();
+ replicatesHistory[index] = data;
+ }
+ else if (findResult == ReplicateTickFinder.DataPlacementResult.InsertMiddle)
+ {
+ InsertReplicatesHistory(replicatesHistory, data, index);
+ }
+ else if (findResult == ReplicateTickFinder.DataPlacementResult.InsertEnd)
+ {
+ AddReplicatesHistory(replicatesHistory, data);
+ }
+
+ /* Insert beginning should not happen unless the data is REALLY old.
+ * This would mean the network was in an unplayable state. Discard the
+ * data. */
+ if (findResult == ReplicateTickFinder.DataPlacementResult.InsertBeginning)
+ InsertReplicatesHistory(replicatesHistory, data, 0);
+ }
+
+ ///
+ /// Adds to replicate history disposing of old entries if needed.
+ ///
+ private void AddReplicatesHistory(RingBuffer replicatesHistory, T value) where T : IReplicateData
+ {
+ T prev = replicatesHistory.Add(value);
+ if (prev != null)
+ prev.Dispose();
+ }
+
+ ///
+ /// Inserts to replicate history disposing of old entries if needed.
+ ///
+ private void InsertReplicatesHistory(RingBuffer replicatesHistory, T value, int index) where T : IReplicateData
+ {
+ T prev = replicatesHistory.Insert(index, value);
+ if (prev != null)
+ prev.Dispose();
+ }
+
+ ///
+ /// Override this method to create your reconcile data, and call your reconcile method.
+ ///
+ public virtual void CreateReconcile() { }
+
+ ///
+ /// Sends a reconcile to clients.
+ ///
+ public void Reconcile_Server(uint methodHash, ref T lastReconcileData, T data, Channel channel) where T : IReconcileData
+ {
+ //Tick does not need to be set for reconciles since they come in as state updates, which have the tick included globally.
+ if (IsServerInitialized)
+ Server_SendReconcileRpc(methodHash, ref lastReconcileData, data, channel);
+ }
+
+ ///
+ /// This is called when the networkbehaviour should perform a reconcile.
+ /// Codegen overrides this calling Reconcile_Client with the needed data.
+ ///
+ [MakePublic]
+ protected internal virtual void Reconcile_Client_Start() { }
+
+ ///
+ /// Processes a reconcile for client.
+ ///
+ [APIExclude]
+ [MakePublic]
+ protected internal void Reconcile_Client_Local(RingBuffer> reconcilesHistory, T data) where T : IReconcileData
+ {
+ //Not used until feature is stable.
+ }
+
+ ///
+ /// Processes a reconcile for client.
+ ///
+ [APIExclude]
+ [MakePublic]
+ protected internal void Reconcile_Client(ReconcileUserLogicDelegate reconcileDel, RingBuffer replicatesHistory, RingBuffer> reconcilesHistory, T data) where T : IReconcileData where T2 : IReplicateData
+ {
+ //Set on the networkObject that a reconcile can now occur.
+ _networkObjectCache.IsObjectReconciling = true;
+
+ uint dataTick = data.GetTick();
+ _lastReconcileTick = dataTick;
+
+ //Remove up reconcile tick from received ticks.
+ int readReplicatesRemovalCount = 0;
+ for (int i = 0; i < _readReplicateTicks.Count; i++)
+ {
+ if (_readReplicateTicks[i] > dataTick)
+ break;
+ else
+ readReplicatesRemovalCount++;
+ }
+
+ _readReplicateTicks.RemoveRange(0, readReplicatesRemovalCount);
+
+ if (replicatesHistory.Count > 0)
+ {
+ /* Remove replicates up to reconcile. Since the reconcile
+ * is the state after a replicate for it's tick we no longer
+ * need any replicates prior. */
+ //Find the closest entry which can be removed.
+ int removeCount = 0;
+ //A few quick tests.
+ if (replicatesHistory.Count > 0)
+ {
+ /* If the last entry in history is less or equal
+ * to datatick then all histories need to be removed
+ * as reconcile is beyond them. */
+ if (replicatesHistory[^1].GetTick() <= dataTick)
+ {
+ removeCount = replicatesHistory.Count;
+ }
+ //Somewhere in between. Find what to remove up to.
+ else
+ {
+ for (int i = 0; i < replicatesHistory.Count; i++)
+ {
+ uint entryTick = replicatesHistory[i].GetTick();
+ /* Soon as an entry beyond dataTick is
+ * found remove up to that entry. */
+ if (entryTick > dataTick)
+ {
+ removeCount = i;
+ break;
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < removeCount; i++)
+ replicatesHistory[i].Dispose();
+ replicatesHistory.RemoveRange(true, removeCount);
+ }
+
+ //Call reconcile user logic.
+ reconcileDel?.Invoke(data, Channel.Reliable);
+ }
+
+ internal void Reconcile_Client_End()
+ {
+ IsBehaviourReconciling = false;
+ }
+
+ ///
+ /// Reads a reconcile from the server.
+ ///
+ public void Reconcile_Reader(PooledReader reader, ref T lastReconciledata, Channel channel) where T : IReconcileData
+ {
+ uint tick = (IsOwner) ? PredictionManager.ClientStateTick : PredictionManager.ServerStateTick;
+//#if !FISHNET_STABLE_MODE
+#if DO_NOT_USE
+ T newData = reader.ReadDeltaReconcile(lastReconciledata);
+#else
+ T newData = reader.ReadReconcile();
+#endif
+ //Do not process if an old state.
+ if (tick < _lastReadReconcileRemoteTick)
+ return;
+
+ lastReconciledata = newData;
+ lastReconciledata.SetTick(tick);
+
+ IsBehaviourReconciling = true;
+ _networkObjectCache.IsObjectReconciling = true;
+ _lastReadReconcileRemoteTick = tick;
+ }
///
/// Sets the last tick this NetworkBehaviour replicated with.
@@ -1462,4 +2819,5 @@ private void SetReplicateTick(uint value, bool createdReplicate)
_networkObjectCache.SetReplicateTick(value, createdReplicate);
}
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour.QOL.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour.QOL.cs
index d753467c6..43960ab82 100644
--- a/Assets/FishNet/Runtime/Object/NetworkBehaviour.QOL.cs
+++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour.QOL.cs
@@ -128,7 +128,21 @@ public abstract partial class NetworkBehaviour : MonoBehaviour
/// True if the object will always initialize as a networked object. When false the object will not automatically initialize over the network. Using Spawn() on an object will always set that instance as networked.
/// To check if server or client has been initialized on this object use IsXYZInitialized.
///
- public bool IsNetworked => _networkObjectCache.IsNetworked;
+ [Obsolete("Use GetIsNetworked.")] //Remove on V5.
+ public bool IsNetworked => GetIsNetworked();
+
+ ///
+ /// True if the object will always initialize as a networked object. When false the object will not automatically initialize over the network. Using Spawn() on an object will always set that instance as networked.
+ /// To check if server or client has been initialized on this object use IsXYZInitialized.
+ ///
+ public bool GetIsNetworked() => _networkObjectCache.GetIsNetworked();
+
+ ///
+ /// Sets IsNetworked value. This method must be called before Start.
+ ///
+ /// New IsNetworked value.
+ public void SetIsNetworked(bool value) => _networkObjectCache.SetIsNetworked(value);
+
///
/// True if a reconcile is occuring on the PredictionManager. Note the difference between this and IsBehaviourReconciling.
///
diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs
index 56770899f..32a7a79fc 100644
--- a/Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs
+++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs
@@ -339,7 +339,7 @@ internal bool WriteDirtySyncTypes(SyncTypeWriteFlag flags)
//None written for this channel.
if (writer.Length == 0)
continue;
- ;
+
CompleteSyncTypePacket(fullWriter, writer);
writer.Reset();
diff --git a/Assets/FishNet/Runtime/Object/NetworkObject.Observers.cs b/Assets/FishNet/Runtime/Object/NetworkObject.Observers.cs
index 8758740ef..37dccf9e7 100644
--- a/Assets/FishNet/Runtime/Object/NetworkObject.Observers.cs
+++ b/Assets/FishNet/Runtime/Object/NetworkObject.Observers.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Object
@@ -13,7 +14,6 @@ namespace FishNet.Object
public partial class NetworkObject : MonoBehaviour
{
#region Public.
-
///
/// Called when the clientHost gains or loses visibility of this object.
/// Boolean value will be true if clientHost has visibility.
@@ -35,12 +35,13 @@ public partial class NetworkObject : MonoBehaviour
///
/// NetworkObserver on this object.
///
- [HideInInspector] public NetworkObserver NetworkObserver = null;
+ [HideInInspector]
+ public NetworkObserver NetworkObserver = null;
///
/// Clients which can see and get messages from this NetworkObject.
///
- [HideInInspector] public HashSet Observers = new();
-
+ [HideInInspector]
+ public HashSet Observers = new();
#endregion
#region Internal.
@@ -55,7 +56,6 @@ public partial class NetworkObject : MonoBehaviour
#endregion
#region Private.
-
///
/// True if NetworkObserver has been initialized.
///
@@ -63,7 +63,8 @@ public partial class NetworkObject : MonoBehaviour
///
/// Found renderers on the NetworkObject and it's children. This is only used as clientHost to hide non-observers objects.
///
- [System.NonSerialized] private Renderer[] _renderers;
+ [System.NonSerialized]
+ private List _renderers = new();
///
/// True if renderers have been looked up.
///
@@ -88,7 +89,6 @@ public partial class NetworkObject : MonoBehaviour
/// Current grid position.
///
private Vector2Int _hashGridPosition = HashGrid.UnsetGridPosition;
-
#endregion
///
@@ -120,7 +120,6 @@ internal void UpdateForNetworkObject(bool force)
/// Updates cached renderers used to managing clientHost visibility.
///
/// True to also update visibility if clientHost.
-
public void UpdateRenderers(bool updateVisibility = true)
{
UpdateRenderers_Internal(updateVisibility);
@@ -131,20 +130,13 @@ public void UpdateRenderers(bool updateVisibility = true)
///
/// True if renderers are to be visibile.
/// True to skip blocking checks.
-
public void SetRenderersVisible(bool visible, bool force = false)
{
- if (!force)
- {
- if (!NetworkObserver.UpdateHostVisibility)
- return;
- }
+ if (!force && !NetworkObserver.UpdateHostVisibility)
+ return;
if (!_renderersPopulated)
- {
- UpdateRenderers_Internal(false);
- _renderersPopulated = true;
- }
+ UpdateRenderers_Internal(updateVisibility: false);
UpdateRenderVisibility(visible);
}
@@ -152,20 +144,22 @@ public void SetRenderersVisible(bool visible, bool force = false)
///
/// Clears and updates renderers.
///
-
private void UpdateRenderers_Internal(bool updateVisibility)
{
- _renderers = GetComponentsInChildren(true);
- List enabledRenderers = new();
- foreach (Renderer r in _renderers)
+ _renderersPopulated = true;
+
+ List cache = CollectionCaches.RetrieveList();
+ GetComponentsInChildren(includeInactive: true, cache);
+
+ _renderers.Clear();
+
+ foreach (Renderer r in cache)
{
if (r.enabled)
- enabledRenderers.Add(r);
+ _renderers.Add(r);
}
- //If there are any disabled renderers then change _renderers to cached values.
- if (enabledRenderers.Count != _renderers.Length)
- _renderers = enabledRenderers.ToArray();
+ CollectionCaches.Store(cache);
if (updateVisibility)
UpdateRenderVisibility(_lastClientHostVisibility);
@@ -177,28 +171,23 @@ private void UpdateRenderers_Internal(bool updateVisibility)
///
private void UpdateRenderVisibility(bool visible)
{
- bool rebuildRenderers = false;
-
- Renderer[] rs = _renderers;
- int count = rs.Length;
- for (int i = 0; i < count; i++)
+ List rs = _renderers;
+ for (int i = 0; i < rs.Count; i++)
{
Renderer r = rs[i];
if (r == null)
{
- rebuildRenderers = true;
- break;
+ _renderers.RemoveAt(i);
+ i--;
+ }
+ else
+ {
+ r.enabled = visible;
}
-
- r.enabled = visible;
}
OnHostVisibilityUpdated?.Invoke(_lastClientHostVisibility, visible);
_lastClientHostVisibility = visible;
-
- //If to rebuild then do so, while updating visibility.
- if (rebuildRenderers)
- UpdateRenderers(true);
}
///
@@ -212,7 +201,6 @@ private void AddDefaultNetworkObserverConditions()
NetworkObserver = NetworkManager.ObserverManager.AddDefaultConditions(this);
}
-
///
/// Removes a connection from observers for this object returning if the connection was removed.
///
@@ -280,10 +268,14 @@ internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool
///
private void TryInvokeOnObserversActive(int startCount)
{
- ObserverAddedTick = TimeManager.LocalTick;
+ if (TimeManager != null)
+ ObserverAddedTick = TimeManager.LocalTick;
- if ((Observers.Count > 0 && startCount == 0) || Observers.Count == 0 && startCount > 0)
- OnObserversActive?.Invoke(this);
+ if (OnObserversActive != null)
+ {
+ if ((Observers.Count > 0 && startCount == 0) || Observers.Count == 0 && startCount > 0)
+ OnObserversActive.Invoke(this);
+ }
}
///
diff --git a/Assets/FishNet/Runtime/Object/NetworkObject.Prediction.cs b/Assets/FishNet/Runtime/Object/NetworkObject.Prediction.cs
index ec01529ea..c27bc980d 100644
--- a/Assets/FishNet/Runtime/Object/NetworkObject.Prediction.cs
+++ b/Assets/FishNet/Runtime/Object/NetworkObject.Prediction.cs
@@ -1,4 +1,5 @@
-using System;
+#define NEW_RECONCILE_TEST
+using System;
using FishNet.Component.Prediction;
using FishNet.Component.Transforming;
using FishNet.Managing;
@@ -339,6 +340,26 @@ private void PredictionManager_OnPreReconcile(uint clientTick, uint serverTick)
PredictionSmoother.OnPreReconcile();
}
+#if !FISHNET_STABLE_MODE
+ private void PredictionManager_OnReconcile(uint clientReconcileTick, uint serverReconcileTick)
+ {
+ /* Tell all prediction behaviours to set/validate their
+ * reconcile data now. This will use reconciles from the server
+ * whenever possible, and local reconciles if a server reconcile
+ * is not available. */
+ for (int i = 0; i < _predictionBehaviours.Count; i++)
+ _predictionBehaviours[i].Reconcile_Client_Start();
+
+ /* If still not reconciling then pause rigidbody.
+ * This shouldn't happen unless the user is not calling
+ * reconcile at all. */
+ if (!IsObjectReconciling)
+ {
+ if (_rigidbodyPauser != null)
+ _rigidbodyPauser.Pause();
+ }
+ }
+#else
private void PredictionManager_OnReconcile(uint clientReconcileTick, uint serverReconcileTick)
{
/* If still not reconciling then pause rigidbody.
@@ -351,7 +372,7 @@ private void PredictionManager_OnReconcile(uint clientReconcileTick, uint server
return;
}
-
+
/* Tell all prediction behaviours to set/validate their
* reconcile data now. This will use reconciles from the server
* whenever possible, and local reconciles if a server reconcile
@@ -359,6 +380,7 @@ private void PredictionManager_OnReconcile(uint clientReconcileTick, uint server
for (int i = 0; i < _predictionBehaviours.Count; i++)
_predictionBehaviours[i].Reconcile_Client_Start();
}
+#endif
private void PredictionManager_OnPostReconcile(uint clientReconcileTick, uint serverReconcileTick)
{
@@ -434,7 +456,7 @@ protected virtual void Objects_OnPreDestroyClientObjects(NetworkConnection conn)
networkObject.RemoveOwnership();
}
}
-
+
///
/// Place this component on NetworkObjects you wish to remove ownership on for a disconnecting owner.
/// This prevents the object from being despawned when the owner disconnects.
diff --git a/Assets/FishNet/Runtime/Object/NetworkObject.RpcLinks.cs b/Assets/FishNet/Runtime/Object/NetworkObject.RpcLinks.cs
index cc592e599..acf383aa8 100644
--- a/Assets/FishNet/Runtime/Object/NetworkObject.RpcLinks.cs
+++ b/Assets/FishNet/Runtime/Object/NetworkObject.RpcLinks.cs
@@ -5,7 +5,6 @@ namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
-
#region Private.
///
/// RpcLinks being used within this NetworkObject.
@@ -26,9 +25,8 @@ internal void SetRpcLinkIndexes(List values)
///
internal void RemoveClientRpcLinkIndexes()
{
- NetworkManager.ClientManager.Objects.RemoveLinkIndexes(_rpcLinkIndexes);
+ //if (NetworkManager != null)
+ NetworkManager.ClientManager.Objects.RemoveLinkIndexes(_rpcLinkIndexes);
}
}
-
-}
-
+}
\ No newline at end of file
diff --git a/Assets/FishNet/Runtime/Object/NetworkObject.Serialized.cs b/Assets/FishNet/Runtime/Object/NetworkObject.Serialized.cs
index 85ffb0be7..8aa2cc22a 100644
--- a/Assets/FishNet/Runtime/Object/NetworkObject.Serialized.cs
+++ b/Assets/FishNet/Runtime/Object/NetworkObject.Serialized.cs
@@ -185,7 +185,7 @@ private void CreateSceneId()
//If not building check to rebuild sceneIds this for object and the scene its in.
else
{
- double realtime = Time.realtimeSinceStartupAsDouble;
+ double realtime = EditorApplication.timeSinceStartup;
//Only do this once every Xms to prevent excessive rebiulds.
if (realtime - _lastSceneIdAutomaticRebuildTime < 0.250d)
return;
diff --git a/Assets/FishNet/Runtime/Object/NetworkObject.cs b/Assets/FishNet/Runtime/Object/NetworkObject.cs
index 8214f5c88..87600e3ab 100644
--- a/Assets/FishNet/Runtime/Object/NetworkObject.cs
+++ b/Assets/FishNet/Runtime/Object/NetworkObject.cs
@@ -1,4 +1,5 @@
-using FishNet.Managing;
+using System;
+using FishNet.Managing;
using FishNet.Connection;
using UnityEngine;
using FishNet.Serializing;
@@ -8,6 +9,7 @@
using FishNet.Component.Ownership;
using FishNet.Utility.Extension;
using GameKit.Dependencies.Utilities;
+using GameKit.Dependencies.Utilities.Types;
#if UNITY_EDITOR
using UnityEditor;
#endif
@@ -37,8 +39,9 @@ public int GetHashCode(NetworkObject obj)
}
}
+ [DefaultExecutionOrder(short.MinValue + 1)]
[DisallowMultipleComponent]
- public partial class NetworkObject : MonoBehaviour
+ public partial class NetworkObject : MonoBehaviour, IOrderable
{
#region Public.
///
@@ -170,19 +173,25 @@ internal NetworkBehaviour CurrentParentNetworkBehaviour
/// True if the object will always initialize as a networked object. When false the object will not automatically initialize over the network. Using Spawn() on an object will always set that instance as networked.
/// To check if server or client has been initialized on this object use IsXYZInitialized.
///
+ [Obsolete("Use Get/SetIsNetworked.")]
public bool IsNetworked
{
- get => _isNetworked;
- private set => _isNetworked = value;
+ get => GetIsNetworked();
+ private set => SetIsNetworked(value);
}
+ ///
+ /// Returns IsNetworked value.
+ ///
+ ///
+ public bool GetIsNetworked() => _isNetworked;
///
/// Sets IsNetworked value. This method must be called before Start.
///
/// New IsNetworked value.
public void SetIsNetworked(bool value)
{
- IsNetworked = value;
+ _isNetworked = value;
}
[Tooltip("True if the object will always initialize as a networked object. When false the object will not automatically initialize over the network. Using Spawn() on an object will always set that instance as networked.")]
@@ -192,7 +201,18 @@ public void SetIsNetworked(bool value)
///
/// True if the object can be spawned at runtime; this is generally false for scene prefabs you do not spawn.
///
+ [Obsolete("Use GetIsSpawnable.")] //Remove on V5.
public bool IsSpawnable => _isSpawnable;
+ ///
+ /// Gets the current IsSpawnable value.
+ ///
+ ///
+ public bool GetIsSpawnable() => _isSpawnable;
+ ///
+ /// Sets IsSpawnable value.
+ ///
+ /// Next value.
+ public void SetIsSpawnable(bool value) => _isSpawnable = value;
[Tooltip("True if the object can be spawned at runtime; this is generally false for scene prefabs you do not spawn.")]
[SerializeField]
@@ -244,6 +264,11 @@ public void SetIsGlobal(bool value)
///
public sbyte GetInitializeOrder() => _initializeOrder;
+ ///
+ /// This is for internal use. Returns the order to initialize the object.
+ ///
+ public int Order => _initializeOrder;
+
[Tooltip("Order to initialize this object's callbacks when spawned with other NetworkObjects in the same tick. Default value is 0, negative values will execute callbacks first.")]
[SerializeField]
private sbyte _initializeOrder = 0;
@@ -310,6 +335,31 @@ protected virtual void Awake()
{
_isStatic = gameObject.isStatic;
RuntimeChildNetworkBehaviours = CollectionCaches.RetrieveList();
+
+ /* If networkBehaviours are not yet initialized then do so now.
+ * After initializing at least 1 networkBehaviour will always exist
+ * as emptyNetworkBehaviour is added automatically when none are present. */
+ if (NetworkBehaviours == null || NetworkBehaviours.Count == 0)
+ {
+ bool isNested = false;
+ //Make sure there are no networkObjects above this since initializing will trickle down.
+ Transform parent = transform.parent;
+ while (parent != null)
+ {
+ if (parent.TryGetComponent(out _))
+ {
+ isNested = true;
+ break;
+ }
+
+ parent = parent.parent;
+ }
+
+ //If not nested then init
+ if (!isNested)
+ SetInitializedValues(parentNob: null);
+ }
+
SetChildDespawnedState();
}
@@ -395,34 +445,33 @@ private void OnDestroy()
}
}
- Owner?.RemoveObject(this);
- NetworkObserver?.Deinitialize(true);
+ if (Owner.IsValid)
+ Owner.RemoveObject(this);
+ if (NetworkObserver != null)
+ NetworkObserver.Deinitialize(true);
if (NetworkManager != null)
{
+ bool isServerStarted = IsServerStarted;
+ bool isClientStarted = IsClientStarted;
//Was destroyed without going through the proper methods.
- if (NetworkManager.IsServerStarted)
+ if (isServerStarted)
{
Deinitialize_Prediction(true);
NetworkManager.ServerManager.Objects.NetworkObjectUnexpectedlyDestroyed(this, true);
+
+ InvokeStopCallbacks(asServer: true, invokeSyncTypeCallbacks: true);
}
- if (NetworkManager.IsClientStarted)
+ if (isClientStarted)
{
Deinitialize_Prediction(false);
NetworkManager.ClientManager.Objects.NetworkObjectUnexpectedlyDestroyed(this, false);
+
+ InvokeStopCallbacks(false, true);
}
}
-
- /* When destroyed unexpectedly it's
- * impossible to know if this occurred on
- * the server or client side, so send callbacks
- * for both. */
- if (IsServerStarted)
- InvokeStopCallbacks(true, true);
- if (IsClientStarted)
- InvokeStopCallbacks(false, true);
-
+
/* If owner exist then remove object from owner.
* This has to be called here as well OnDisable because
* the OnDisable will only remove the object if
@@ -496,7 +545,7 @@ private void SetChildDespawnedState()
internal void TryStartDeactivation()
{
- if (!IsNetworked)
+ if (!GetIsNetworked())
return;
//Global.
@@ -963,11 +1012,29 @@ internal void Initialize(bool asServer, bool invokeSyncTypeCallbacks)
InvokeStartCallbacks(asServer, invokeSyncTypeCallbacks);
}
+ ///
+ /// Returns if a deinitialize call can process.
+ ///
+ internal bool CanDeinitialize(bool asServer)
+ {
+ if (NetworkManager == null)
+ return false;
+ else if (asServer && !IsServerInitialized)
+ return false;
+ else if (!asServer && !IsClientInitialized)
+ return false;
+
+ return true;
+ }
+
///
/// Called to prepare this object to be destroyed or disabled.
///
internal void Deinitialize(bool asServer)
{
+ if (!CanDeinitialize(asServer))
+ return;
+
Deinitialize_Prediction(asServer);
InvokeStopCallbacks(asServer, true);
@@ -976,12 +1043,14 @@ internal void Deinitialize(bool asServer)
if (asServer)
{
- NetworkObserver?.Deinitialize(false);
+ if (NetworkObserver != null)
+ NetworkObserver.Deinitialize(false);
IsDeinitializing = true;
}
else
{
//Client only.
+ //if (NetworkManager != null && !NetworkManager.IsServerStarted)
if (!NetworkManager.IsServerStarted)
IsDeinitializing = true;
@@ -1011,6 +1080,7 @@ public void ResetState(bool asServer)
SetOwner(NetworkManager.EmptyConnection);
if (NetworkObserver != null)
NetworkObserver.Deinitialize(false);
+
//QOL references.
NetworkManager = null;
ServerManager = null;
diff --git a/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs b/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs
index f48369b2f..957f91c08 100644
--- a/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs
+++ b/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs
@@ -1,5 +1,6 @@
using FishNet.Documenting;
using FishNet.Serializing;
+using UnityEngine;
namespace FishNet.Object.Prediction
{
diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncDictionary.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncDictionary.cs
index 24cf84c16..fe2efbd5e 100644
--- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncDictionary.cs
+++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncDictionary.cs
@@ -275,7 +275,7 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT
_changed.Clear();
}
}
-
+
///
/// Writers all values if not initial values.
/// Internal use.
@@ -289,10 +289,10 @@ internal protected override void WriteFull(PooledWriter writer)
return;
base.WriteHeader(writer, false);
-
+
//True for full write.
writer.WriteBoolean(true);
-
+
writer.WriteInt32(Collection.Count);
foreach (KeyValuePair item in Collection)
{
@@ -309,7 +309,7 @@ internal protected override void WriteFull(PooledWriter writer)
internal protected override void Read(PooledReader reader, bool asServer)
{
base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues);
-
+
//True to warn if this object was deinitialized on the server.
bool deinitialized = (asClientHost && !base.OnStartServerCalled);
if (deinitialized)
@@ -338,7 +338,7 @@ internal protected override void Read(PooledReader reader, bool asServer)
{
key = reader.Read();
value = reader.Read();
-
+
if (canModifyValues)
collection[key] = value;
}
@@ -352,7 +352,7 @@ internal protected override void Read(PooledReader reader, bool asServer)
else if (operation == SyncDictionaryOperation.Remove)
{
key = reader.Read();
-
+
if (canModifyValues)
collection.Remove(key);
}
@@ -394,13 +394,19 @@ private void InvokeOnChange(SyncDictionaryOperation operation, TKey key, TValue
internal protected override void ResetState(bool asServer)
{
base.ResetState(asServer);
- _sendAll = false;
- _changed.Clear();
- Collection.Clear();
- _valuesChanged = false;
- foreach (KeyValuePair item in _initialValues)
- Collection[item.Key] = item.Value;
+ bool canReset = (asServer || !base.IsReadAsClientHost(asServer));
+
+ if (canReset)
+ {
+ _sendAll = false;
+ _changed.Clear();
+ Collection.Clear();
+ _valuesChanged = false;
+
+ foreach (KeyValuePair item in _initialValues)
+ Collection[item.Key] = item.Value;
+ }
}
///
diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncHashset.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncHashset.cs
index 4c55f710d..d457b20d2 100644
--- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncHashset.cs
+++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncHashset.cs
@@ -226,7 +226,7 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT
else
{
base.WriteDelta(writer, resetSyncTick);
-
+
//False for not full write.
writer.WriteBoolean(false);
@@ -277,7 +277,7 @@ internal protected override void WriteFull(PooledWriter writer)
internal protected override void Read(PooledReader reader, bool asServer)
{
base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues);
-
+
//True to warn if this object was deinitialized on the server.
bool deinitialized = (asClientHost && !base.OnStartServerCalled);
if (deinitialized)
@@ -301,7 +301,7 @@ internal protected override void Read(PooledReader reader, bool asServer)
if (operation == SyncHashSetOperation.Add)
{
next = reader.Read();
-
+
if (canModifyValues)
collection.Add(next);
}
@@ -315,7 +315,7 @@ internal protected override void Read(PooledReader reader, bool asServer)
else if (operation == SyncHashSetOperation.Remove)
{
next = reader.Read();
-
+
if (canModifyValues)
collection.Remove(next);
}
@@ -323,7 +323,7 @@ internal protected override void Read(PooledReader reader, bool asServer)
else if (operation == SyncHashSetOperation.Update)
{
next = reader.Read();
-
+
if (canModifyValues)
{
collection.Remove(next);
@@ -367,12 +367,18 @@ private void InvokeOnChange(SyncHashSetOperation operation, T item, bool asServe
internal protected override void ResetState(bool asServer)
{
base.ResetState(asServer);
- _sendAll = false;
- _changed.Clear();
- Collection.Clear();
- foreach (T item in _initialValues)
- Collection.Add(item);
+ bool canReset = (asServer || !base.IsReadAsClientHost(asServer));
+
+ if (canReset)
+ {
+ _sendAll = false;
+ _changed.Clear();
+ Collection.Clear();
+
+ foreach (T item in _initialValues)
+ Collection.Add(item);
+ }
}
///
@@ -545,7 +551,7 @@ private void IntersectWith(ISet other)
if (!other.Contains(entry))
Remove(entry);
}
-
+
_cache.Clear();
}
diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncList.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncList.cs
index 90634f2bc..53e2b9032 100644
--- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncList.cs
+++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncList.cs
@@ -14,7 +14,7 @@ namespace FishNet.Object.Synchronizing
{
[System.Serializable]
public class SyncList : SyncBase, IList, IReadOnlyList
- {
+ {
#region Types.
///
/// Information needed to invoke a callback.
@@ -59,6 +59,7 @@ public ChangeData(SyncListOperation operation, int index, T item)
///
[APIExclude]
public bool IsReadOnly => false;
+
///
/// Delegate signature for when SyncList changes.
///
@@ -68,6 +69,7 @@ public ChangeData(SyncListOperation operation, int index, T item)
///
[APIExclude]
public delegate void SyncListChanged(SyncListOperation op, int index, T oldItem, T newItem, bool asServer);
+
///
/// Called when the SyncList changes.
///
@@ -82,7 +84,7 @@ public ChangeData(SyncListOperation operation, int index, T item)
public int Count => Collection.Count;
#endregion
- #region Private.
+ #region Private.
///
/// Values upon initialization.
///
@@ -117,6 +119,7 @@ public ChangeData(SyncListOperation operation, int index, T item)
#region Constructors.
public SyncList(SyncTypeSettings settings = new()) : this(CollectionCaches.RetrieveList(), EqualityComparer.Default, settings) { }
public SyncList(IEqualityComparer comparer, SyncTypeSettings settings = new()) : this(new(), (comparer == null) ? EqualityComparer.Default : comparer, settings) { }
+
public SyncList(List collection, IEqualityComparer comparer = null, SyncTypeSettings settings = new()) : base(settings)
{
_comparer = (comparer == null) ? EqualityComparer.Default : comparer;
@@ -176,18 +179,17 @@ public List GetCollection(bool asServer)
///
///
///
-
private void AddOperation(SyncListOperation operation, int index, T prev, T next)
{
if (!base.IsInitialized)
return;
/* asServer might be true if the client is setting the value
- * through user code. Typically synctypes can only be set
- * by the server, that's why it is assumed asServer via user code.
- * However, when excluding owner for the synctype the client should
- * have permission to update the value locally for use with
- * prediction. */
+ * through user code. Typically synctypes can only be set
+ * by the server, that's why it is assumed asServer via user code.
+ * However, when excluding owner for the synctype the client should
+ * have permission to update the value locally for use with
+ * prediction. */
bool asServerInvoke = (!base.IsNetworkInitialized || base.NetworkBehaviour.IsServerStarted);
/* Only the adds asServer may set
@@ -197,10 +199,10 @@ private void AddOperation(SyncListOperation operation, int index, T prev, T next
if (asServerInvoke)
{
/* Set as changed even if cannot dirty.
- * Dirty is only set when there are observers,
- * but even if there are not observers
- * values must be marked as changed so when
- * there are observers, new values are sent. */
+ * Dirty is only set when there are observers,
+ * but even if there are not observers
+ * values must be marked as changed so when
+ * there are observers, new values are sent. */
_valuesChanged = true;
/* If unable to dirty then do not add to changed.
@@ -255,10 +257,10 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT
else
{
base.WriteDelta(writer, resetSyncTick);
-
+
//False for not full write.
writer.WriteBoolean(false);
-
+
//Number of entries expected.
writer.WriteInt32(_changed.Count);
@@ -312,12 +314,11 @@ internal protected override void WriteFull(PooledWriter writer)
///
/// Reads and sets the current values for server or client.
///
-
[APIExclude]
internal protected override void Read(PooledReader reader, bool asServer)
{
base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues);
-
+
//True to warn if this object was deinitialized on the server.
bool deinitialized = (asClientHost && !base.OnStartServerCalled);
if (deinitialized)
@@ -343,7 +344,7 @@ internal protected override void Read(PooledReader reader, bool asServer)
if (operation == SyncListOperation.Add)
{
next = reader.Read();
-
+
if (canModifyValues)
{
index = collection.Count;
@@ -361,7 +362,7 @@ internal protected override void Read(PooledReader reader, bool asServer)
{
index = reader.ReadInt32();
next = reader.Read();
-
+
if (canModifyValues)
collection.Insert(index, next);
}
@@ -369,7 +370,7 @@ internal protected override void Read(PooledReader reader, bool asServer)
else if (operation == SyncListOperation.RemoveAt)
{
index = reader.ReadInt32();
-
+
if (canModifyValues)
{
prev = collection[index];
@@ -381,7 +382,7 @@ internal protected override void Read(PooledReader reader, bool asServer)
{
index = reader.ReadInt32();
next = reader.Read();
-
+
if (canModifyValues)
{
prev = collection[index];
@@ -425,14 +426,19 @@ private void InvokeOnChange(SyncListOperation operation, int index, T prev, T ne
internal protected override void ResetState(bool asServer)
{
base.ResetState(asServer);
- _sendAll = false;
- _changed.Clear();
- Collection.Clear();
- foreach (T item in _initialValues)
- Collection.Add(item);
- }
+ bool canReset = (asServer || !base.IsReadAsClientHost(asServer));
+
+ if (canReset)
+ {
+ _sendAll = false;
+ _changed.Clear();
+ Collection.Clear();
+ foreach (T item in _initialValues)
+ Collection.Add(item);
+ }
+ }
///
/// Adds value.
@@ -442,6 +448,7 @@ public void Add(T item)
{
Add(item, true);
}
+
private void Add(T item, bool asServer)
{
if (!base.CanNetworkSetValues(true))
@@ -451,6 +458,7 @@ private void Add(T item, bool asServer)
if (asServer)
AddOperation(SyncListOperation.Add, Collection.Count - 1, default, item);
}
+
///
/// Adds a range of values.
///
@@ -468,6 +476,7 @@ public void Clear()
{
Clear(true);
}
+
private void Clear(bool asServer)
{
if (!base.CanNetworkSetValues(true))
@@ -564,6 +573,7 @@ public void Insert(int index, T item)
{
Insert(index, item, true);
}
+
private void Insert(int index, T item, bool asServer)
{
if (!base.CanNetworkSetValues(true))
@@ -612,6 +622,7 @@ public void RemoveAt(int index)
{
RemoveAt(index, true);
}
+
private void RemoveAt(int index, bool asServer)
{
if (!base.CanNetworkSetValues(true))
@@ -632,11 +643,11 @@ public int RemoveAll(Predicate match)
{
List toRemove = new();
for (int i = 0; i < Collection.Count; ++i)
- {
+ {
if (match(Collection[i]))
toRemove.Add(Collection[i]);
}
-
+
foreach (T entry in toRemove)
Remove(entry);
@@ -683,6 +694,7 @@ public void Dirty(T obj)
else
base.NetworkManager.LogError($"Could not find object within SyncList, dirty will not be set.");
}
+
///
/// Marks an index as dirty.
/// While using this operation previous value will be the same as next.
@@ -698,6 +710,7 @@ public void Dirty(int index)
if (asServer)
AddOperation(SyncListOperation.Set, index, value, value);
}
+
///
/// Sets value at index.
///
@@ -707,6 +720,7 @@ public void Set(int index, T value, bool force = true)
{
Set(index, value, true, force);
}
+
private void Set(int index, T value, bool asServer, bool force)
{
if (!base.CanNetworkSetValues(true))
@@ -722,17 +736,17 @@ private void Set(int index, T value, bool asServer, bool force)
}
}
-
///
/// Returns Enumerator for collection.
///
///
public IEnumerator GetEnumerator() => Collection.GetEnumerator();
+
[APIExclude]
IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator();
+
[APIExclude]
IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator();
-
}
}
#endif
\ No newline at end of file
diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncTimer.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncTimer.cs
index f27bddf13..4c22787f6 100644
--- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncTimer.cs
+++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncTimer.cs
@@ -275,6 +275,8 @@ internal protected override void Read(PooledReader reader, bool asServer)
base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues);
int changes = reader.ReadInt32();
+ //Has previous value if should invoke finished.
+ float? finishedPrevious = null;
for (int i = 0; i < changes; i++)
{
@@ -292,7 +294,17 @@ internal protected override void Read(PooledReader reader, bool asServer)
}
if (newChangeId)
+ {
InvokeOnChange(op, -1f, next, asServer);
+ /* If next is 0 then that means the timer
+ * expired on the same tick it was started.
+ * This can be true depending on when in code
+ * the server starts the timer.
+ *
+ * When 0 also invoke finished. */
+ if (next == 0)
+ finishedPrevious = duration;
+ }
}
else if (op == SyncTimerOperation.Pause || op == SyncTimerOperation.PauseUpdated || op == SyncTimerOperation.Unpause)
{
@@ -348,6 +360,8 @@ void UpdatePauseState(SyncTimerOperation op)
if (newChangeId && changes > 0)
InvokeOnChange(SyncTimerOperation.Complete, -1f, -1f, false);
+ if (finishedPrevious.HasValue)
+ InvokeFinished(finishedPrevious.Value);
}
///
@@ -446,6 +460,15 @@ public void Update(float delta)
* for some but at this time I'm unable to think of any
* problems. */
Remaining = 0f;
+ InvokeFinished(prev);
+ }
+
+ ///
+ /// Invokes SyncTimer finished a previous value.
+ ///
+ ///
+ private void InvokeFinished(float prev)
+ {
if (base.NetworkManager.IsServerStarted)
OnChange?.Invoke(SyncTimerOperation.Finished, prev, 0f, true);
if (base.NetworkManager.IsClientStarted)
diff --git a/Assets/FishNet/Runtime/Observing/NetworkObserver.cs b/Assets/FishNet/Runtime/Observing/NetworkObserver.cs
index 93039e36b..f07de094a 100644
--- a/Assets/FishNet/Runtime/Observing/NetworkObserver.cs
+++ b/Assets/FishNet/Runtime/Observing/NetworkObserver.cs
@@ -135,9 +135,9 @@ internal List ObserverConditionsInternal
///
internal void Deinitialize(bool destroyed)
{
-
_lastParentVisible = false;
- _nonTimedMet?.Clear();
+ if (_nonTimedMet != null)
+ _nonTimedMet.Clear();
UnregisterTimedConditions();
if (_serverManager != null)
@@ -187,8 +187,8 @@ internal void Initialize(NetworkObject networkObject)
/* Sort the conditions so that normal conditions are first.
* This prevents normal conditions from being skipped if a timed
- * condition fails before the normal passed.
- *
+ * condition fails before the normal passed.
+ *
* Example: Let's say an object has a distance and scene condition, with
* the distance condition being first. Normal conditions are only checked
* as the change occurs, such as when the scene was loaded. So if the client
@@ -196,59 +196,62 @@ internal void Initialize(NetworkObject networkObject)
* iterations would skip remaining, which would be the scene condition. As
* result normal conditions (non timed) would never be met since they are only
* checked as-needed, in this case during a scene change.
- *
+ *
* By moving normal conditions to the front they will always be checked first
* and timed can update at intervals per expectancy. This could also be resolved
* by simply not exiting early when a condition fails but that's going to
* cost hotpath performance where sorting is only done once. */
- //Linq would be easier but less performant.
- List sortedConditions = CollectionCaches.RetrieveList();
//Initialize collections.
- _timedConditions = CollectionCaches.RetrieveList();
_nonTimedMet = CollectionCaches.RetrieveHashSet();
+ //Caches for ordering.
+ List nonTimedConditions = CollectionCaches.RetrieveList();
+ List timedConditions = CollectionCaches.RetrieveList();
- //Next index a sorted condition will be inserted into.
- int nextSortedNormalConditionIndex = 0;
bool observerFound = false;
- for (int i = 0; i < _observerConditions.Count; i++)
+ foreach (ObserverCondition condition in _observerConditions)
{
- if (_observerConditions[i] != null)
+ if (condition == null)
+ continue;
+
+ observerFound = true;
+
+ /* Make an instance of each condition so values are
+ * not overwritten when the condition exist more than
+ * once in the scene. Double-edged sword of using scriptable
+ * objects for conditions. */
+ ObserverCondition ocCopy = Instantiate(condition);
+
+ //Condition type.
+ ObserverConditionType oct = ocCopy.GetConditionType();
+ if (oct == ObserverConditionType.Timed)
{
- observerFound = true;
-
- /* Make an instance of each condition so values are
- * not overwritten when the condition exist more than
- * once in the scene. Double edged sword of using scriptable
- * objects for conditions. */
- ObserverCondition ocCopy = Instantiate(_observerConditions[i]);
- _observerConditions[i] = ocCopy;
-
- //Condition type.
- ObserverConditionType oct = ocCopy.GetConditionType();
- if (oct == ObserverConditionType.Timed)
- {
- sortedConditions.Add(ocCopy);
- }
- else
- {
- _hasNormalConditions = true;
- sortedConditions.Insert(nextSortedNormalConditionIndex++, ocCopy);
- }
- //REPLACE WITH THIS BLOCK ..^
- if (oct == ObserverConditionType.Timed)
- _timedConditions.Add(ocCopy);
+ timedConditions.AddOrdered(ocCopy);
}
else
{
- _observerConditions.RemoveAt(i);
- i--;
+ _hasNormalConditions = true;
+ nonTimedConditions.AddOrdered(ocCopy);
}
}
- //Store original collection and replace with one from cache.
- CollectionCaches.Store(_observerConditions);
- _observerConditions = sortedConditions;
+ //Add to condition collection as ordered now.
+ _observerConditions.Clear();
+ //Non timed.
+ for (int i = 0; i < nonTimedConditions.Count; i++)
+ _observerConditions.Add(nonTimedConditions[i]);
+
+ //Timed.
+ _timedConditions = CollectionCaches.RetrieveList();
+ foreach (ObserverCondition condition in timedConditions)
+ {
+ _observerConditions.Add(condition);
+ _timedConditions.Add(condition);
+ }
+
+ //Store caches.
+ CollectionCaches.Store(nonTimedConditions);
+ CollectionCaches.Store(timedConditions);
//No observers specified, do not need to take further action.
if (!observerFound)
@@ -288,7 +291,6 @@ public ObserverCondition GetObserverCondition() where T : ObserverCondition
/// Returns ObserverStateChange by comparing conditions for a connection.
///
/// True if added to Observers.
-
internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool timedOnly)
{
bool currentlyAdded = (_networkObject.Observers.Contains(connection));
@@ -297,7 +299,7 @@ internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool
/* If cnnection is owner then they can see the object. */
bool notOwner = (connection != _networkObject.Owner);
/* Only check conditions if not owner. Owner will always
- * have visibility. */
+ * have visibility. */
if (notOwner)
{
bool parentVisible = true;
@@ -346,7 +348,7 @@ internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool
* from loop and return removed. If one observer has
* removed then there's no reason to iterate
* the rest.
- *
+ *
* A condition is automatically met if it's not enabled. */
bool notProcessed = false;
bool conditionMet = (!condition.GetIsEnabled() || condition.ConditionMet(connection, currentlyAdded, out notProcessed));
@@ -468,6 +470,5 @@ public void SetUpdateHostVisibility(bool value)
UpdateHostVisibility = value;
}
-
}
-}
+}
\ No newline at end of file
diff --git a/Assets/FishNet/Runtime/Observing/ObserverCondition.cs b/Assets/FishNet/Runtime/Observing/ObserverCondition.cs
index 7203f4565..8e6672b6c 100644
--- a/Assets/FishNet/Runtime/Observing/ObserverCondition.cs
+++ b/Assets/FishNet/Runtime/Observing/ObserverCondition.cs
@@ -2,6 +2,7 @@
using FishNet.Managing.Server;
using FishNet.Object;
using System;
+using GameKit.Dependencies.Utilities.Types;
using UnityEngine;
namespace FishNet.Observing
@@ -10,7 +11,7 @@ namespace FishNet.Observing
/// Condition a connection must meet to be added as an observer.
/// This class can be inherited from for custom conditions.
///
- public abstract class ObserverCondition : ScriptableObject
+ public abstract class ObserverCondition : ScriptableObject, IOrderable
{
#region Public.
///
@@ -20,6 +21,17 @@ public abstract class ObserverCondition : ScriptableObject
public NetworkObject NetworkObject;
#endregion
+ #region Serialized.
+ ///
+ /// Order in which conditions are added to the NetworkObserver. Lower values will added first, resulting in the condition being checked first. Timed conditions will never check before non-timed conditions.
+ ///
+ public int Order => _addOrder;
+ [Tooltip("Order in which conditions are added to the NetworkObserver. Lower values will added first, resulting in the condition being checked first. Timed conditions will never check before non-timed conditions.")]
+ [SerializeField]
+ [Range(sbyte.MinValue, sbyte.MaxValue)]
+ private sbyte _addOrder;
+ #endregion
+
#region Private.
///
/// True if this condition is enabled.
@@ -80,6 +92,5 @@ public virtual void Deinitialize(bool destroyed)
///
///
public abstract ObserverConditionType GetConditionType();
-
}
}
diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Arrays.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Arrays.cs
index 09224bb05..1a4a4e871 100644
--- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Arrays.cs
+++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Arrays.cs
@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Text;
+using GameKit.Dependencies.Utilities.Types;
namespace GameKit.Dependencies.Utilities
{
-
public static class Arrays
{
///
@@ -81,7 +81,6 @@ public static void FastIndexRemove(this List list, int index)
list.RemoveAt(list.Count - 1);
}
-
///
/// Shuffles an array.
///
@@ -116,7 +115,33 @@ public static void Shuffle(this List lst)
}
}
- }
-
+ ///
+ /// Adds an item to a collection, ordering it's position based on itemOrder. Lower values are inserted near the beginning of the collection.
+ ///
+ public static void AddOrdered(this List collection, T item) where T : IOrderable
+ {
+ int count = collection.Count;
+ int itemOrder = item.Order;
+ /* If no entries or is equal or larger to last
+ * entry then value can be added onto the end. */
+ if (count == 0 || itemOrder >= collection[^1].Order)
+ {
+ collection.Add(item);
+ }
+ else
+ {
+ for (int i = 0; i < count; i++)
+ {
+ /* If item being sorted is lower than the one in already added.
+ * then insert it before the one already added. */
+ if (itemOrder <= collection[i].Order)
+ {
+ collection.Insert(i, item);
+ break;
+ }
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/IOrderable.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/IOrderable.cs
new file mode 100644
index 000000000..8761eec30
--- /dev/null
+++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/IOrderable.cs
@@ -0,0 +1,10 @@
+namespace GameKit.Dependencies.Utilities.Types
+{
+
+ public interface IOrderable
+ {
+ public int Order { get; }
+ }
+
+
+}
\ No newline at end of file
diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/IOrderable.cs.meta b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/IOrderable.cs.meta
new file mode 100644
index 000000000..b0c668cd9
--- /dev/null
+++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/IOrderable.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 310411b63c217834297f4b81bda8b175
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ResettableRingBuffer.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ResettableRingBuffer.cs
index 20eabaf3b..e2deafd23 100644
--- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ResettableRingBuffer.cs
+++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ResettableRingBuffer.cs
@@ -78,6 +78,10 @@ public int ActualIndex
public void Initialize(ResettableRingBuffer c)
{
+ //if none are written then return.
+ if (c.Count == 0)
+ return;
+
_entriesEnumerated = 0;
_startIndex = c.GetRealIndex(0);
_enumeratedRingBuffer = c;
diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/RingBuffer.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/RingBuffer.cs
index c734f0a62..caf032a3e 100644
--- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/RingBuffer.cs
+++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/RingBuffer.cs
@@ -54,6 +54,10 @@ public struct Enumerator : IEnumerator
public void Initialize(RingBuffer c)
{
+ //if none are written then return.
+ if (c.Count == 0)
+ return;
+
_entriesEnumerated = 0;
_startIndex = c.GetRealIndex(0);
_enumeratedRingBuffer = c;
@@ -200,7 +204,7 @@ public void Initialize(int capacity)
void GetNewCollection() => Collection = ArrayPool.Shared.Rent(capacity);
}
-
+
///
/// Initializes with default capacity.
///
@@ -214,7 +218,6 @@ public void Initialize()
}
}
-
///
/// Clears the collection to default values and resets indexing.
///
@@ -404,7 +407,7 @@ public void RemoveRange(bool fromStart, int length)
WriteIndex += Capacity;
}
}
-
+
///
/// Returns Enumerator for the collection.
///
diff --git a/Assets/FishNet/Runtime/Plugins/SourceAnalyzer.meta b/Assets/FishNet/Runtime/Plugins/SourceAnalyzer.meta
deleted file mode 100644
index da863e04c..000000000
--- a/Assets/FishNet/Runtime/Plugins/SourceAnalyzer.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 8e4d0af726c8ae041afff9bebf93b00d
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/FishNet/Runtime/Plugins/SourceAnalyzer/FishNet.SourceAnalyzer.dll b/Assets/FishNet/Runtime/Plugins/SourceAnalyzer/FishNet.SourceAnalyzer.dll
deleted file mode 100644
index 26a09347f..000000000
Binary files a/Assets/FishNet/Runtime/Plugins/SourceAnalyzer/FishNet.SourceAnalyzer.dll and /dev/null differ
diff --git a/Assets/FishNet/Runtime/Plugins/SourceAnalyzer/FishNet.SourceAnalyzer.dll.meta b/Assets/FishNet/Runtime/Plugins/SourceAnalyzer/FishNet.SourceAnalyzer.dll.meta
deleted file mode 100644
index 574892ce2..000000000
--- a/Assets/FishNet/Runtime/Plugins/SourceAnalyzer/FishNet.SourceAnalyzer.dll.meta
+++ /dev/null
@@ -1,82 +0,0 @@
-fileFormatVersion: 2
-guid: 9cd0504922c83fa4ba539c095513ba14
-labels:
-- RoslynAnalyzer
-PluginImporter:
- externalObjects: {}
- serializedVersion: 2
- iconMap: {}
- executionOrder: {}
- defineConstraints: []
- isPreloaded: 0
- isOverridable: 0
- isExplicitlyReferenced: 0
- validateReferences: 1
- platformData:
- - first:
- : Any
- second:
- enabled: 0
- settings:
- Exclude Android: 1
- Exclude Editor: 1
- Exclude Linux64: 1
- Exclude Lumin: 1
- Exclude OSXUniversal: 1
- Exclude WebGL: 1
- Exclude Win: 1
- Exclude Win64: 1
- Exclude iOS: 1
- Exclude tvOS: 1
- - first:
- Android: Android
- second:
- enabled: 0
- settings:
- CPU: ARMv7
- - first:
- Any:
- second:
- enabled: 0
- settings: {}
- - first:
- Editor: Editor
- second:
- enabled: 0
- settings:
- CPU: AnyCPU
- DefaultValueInitialized: true
- OS: AnyOS
- - first:
- Standalone: Linux64
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Standalone: OSXUniversal
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Standalone: Win
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Standalone: Win64
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Windows Store Apps: WindowsStoreApps
- second:
- enabled: 0
- settings:
- CPU: AnyCPU
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/FishNet/Runtime/Plugins/SourceGenerator.meta b/Assets/FishNet/Runtime/Plugins/SourceGenerator.meta
deleted file mode 100644
index 61d05dd7d..000000000
--- a/Assets/FishNet/Runtime/Plugins/SourceGenerator.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 6fdcb0e6962716643b3acd6207c55a8b
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/FishNet/Runtime/Plugins/SourceGenerator/FishNet.SourceGenerator.dll b/Assets/FishNet/Runtime/Plugins/SourceGenerator/FishNet.SourceGenerator.dll
deleted file mode 100644
index 0d134ca65..000000000
Binary files a/Assets/FishNet/Runtime/Plugins/SourceGenerator/FishNet.SourceGenerator.dll and /dev/null differ
diff --git a/Assets/FishNet/Runtime/Plugins/SourceGenerator/FishNet.SourceGenerator.dll.meta b/Assets/FishNet/Runtime/Plugins/SourceGenerator/FishNet.SourceGenerator.dll.meta
deleted file mode 100644
index 662392893..000000000
--- a/Assets/FishNet/Runtime/Plugins/SourceGenerator/FishNet.SourceGenerator.dll.meta
+++ /dev/null
@@ -1,87 +0,0 @@
-fileFormatVersion: 2
-guid: b3080f1fc7fd0884382d9e837fd946ea
-labels:
-- RoslynAnalyzer
-PluginImporter:
- externalObjects: {}
- serializedVersion: 2
- iconMap: {}
- executionOrder: {}
- defineConstraints: []
- isPreloaded: 0
- isOverridable: 0
- isExplicitlyReferenced: 0
- validateReferences: 1
- platformData:
- - first:
- : Any
- second:
- enabled: 0
- settings:
- Exclude Android: 1
- Exclude Editor: 1
- Exclude Linux64: 1
- Exclude Lumin: 1
- Exclude OSXUniversal: 1
- Exclude WebGL: 1
- Exclude Win: 1
- Exclude Win64: 1
- Exclude iOS: 1
- Exclude tvOS: 1
- - first:
- Android: Android
- second:
- enabled: 0
- settings:
- CPU: ARMv7
- - first:
- Any:
- second:
- enabled: 0
- settings: {}
- - first:
- Editor: Editor
- second:
- enabled: 0
- settings:
- CPU: AnyCPU
- DefaultValueInitialized: true
- OS: AnyOS
- - first:
- Standalone: Linux64
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Standalone: OSXUniversal
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Standalone: Win
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Standalone: Win64
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- WebGL: WebGL
- second:
- enabled: 0
- settings: {}
- - first:
- Windows Store Apps: WindowsStoreApps
- second:
- enabled: 0
- settings:
- CPU: AnyCPU
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/FishNet/Runtime/Serializing/Reader.cs b/Assets/FishNet/Runtime/Serializing/Reader.cs
index c32640f22..71072dc21 100644
--- a/Assets/FishNet/Runtime/Serializing/Reader.cs
+++ b/Assets/FishNet/Runtime/Serializing/Reader.cs
@@ -205,7 +205,14 @@ public Dictionary ReadDictionaryAllocated()
return null;
int count = ReadInt32();
-
+ if (count < 0)
+ {
+ NetworkManager.Log($"Dictionary count cannot be less than 0.");
+ //Purge renaming and return default.
+ Position += Remaining;
+ return default;
+ }
+
Dictionary result = new(count);
for (int i = 0; i < count; i++)
{
@@ -372,7 +379,13 @@ public void ReadUInt8Array(ref byte[] buffer, int count)
///
public ArraySegment ReadArraySegment(int count)
{
- if (count == 0) return default;
+ if (count < 0)
+ {
+ NetworkManager.Log($"ArraySegment count cannot be less than 0.");
+ //Purge renaming and return default.
+ Position += Remaining;
+ return default;
+ }
ArraySegment result = new(_buffer, Position, count);
Position += count;
@@ -896,6 +909,15 @@ public Matrix4x4 ReadMatrix4x4Unpacked()
///
public byte[] ReadUInt8ArrayAllocated(int count)
{
+ if (count < 0)
+ {
+ NetworkManager.Log($"Bytes count cannot be less than 0.");
+ //Purge renaming and return default.
+ Position += Remaining;
+ return default;
+ }
+
+
byte[] bytes = new byte[count];
ReadUInt8Array(ref bytes, count);
return bytes;
@@ -1353,6 +1375,14 @@ internal int ReadReplicate(ref T[] collection, uint tick) where T : IReplicat
{
//Number of entries written.
int count = (int)ReadUInt8Unpacked();
+ if (count <= 0)
+ {
+ NetworkManager.Log($"Replicate count cannot be 0 or less.");
+ //Purge renaming and return default.
+ Position += Remaining;
+ return default;
+ }
+
if (collection == null || collection.Length < count)
collection = new T[count];
@@ -1398,6 +1428,14 @@ public List ReadListAllocated()
public int ReadList(ref List collection, bool allowNullification = false)
{
int count = (int)ReadSignedPackedWhole();
+ if (count < 0)
+ {
+ NetworkManager.Log($"List count cannot be less than 0.");
+ //Purge renaming and return default.
+ Position += Remaining;
+ return default;
+ }
+
if (count == Writer.UNSET_COLLECTION_SIZE_VALUE)
{
if (allowNullification)
@@ -1440,6 +1478,14 @@ public T[] ReadArrayAllocated()
public int ReadArray(ref T[] collection)
{
int count = (int)ReadSignedPackedWhole();
+ if (count < 0)
+ {
+ NetworkManager.Log($"Array count cannot be less than 0.");
+ //Purge renaming and return default.
+ Position += Remaining;
+ return default;
+ }
+
if (count == Writer.UNSET_COLLECTION_SIZE_VALUE)
{
return 0;
diff --git a/Assets/FishNet/Runtime/Transporting/ConnectionStates.cs b/Assets/FishNet/Runtime/Transporting/ConnectionStates.cs
index 8e78a6a82..8c03fabb9 100644
--- a/Assets/FishNet/Runtime/Transporting/ConnectionStates.cs
+++ b/Assets/FishNet/Runtime/Transporting/ConnectionStates.cs
@@ -4,24 +4,27 @@
///
/// States the local connection can be in.
///
- public enum LocalConnectionState : byte
+ public enum LocalConnectionState : int
{
///
/// Connection is fully stopped.
///
- Stopped = 0,
+ Stopped = (1 << 3) | (1 << 4),
///
- /// Connection is starting but not yet established.
+ /// Connection is stopping.
///
- Starting = 1,
+ Stopping = 1,
///
- /// Connection is established.
+ /// Connection is starting but not yet established.
///
- Started = 2,
+ Starting = 2,
///
- /// Connection is stopping.
+ /// Connection is established.
///
- Stopping = 3
+ Started = 4,
+
+ StoppedError = 8,
+ StoppedClosed = 16,
}
///
diff --git a/Assets/FishNet/Runtime/Transporting/Transports/Tugboat/Tugboat.cs b/Assets/FishNet/Runtime/Transporting/Transports/Tugboat/Tugboat.cs
index b88240815..cb1e18821 100644
--- a/Assets/FishNet/Runtime/Transporting/Transports/Tugboat/Tugboat.cs
+++ b/Assets/FishNet/Runtime/Transporting/Transports/Tugboat/Tugboat.cs
@@ -89,6 +89,14 @@ public class Tugboat : Transport
/// Client socket and handler.
///
private Client.ClientSocket _client = new();
+ ///
+ /// Current timeout for the client.
+ ///
+ private int _clientTimeout = MAX_TIMEOUT_SECONDS;
+ ///
+ /// Current timeout for the server.
+ ///
+ private int _serverTimeout = MAX_TIMEOUT_SECONDS;
#endregion
#region Const.
@@ -177,7 +185,6 @@ public override void HandleClientConnectionState(ClientConnectionStateArgs conne
public override void HandleServerConnectionState(ServerConnectionStateArgs connectionStateArgs)
{
OnServerConnectionState?.Invoke(connectionStateArgs);
- UpdateTimeout();
}
///
/// Handles a ConnectionStateArgs for a remote client.
@@ -307,7 +314,16 @@ public override float GetTimeout(bool asServer)
/// Sets how long in seconds until either the server or client socket must go without data before being timed out.
///
/// True to set the timeout for the server socket, false for the client socket.
- public override void SetTimeout(float value, bool asServer) { }
+ public override void SetTimeout(float value, bool asServer)
+ {
+ int timeoutValue = (int)Math.Ceiling(value);
+ if (asServer)
+ _serverTimeout = timeoutValue;
+ else
+ _clientTimeout = timeoutValue;
+
+ UpdateTimeout();
+ }
///
/// Returns the maximum number of clients allowed to connect to the server. If the transport does not support this method the value -1 is returned.
///
@@ -475,9 +491,8 @@ private bool StartClient(string address)
///
private void UpdateTimeout()
{
- int timeout = MAX_TIMEOUT_SECONDS;
- _client.UpdateTimeout(timeout);
- _server.UpdateTimeout(timeout);
+ _client.UpdateTimeout(_clientTimeout);
+ _server.UpdateTimeout(_serverTimeout);
}
///
/// Stops the client.
diff --git a/Assets/FishNet/package.json b/Assets/FishNet/package.json
index cd3da88ef..580f5a5ff 100644
--- a/Assets/FishNet/package.json
+++ b/Assets/FishNet/package.json
@@ -1,6 +1,6 @@
{
"name": "com.firstgeargames.fishnet",
- "version": "4.4.7",
+ "version": "4.5.0",
"displayName": "FishNet: Networking Evolved",
"description": "A feature-rich Unity networking solution aimed towards reliability, ease of use, efficiency, and flexibility.",
"unity": "2021.3",