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",