diff --git a/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef b/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef index 9aa9b97a..22a5b611 100644 --- a/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef +++ b/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef @@ -4,7 +4,10 @@ "references": [ "FishNet.Runtime", "FishNet.Codegen.Cecil", - "GameKit.Dependencies" + "GameKit.Dependencies", + "Unity.Burst", + "Unity.Mathematics", + "Unity.Collections" ], "includePlatforms": [ "Editor" diff --git a/Assets/FishNet/Runtime/CodeGenerating/Attributes.cs b/Assets/FishNet/Runtime/CodeGenerating/Attributes.cs index 4eae338d..bfa4f8a2 100644 --- a/Assets/FishNet/Runtime/CodeGenerating/Attributes.cs +++ b/Assets/FishNet/Runtime/CodeGenerating/Attributes.cs @@ -30,7 +30,7 @@ public class NotSerializerAttribute : Attribute { } /// /// Method or type will be made public by codegen. /// - internal class MakePublicAttribute : Attribute { } + public class MakePublicAttribute : Attribute { } /// /// Method is a comparer for a value type. diff --git a/Assets/FishNet/Runtime/Connection/NetworkConnection.QOL.cs b/Assets/FishNet/Runtime/Connection/NetworkConnection.QOL.cs index 43c45e12..0c63fa69 100644 --- a/Assets/FishNet/Runtime/Connection/NetworkConnection.QOL.cs +++ b/Assets/FishNet/Runtime/Connection/NetworkConnection.QOL.cs @@ -43,10 +43,10 @@ public string GetAddress() /// Reason client is being kicked. /// How to print logging as. /// Optional message to be debug logged. - public void Kick(KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "") + public void Kick(KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "", bool immediately = true) { if (CanKick()) - NetworkManager.ServerManager.Kick(this, kickReason, loggingType, log); + NetworkManager.ServerManager.Kick(this, kickReason, loggingType, log, immediately); } /// @@ -56,10 +56,10 @@ public void Kick(KickReason kickReason, LoggingType loggingType = LoggingType.Co /// Reason client is being kicked. /// How to print logging as. /// Optional message to be debug logged. - public void Kick(Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "") + public void Kick(Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "", bool immediately = true) { if (CanKick()) - NetworkManager.ServerManager.Kick(this, reader, kickReason, loggingType, log); + NetworkManager.ServerManager.Kick(this, reader, kickReason, loggingType, log, immediately); } private bool CanKick() diff --git a/Assets/FishNet/Runtime/FishNet.Runtime.asmdef b/Assets/FishNet/Runtime/FishNet.Runtime.asmdef index dc41020c..b2c5f6a9 100644 --- a/Assets/FishNet/Runtime/FishNet.Runtime.asmdef +++ b/Assets/FishNet/Runtime/FishNet.Runtime.asmdef @@ -4,7 +4,9 @@ "references": [ "GUID:894a6cc6ed5cd2645bb542978cbed6a9", "GUID:1d82bdf40e2465b44b34adf79595e74c", - "GUID:d8b63aba1907145bea998dd612889d6b" + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:2665a8d13d1b3f18800f46e256720795", + "GUID:e0cd26848372d4e5c891c569017e11f1" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs b/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs index 4c312763..1bd718e9 100644 --- a/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs +++ b/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs @@ -15,8 +15,9 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using FishNet.Managing; -using Unity.Profiling; using UnityEngine; +using UnityEngine.Profiling; +using Unity.Profiling; using TimeManagerCls = FishNet.Managing.Timing.TimeManager; namespace FishNet.Component.Animating @@ -33,6 +34,7 @@ private struct ReceivedServerData /// /// Gets an Arraysegment of received data. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArraySegment GetArraySegment() => new(_data, 0, _length); /// @@ -51,6 +53,7 @@ public ReceivedServerData(ArraySegment segment) Buffer.BlockCopy(segment.Array, segment.Offset, _data, 0, _length); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { if (_data != null) @@ -210,6 +213,7 @@ public void GetBuffer(int index, ref byte[] buffer, ref int length) /// /// Resets buffers. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() { BufferCount = 0; @@ -274,6 +278,23 @@ public ParameterDetail(AnimatorControllerParameter controllerParameter, byte typ } #endregion + #region Private + + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("NetworkAnimator.TimeManager_OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("NetworkAnimator.TimeManager_OnPostTick()"); + private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("NetworkAnimator.TimeManager_OnUpdate()"); + private static readonly ProfilerMarker _pm_CheckSendToServer = new ProfilerMarker("NetworkAnimator.CheckSendToServer()"); + private static readonly ProfilerMarker _pm_CheckSendToClients = new ProfilerMarker("NetworkAnimator.CheckSendToClients()"); + private static readonly ProfilerMarker _pm_SmoothFloats = new ProfilerMarker("NetworkAnimator.SmoothFloats()"); + private static readonly ProfilerMarker _pm_AnimatorUpdated = new ProfilerMarker("NetworkAnimator.AnimatorUpdated(ref ArraySegment, bool)"); + private static readonly ProfilerMarker _pm_ApplyParametersUpdated = new ProfilerMarker("NetworkAnimator.ApplyParametersUpdated(ref ArraySegment)"); + + #endregion + + #endregion + #region Public. /// /// Parameters which will not be synchronized. @@ -304,6 +325,14 @@ public Animator Animator [SerializeField] private bool _synchronizeWhenDisabled; /// + /// True to synchronize changes even when the animator component is disabled. + /// + public bool SynchronizeWhenDisabled + { + get { return _synchronizeWhenDisabled; } + set { _synchronizeWhenDisabled = value; } + } + /// /// True to smooth float value changes for spectators. /// [Tooltip("True to smooth float value changes for spectators.")] @@ -334,6 +363,11 @@ public bool ClientAuthoritative [Tooltip("True to synchronize server results back to owner. Typically used when you are changing animations on the server and are relying on the server response to update the clients animations.")] [SerializeField] private bool _sendToOwner; + /// + /// True to synchronize server results back to owner. Typically used when you are changing animations on the server and are relying on the server response to update the clients animations. + /// + public bool SendToOwner => _sendToOwner; + #endregion #region Private. @@ -370,12 +404,19 @@ public bool ClientAuthoritative // /// // private List _toClientsBuffer = new(); /// + /// Synchronization enabled state. True by default + /// + private bool _isSynchronizationEnabled = true; + /// /// Returns if the animator is exist and can be synchronized. /// private bool _canSynchronizeAnimator { get { + if (!_isSynchronizationEnabled) + return false; + if (!_isAnimatorSet) return false; @@ -459,17 +500,6 @@ private bool _canSmoothFloats private bool _subscribedToTicks; #endregion - #region Private Profiler Markers - private static readonly ProfilerMarker _pm_OnPreTick = new("NetworkAnimator.TimeManager_OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostTick = new("NetworkAnimator.TimeManager_OnPostTick()"); - private static readonly ProfilerMarker _pm_OnUpdate = new("NetworkAnimator.TimeManager_OnUpdate()"); - private static readonly ProfilerMarker _pm_CheckSendToServer = new("NetworkAnimator.CheckSendToServer()"); - private static readonly ProfilerMarker _pm_CheckSendToClients = new("NetworkAnimator.CheckSendToClients()"); - private static readonly ProfilerMarker _pm_SmoothFloats = new("NetworkAnimator.SmoothFloats()"); - private static readonly ProfilerMarker _pm_AnimatorUpdated = new("NetworkAnimator.AnimatorUpdated(ref ArraySegment, bool)"); - private static readonly ProfilerMarker _pm_ApplyParametersUpdated = new("NetworkAnimator.ApplyParametersUpdated(ref ArraySegment)"); - #endregion - #region Const. ///// ///// How much time to fall behind when using smoothing. Only increase value if the smoothing is sometimes jittery. Recommended values are between 0 and 0.04. @@ -515,6 +545,7 @@ public override void OnSpawnServer(NetworkConnection connection) public override void OnStartNetwork() { ChangeTickSubscription(true); + _isSynchronizationEnabled = true; } [APIExclude] @@ -584,6 +615,7 @@ private void TimeManager_OnPreTick() _fromServerBuffer.Clear(); return; } + //Disabled/cannot start. if (_startTick == 0) return; @@ -593,6 +625,7 @@ private void TimeManager_OnPreTick() _startTick = 0; return; } + //Not enough time has passed to start queue. if (TimeManager.LocalTick < _startTick) return; @@ -645,7 +678,7 @@ private void InitializeOnce() //Don't run the rest if not in play mode. if (!ApplicationState.IsPlaying()) return; - + if (!_canSynchronizeAnimator) { //Debug.LogWarning("Animator is null or not enabled; unable to initialize for animator. Use SetAnimator if animator was changed or enable the animator."); @@ -708,6 +741,15 @@ private void InitializeOnce() } } } + + /// + /// Sets synchronization state to NetworkAnimator. Enabled by default. + /// + /// + public void SetSynchronizationState(bool state) + { + _isSynchronizationEnabled = state; + } /// /// Sets which animator to use. You must call this with the appropriate animator on all clients and server. This change is not automatically synchronized. @@ -846,6 +888,7 @@ private void CheckSendToClients() SendSegment(new(buffer, 0, bufferLength)); } + //Reset client auth buffer. _clientAuthoritativeUpdates.Reset(); } @@ -978,6 +1021,7 @@ private bool AnimatorUpdated(out ArraySegment updatedBytes, bool forceAll _writer.WriteUInt8Unpacked(_triggerUpdates[i].ParameterIndex); _writer.WriteBoolean(_triggerUpdates[i].Setting); } + _triggerUpdates.Clear(); /* States. */ @@ -1069,7 +1113,7 @@ private bool AnimatorUpdated(out ArraySegment updatedBytes, bool forceAll //Nothing to update. if (_writer.Position == 0) return false; - + updatedBytes = _writer.GetArraySegment(); return true; } @@ -1225,6 +1269,7 @@ private bool ReturnCurrentLayerState(out int stateHash, out float normalizedTime /// Immediately sends all variables and states of layers. /// This is a very bandwidth intensive operation. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SendAll() { _forceAllOnTimed = true; @@ -1234,6 +1279,7 @@ public void SendAll() /// /// Plays a state. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Play(string name) { Play(Animator.StringToHash(name)); @@ -1242,6 +1288,7 @@ public void Play(string name) /// /// Plays a state. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Play(int hash) { for (int i = 0; i < _animator.layerCount; i++) @@ -1251,6 +1298,7 @@ public void Play(int hash) /// /// Plays a state. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Play(string name, int layer) { Play(Animator.StringToHash(name), layer); @@ -1259,6 +1307,7 @@ public void Play(string name, int layer) /// /// Plays a state. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Play(int hash, int layer) { Play(hash, layer, 0f); @@ -1267,6 +1316,7 @@ public void Play(int hash, int layer) /// /// Plays a state. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Play(string name, int layer, float normalizedTime) { Play(Animator.StringToHash(name), layer, normalizedTime); @@ -1289,6 +1339,7 @@ public void Play(int hash, int layer, float normalizedTime) /// /// Plays a state. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PlayInFixedTime(string name, float fixedTime) { PlayInFixedTime(Animator.StringToHash(name), fixedTime); @@ -1297,6 +1348,7 @@ public void PlayInFixedTime(string name, float fixedTime) /// /// Plays a state. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PlayInFixedTime(int hash, float fixedTime) { for (int i = 0; i < _animator.layerCount; i++) @@ -1306,6 +1358,7 @@ public void PlayInFixedTime(int hash, float fixedTime) /// /// Plays a state. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PlayInFixedTime(string name, int layer, float fixedTime) { PlayInFixedTime(Animator.StringToHash(name), layer, fixedTime); @@ -1335,6 +1388,7 @@ public void PlayInFixedTime(int hash, int layer, float fixedTime) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CrossFade(string stateName, float normalizedTransitionDuration, int layer, float normalizedTimeOffset = float.NegativeInfinity, float normalizedTransitionTime = 0.0f) { CrossFade(Animator.StringToHash(stateName), normalizedTransitionDuration, layer, normalizedTimeOffset, normalizedTransitionTime); @@ -1367,6 +1421,7 @@ public void CrossFade(int hash, float normalizedTransitionDuration, int layer, f /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CrossFadeInFixedTime(string stateName, float fixedTransitionDuration, int layer, float fixedTimeOffset = 0.0f, float normalizedTransitionTime = 0.0f) { CrossFadeInFixedTime(Animator.StringToHash(stateName), fixedTransitionDuration, layer, fixedTimeOffset, normalizedTransitionTime); @@ -1397,6 +1452,7 @@ public void CrossFadeInFixedTime(int hash, float fixedTransitionDuration, int la /// Sets a trigger on the animator and sends it over the network. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetTrigger(int hash) { if (!_canSynchronizeAnimator) @@ -1408,6 +1464,7 @@ public void SetTrigger(int hash) /// Sets a trigger on the animator and sends it over the network. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetTrigger(string name) { SetTrigger(Animator.StringToHash(name)); @@ -1417,6 +1474,7 @@ public void SetTrigger(string name) /// Resets a trigger on the animator and sends it over the network. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetTrigger(int hash) { UpdateTrigger(hash, false); @@ -1426,6 +1484,7 @@ public void ResetTrigger(int hash) /// Resets a trigger on the animator and sends it over the network. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetTrigger(string name) { ResetTrigger(Animator.StringToHash(name)); diff --git a/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs b/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs index b44b8579..f9789010 100644 --- a/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs +++ b/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs @@ -12,9 +12,10 @@ using GameKit.Dependencies.Utilities; using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using FishNet.Managing.Timing; -using Unity.Profiling; using UnityEngine; +using Unity.Profiling; using UnityEngine.Scripting; using static FishNet.Object.NetworkObject; @@ -74,12 +75,14 @@ public void Update(ArraySegment data, Channel channel, bool updateHasData, /// /// Will cause this data to send on the reliable channel once even if data is unchanged. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SendReliably() { HasData = true; Channel = Channel.Reliable; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetState() { HasData = false; @@ -159,6 +162,7 @@ public class GoalData : IResettable [Preserve] public GoalData() { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetState() { ReceivedTick = 0; @@ -166,6 +170,7 @@ public void ResetState() Rates.ResetState(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void InitializeState() { } } @@ -200,6 +205,7 @@ public class RateData : IResettable [Preserve] public RateData() { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(RateData rd) { Update(rd.Position, rd.Rotation, rd.Scale, rd.LastUnalteredPositionRate, rd.TickSpan, rd.TimeRemaining); @@ -208,6 +214,7 @@ public void Update(RateData rd) /// /// Updates rates. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale, float unalteredPositionRate, uint tickSpan, float timeRemaining) { Position = position; @@ -218,6 +225,7 @@ public void Update(float position, float rotation, float scale, float unalteredP TimeRemaining = timeRemaining; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetState() { Position = 0f; @@ -228,6 +236,7 @@ public void ResetState() TimeRemaining = 0f; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void InitializeState() { } } @@ -282,13 +291,16 @@ public enum ExtrapolateState : byte [Preserve] public TransformData() { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetIsDefaultToFalse() => IsDefault = false; - + + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Update(TransformData copy) { Update(copy.Tick, copy.Position, copy.Rotation, copy.Scale, copy.ExtrapolatedPosition, copy.ParentBehaviour); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Update(uint tick, Vector3 position, Quaternion rotation, Vector3 scale, Vector3 extrapolatedPosition, NetworkBehaviour parentBehaviour) { IsDefault = false; @@ -300,6 +312,7 @@ internal void Update(uint tick, Vector3 position, Quaternion rotation, Vector3 s ParentBehaviour = parentBehaviour; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetState() { IsDefault = true; @@ -313,6 +326,7 @@ public void ResetState() ParentBehaviour = null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void InitializeState() { } } #endregion @@ -536,6 +550,23 @@ public void SetSendToOwner(bool value) #endregion #region Private. + + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("NetworkTransform.TimeManager_OnUpdate()"); + private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("NetworkTransform.TimeManager_OnPostTick()"); + private static readonly ProfilerMarker _pm_MoveToTarget = new ProfilerMarker("NetworkTransform.MoveToTarget(float)"); + private static readonly ProfilerMarker _pm_UpdateTransformData = new ProfilerMarker("NetworkTransform.UpdateTransformData(ArraySegment, TransformData, TransformData, ref ChangedFull)"); + private static readonly ProfilerMarker _pm_ForceSend0 = new ProfilerMarker("NetworkTransform.ForceSend()"); + private static readonly ProfilerMarker _pm_ForceSend1 = new ProfilerMarker("NetworkTransform.ForceSend(uint)"); + private static readonly ProfilerMarker _pm_SendToClients = new ProfilerMarker("NetworkTransform.SendToClients()"); + private static readonly ProfilerMarker _pm_SendToServer = new ProfilerMarker("NetworkTransform.SendToServer(TransformData)"); + private static readonly ProfilerMarker _pm_GetChanged = new ProfilerMarker("NetworkTransform.GetChanged(Vector3, Quaternion, Vector3, NetworkBehaviour)"); + private static readonly ProfilerMarker _pm_SerializeChanged = new ProfilerMarker("NetworkTransform.SerializeChanged(ChangedDelta, PooledWriter, TransformData)"); + private static readonly ProfilerMarker _pm_DataReceived = new ProfilerMarker("NetworkTransform.DataReceived(ArraySegment, Channel, bool)"); + + #endregion + /// /// Packing data with all values set to uncompressed. /// @@ -647,17 +678,6 @@ public void SetSendToOwner(bool value) private TimeManager _timeManager; #endregion - #region Private Profiler Markers - private static readonly ProfilerMarker _pm_OnUpdate = new("NetworkTransform.TimeManager_OnUpdate()"); - private static readonly ProfilerMarker _pm_OnPostTick = new("NetworkTransform.TimeManager_OnPostTick()"); - private static readonly ProfilerMarker _pm_MoveToTarget = new("NetworkTransform.MoveToTarget(float)"); - private static readonly ProfilerMarker _pm_UpdateTransformData = new("NetworkTransform.UpdateTransformData(ArraySegment, TransformData, TransformData, ref ChangedFull)"); - private static readonly ProfilerMarker _pm_ForceSend0 = new("NetworkTransform.ForceSend()"); - private static readonly ProfilerMarker _pm_ForceSend1 = new("NetworkTransform.ForceSend(uint)"); - private static readonly ProfilerMarker _pm_SendToClients = new("NetworkTransform.SendToClients()"); - private static readonly ProfilerMarker _pm_SendToServer = new("NetworkTransform.SendToServer(TransformData)"); - #endregion - #region Const. /// /// Maximum possible interpolation value. @@ -1029,6 +1049,7 @@ public void SetExtrapolation(ushort value) /// Returns if controlling logic can be run. This may be the server when there is no owner, even if client authoritative, and more. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool CanControl() { //Client auth. @@ -1047,6 +1068,7 @@ private bool CanControl() /// /// When called by the controller of this object the next changed data will be teleported to by spectators. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Teleport() { if (CanControl()) @@ -1066,6 +1088,7 @@ private void ObserversSetSendToOwner(bool value) /// /// Resets last sent information to force a resend of current values after a number of ticks. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ForceSend(uint ticks) { using (_pm_ForceSend1.Auto()) @@ -1081,6 +1104,7 @@ public void ForceSend(uint ticks) /// /// Resets last sent information to force a resend of current values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ForceSend() { using (_pm_ForceSend0.Auto()) @@ -1112,6 +1136,7 @@ public void SetInterval(byte value) /// Updates the interval value. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetIntervalInternal(byte value) { value = (byte)Mathf.Max(value, 1); @@ -1168,17 +1193,19 @@ private void SetDefaultGoalData() } _teleport = false; - SetLastReceived(_lastReceivedServerTransformData); - SetLastReceived(_lastReceivedClientTransformData); + SetLastReceived(t, _lastReceivedServerTransformData, parentBehaviour); + SetLastReceived(t, _lastReceivedClientTransformData, parentBehaviour); //SetInstantRates(_currentGoalData.Rates, 0, -1f); - void SetLastReceived(TransformData td) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void SetLastReceived(Transform t, TransformData td, NetworkBehaviour parentBehaviour) { //Could be null if not initialized due to server or client side not being used. if (td == null) return; - td.Update(0, t.localPosition, t.localRotation, t.localScale, t.localPosition, parentBehaviour); + t.GetLocalPositionAndRotation(out var localPosition, out var localRotation); + td.Update(0, localPosition, localRotation, t.localScale, localPosition, parentBehaviour); } } @@ -1195,220 +1222,226 @@ private void LogInvalidParent() /// private void SerializeChanged(ChangedDelta changed, PooledWriter writer, TransformData dataToUpdate = null) { - bool canUpdateData = dataToUpdate != null; - if (canUpdateData && changed != ChangedDelta.Unset) - dataToUpdate.SetIsDefaultToFalse(); - - UpdateFlagA flagsA = UpdateFlagA.Unset; - UpdateFlagB flagsB = UpdateFlagB.Unset; - /* Do not use compression when childed. Depending - * on the scale of the parent compression may - * not be accurate enough. */ - TransformPackingData packing = ChangedContains(changed, ChangedDelta.Nested) ? _unpacked : _packing; - - int startIndexA = writer.Position; - writer.Skip(1); - //Original axis value. - float original; - //Compressed axis value. - float compressed; - //Multiplier for compression. - float multiplier = 100f; - /* Maximum value compressed may be - * to send as compressed. */ - float maxValue = short.MaxValue - 1; - - Transform t = _cachedTransform; - /* Position. */ - if (_synchronizePosition) + using (_pm_SerializeChanged.Auto()) { - AutoPackType localPacking = packing.Position; - //PositionX - if (ChangedContains(changed, ChangedDelta.PositionX)) - { - original = t.localPosition.x; - - if (canUpdateData) - dataToUpdate.Position.x = original; + bool canUpdateData = dataToUpdate != null; + if (canUpdateData && changed != ChangedDelta.Unset) + dataToUpdate.SetIsDefaultToFalse(); - compressed = original * multiplier; - if (localPacking != AutoPackType.Unpacked && Math.Abs(compressed) <= maxValue) - { - flagsA |= UpdateFlagA.X2; - writer.WriteInt16((short)compressed); - } - else - { - flagsA |= UpdateFlagA.X4; - writer.WriteSingle(original); - } - } + UpdateFlagA flagsA = UpdateFlagA.Unset; + UpdateFlagB flagsB = UpdateFlagB.Unset; + /* Do not use compression when childed. Depending + * on the scale of the parent compression may + * not be accurate enough. */ + TransformPackingData packing = ChangedContains(changed, ChangedDelta.Nested) ? _unpacked : _packing; - //PositionY - if (ChangedContains(changed, ChangedDelta.PositionY)) - { - original = t.localPosition.y; - - if (canUpdateData) - dataToUpdate.Position.y = original; - - compressed = original * multiplier; - if (localPacking != AutoPackType.Unpacked && Math.Abs(compressed) <= maxValue) - { - flagsA |= UpdateFlagA.Y2; - writer.WriteInt16((short)compressed); - } - else - { - flagsA |= UpdateFlagA.Y4; - writer.WriteSingle(original); - } - } - - //PositionZ - if (ChangedContains(changed, ChangedDelta.PositionZ)) - { - original = t.localPosition.z; - - if (canUpdateData) - dataToUpdate.Position.z = original; - - compressed = original * multiplier; - if (localPacking != AutoPackType.Unpacked && Math.Abs(compressed) <= maxValue) - { - flagsA |= UpdateFlagA.Z2; - writer.WriteInt16((short)compressed); - } - else - { - flagsA |= UpdateFlagA.Z4; - writer.WriteSingle(original); - } - } - } - - /* Rotation. */ - if (_synchronizeRotation) - { - if (ChangedContains(changed, ChangedDelta.Rotation)) - { - if (canUpdateData) - dataToUpdate.Rotation = t.localRotation; - - flagsA |= UpdateFlagA.Rotation; - /* Rotation can always use pack settings even - * if childed. Unsual transform scale shouldn't affect rotation. */ - writer.WriteQuaternion(t.localRotation, _packing.Rotation); - } - } - - /* If there is a teleport pending then apply - * extended flag since thats where teleport resides. */ - bool teleport = _teleport; - if (teleport) - changed |= ChangedDelta.Extended; - - if (ChangedContains(changed, ChangedDelta.Extended)) - { - AutoPackType localPacking = packing.Scale; - flagsA |= UpdateFlagA.Extended; - int startIndexB = writer.Position; + int startIndexA = writer.Position; writer.Skip(1); + //Original axis value. + float original; + //Compressed axis value. + float compressed; + //Multiplier for compression. + float multiplier = 100f; + /* Maximum value compressed may be + * to send as compressed. */ + float maxValue = short.MaxValue - 1; - /* Redundant to do the teleport check here since it was done - * just above, but for code consistency the teleport updateflag - * is set within this conditional with rest of the extended - * data. */ - if (teleport) - { - flagsB |= UpdateFlagB.Teleport; - _teleport = false; - } - - /* Scale. */ - if (_synchronizeScale) + Transform t = _cachedTransform; + t.GetLocalPositionAndRotation(out Vector3 localPosition, out Quaternion localRotation); + Vector3 localScale = t.localScale; + + /* Position. */ + if (_synchronizePosition) { - //ScaleX - if (ChangedContains(changed, ChangedDelta.ScaleX)) + AutoPackType localPacking = packing.Position; + //PositionX + if (ChangedContains(changed, ChangedDelta.PositionX)) { - original = t.localScale.x; + original = localPosition.x; if (canUpdateData) - dataToUpdate.Scale.x = original; + dataToUpdate.Position.x = original; compressed = original * multiplier; if (localPacking != AutoPackType.Unpacked && Math.Abs(compressed) <= maxValue) { - flagsB |= UpdateFlagB.X2; + flagsA |= UpdateFlagA.X2; writer.WriteInt16((short)compressed); } else { - flagsB |= UpdateFlagB.X4; + flagsA |= UpdateFlagA.X4; writer.WriteSingle(original); } } - //ScaleY - if (ChangedContains(changed, ChangedDelta.ScaleY)) + //PositionY + if (ChangedContains(changed, ChangedDelta.PositionY)) { - original = t.localScale.y; + original = localPosition.y; if (canUpdateData) - dataToUpdate.Scale.y = original; + dataToUpdate.Position.y = original; compressed = original * multiplier; if (localPacking != AutoPackType.Unpacked && Math.Abs(compressed) <= maxValue) { - flagsB |= UpdateFlagB.Y2; + flagsA |= UpdateFlagA.Y2; writer.WriteInt16((short)compressed); } else { - flagsB |= UpdateFlagB.Y4; + flagsA |= UpdateFlagA.Y4; writer.WriteSingle(original); } } - //ScaleZ - if (ChangedContains(changed, ChangedDelta.ScaleZ)) + //PositionZ + if (ChangedContains(changed, ChangedDelta.PositionZ)) { - original = t.localScale.z; + original = localPosition.z; if (canUpdateData) - dataToUpdate.Scale.z = original; + dataToUpdate.Position.z = original; compressed = original * multiplier; if (localPacking != AutoPackType.Unpacked && Math.Abs(compressed) <= maxValue) { - flagsB |= UpdateFlagB.Z2; + flagsA |= UpdateFlagA.Z2; writer.WriteInt16((short)compressed); } else { - flagsB |= UpdateFlagB.Z4; + flagsA |= UpdateFlagA.Z4; writer.WriteSingle(original); } } } - //Childed. - if (ChangedContains(changed, ChangedDelta.Nested) && ParentBehaviour != null) + /* Rotation. */ + if (_synchronizeRotation) { - if (canUpdateData) - dataToUpdate.ParentBehaviour = ParentBehaviour; + if (ChangedContains(changed, ChangedDelta.Rotation)) + { + if (canUpdateData) + dataToUpdate.Rotation = localRotation; - flagsB |= UpdateFlagB.Child; - writer.WriteNetworkBehaviour(ParentBehaviour); + flagsA |= UpdateFlagA.Rotation; + /* Rotation can always use pack settings even + * if childed. Unsual transform scale shouldn't affect rotation. */ + writer.WriteQuaternion(localRotation, _packing.Rotation); + } } - writer.InsertUInt8Unpacked((byte)flagsB, startIndexB); - } + /* If there is a teleport pending then apply + * extended flag since thats where teleport resides. */ + bool teleport = _teleport; + if (teleport) + changed |= ChangedDelta.Extended; + + if (ChangedContains(changed, ChangedDelta.Extended)) + { + AutoPackType localPacking = packing.Scale; + flagsA |= UpdateFlagA.Extended; + int startIndexB = writer.Position; + writer.Skip(1); + + /* Redundant to do the teleport check here since it was done + * just above, but for code consistency the teleport updateflag + * is set within this conditional with rest of the extended + * data. */ + if (teleport) + { + flagsB |= UpdateFlagB.Teleport; + _teleport = false; + } + + /* Scale. */ + if (_synchronizeScale) + { + //ScaleX + if (ChangedContains(changed, ChangedDelta.ScaleX)) + { + original = localScale.x; + + if (canUpdateData) + dataToUpdate.Scale.x = original; + + compressed = original * multiplier; + if (localPacking != AutoPackType.Unpacked && Math.Abs(compressed) <= maxValue) + { + flagsB |= UpdateFlagB.X2; + writer.WriteInt16((short)compressed); + } + else + { + flagsB |= UpdateFlagB.X4; + writer.WriteSingle(original); + } + } + + //ScaleY + if (ChangedContains(changed, ChangedDelta.ScaleY)) + { + original = localScale.y; - //Insert flags. - writer.InsertUInt8Unpacked((byte)flagsA, startIndexA); + if (canUpdateData) + dataToUpdate.Scale.y = original; - bool ChangedContains(ChangedDelta whole, ChangedDelta part) + compressed = original * multiplier; + if (localPacking != AutoPackType.Unpacked && Math.Abs(compressed) <= maxValue) + { + flagsB |= UpdateFlagB.Y2; + writer.WriteInt16((short)compressed); + } + else + { + flagsB |= UpdateFlagB.Y4; + writer.WriteSingle(original); + } + } + + //ScaleZ + if (ChangedContains(changed, ChangedDelta.ScaleZ)) + { + original = localScale.z; + + if (canUpdateData) + dataToUpdate.Scale.z = original; + + compressed = original * multiplier; + if (localPacking != AutoPackType.Unpacked && Math.Abs(compressed) <= maxValue) + { + flagsB |= UpdateFlagB.Z2; + writer.WriteInt16((short)compressed); + } + else + { + flagsB |= UpdateFlagB.Z4; + writer.WriteSingle(original); + } + } + } + + //Childed. + if (ChangedContains(changed, ChangedDelta.Nested) && ParentBehaviour != null) + { + if (canUpdateData) + dataToUpdate.ParentBehaviour = ParentBehaviour; + + flagsB |= UpdateFlagB.Child; + writer.WriteNetworkBehaviour(ParentBehaviour); + } + + writer.InsertUInt8Unpacked((byte)flagsB, startIndexB); + } + + //Insert flags. + writer.InsertUInt8Unpacked((byte)flagsA, startIndexA); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool ChangedContains(ChangedDelta whole, ChangedDelta part) { return (whole & part) == part; } @@ -1504,29 +1537,32 @@ private void DeserializePacket(ArraySegment data, TransformData prevTransf } else { - Unnest(); + Unnest(nextTransformData); } } //No extended settings. else { nextTransformData.Scale = prevTransformData.Scale; - Unnest(); + Unnest(nextTransformData); } - void Unnest() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Unnest(TransformData nextTransformData) { nextTransformData.ParentBehaviour = null; } //Returns if whole contains part. - bool UpdateFlagAContains(UpdateFlagA whole, UpdateFlagA part) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool UpdateFlagAContains(UpdateFlagA whole, UpdateFlagA part) { return (whole & part) == part; } //Returns if whole contains part. - bool UpdateFlagBContains(UpdateFlagB whole, UpdateFlagB part) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool UpdateFlagBContains(UpdateFlagB whole, UpdateFlagB part) { return (whole & part) == part; } @@ -1647,6 +1683,8 @@ private void MoveToTarget(float delta) //Rate to update. Changes per property. float rate; Transform t = _cachedTransform; + t.GetLocalPositionAndRotation(out Vector3 localPosition, out Quaternion localRotation); + Vector3 localScale = t.localScale; //Snap any bits of the transform that should be. SnapProperties(td); @@ -1655,12 +1693,15 @@ private void MoveToTarget(float delta) if (_synchronizePosition) { rate = rd.Position; - Vector3 posGoal = td.ExtrapolationState == TransformData.ExtrapolateState.Active && !_lastReceiveReliable ? td.ExtrapolatedPosition : td.Position; + Vector3 posGoal = + td.ExtrapolationState == TransformData.ExtrapolateState.Active && !_lastReceiveReliable + ? td.ExtrapolatedPosition + : td.Position; // ReSharper disable once CompareOfFloatsByEqualityOperator if (rate == -1f) t.localPosition = td.Position; else - t.localPosition = Vector3.MoveTowards(t.localPosition, posGoal, rate * delta * multiplier); + t.localPosition = Vector3.MoveTowards(localPosition, posGoal, rate * delta * multiplier); } //Rotation. @@ -1671,7 +1712,7 @@ private void MoveToTarget(float delta) if (rate == -1f) t.localRotation = td.Rotation; else - t.localRotation = Quaternion.RotateTowards(t.localRotation, td.Rotation, rate * delta); + t.localRotation = Quaternion.RotateTowards(localRotation, td.Rotation, rate * delta); } //Scale. @@ -1682,7 +1723,7 @@ private void MoveToTarget(float delta) if (rate == -1f) t.localScale = td.Scale; else - t.localScale = Vector3.MoveTowards(t.localScale, td.Scale, rate * delta); + t.localScale = Vector3.MoveTowards(localScale, td.Scale, rate * delta); } float timeRemaining = rd.TimeRemaining - delta * multiplier; @@ -1703,7 +1744,7 @@ private void MoveToTarget(float delta) //No more in buffer, see if can extrapolate. else { - /* If everything matches up then end queue. + /* If everything matches up then end queue. * Otherwise let it play out until stuff * aligns. Generally the time remaining is enough * but every once in awhile something goes funky @@ -1711,7 +1752,7 @@ private void MoveToTarget(float delta) if (!HasChanged(td)) _currentGoalData = null; OnInterpolationComplete?.Invoke(); - } + } } } } @@ -1763,7 +1804,8 @@ private void SendToClients() * 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.Writer != null) + if (!_authoritativeClientData.HasData && _authoritativeClientData.Channel != Channel.Reliable && + _authoritativeClientData.Writer != null) { /* If ticks have passed beyond interpolation then force * to send reliably. */ @@ -1780,7 +1822,8 @@ private void SendToClients() { _changedSinceStart = true; //Resend data from clients. - ObserversUpdateClientAuthoritativeTransform(_authoritativeClientData.Writer.GetArraySegment(), _authoritativeClientData.Channel); + ObserversUpdateClientAuthoritativeTransform(_authoritativeClientData.Writer.GetArraySegment(), + _authoritativeClientData.Channel); //Now being sent data can unset. _authoritativeClientData.HasData = false; } @@ -1819,7 +1862,8 @@ private void SendToClients() /* If here a send for transform values will occur. Update last values. * Tick doesn't need to be set for whoever controls transform. */ //Transform t = _cachedTransform; - //lastSentData.Update(0, t.localPosition, t.localRotation, t.localScale, t.localPosition, ParentBehaviour); + //t.GetLocalPositionAndRotation(out var localPosition, out var localRotation); + //lastSentData.Update(0, localPosition, localRotation, t.localScale, localPosition, ParentBehaviour); lastSentData.Tick = 0; SerializeChanged(changed, writer, lastSentData); @@ -1876,7 +1920,8 @@ private void SendToServer(TransformData lastSentTransformData) * Tick doesn't need to be set for whoever controls transform. */ Transform t = _cachedTransform; - //lastSentData.Update(0, t.localPosition, t.localRotation, t.localScale, t.localPosition, ParentBehaviour); + //t.GetLocalPositionAndRotation(out var localPosition, out var localRotation); + //lastSentData.Update(0, localPosition, localRotation, t.localScale, localPosition, ParentBehaviour); lastSentTransformData.Tick = 0; //Send latest. @@ -1893,10 +1938,12 @@ private void SendToServer(TransformData lastSentTransformData) /// /// Returns if the transform differs from td. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool HasChanged(TransformData td) { Transform t = _cachedTransform; - bool changed = td.Position != t.localPosition || td.Rotation != t.localRotation || td.Scale != t.localScale; + t.GetLocalPositionAndRotation(out var localPosition, out var localRotation); + bool changed = td.Position != localPosition || td.Rotation != localRotation || td.Scale != t.localScale; return changed; } @@ -1904,6 +1951,7 @@ private bool HasChanged(TransformData td) /// /// Returns if there is any change between two datas. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool HasChanged(TransformData a, TransformData b) { return a.Position != b.Position || a.Rotation != b.Rotation || a.Scale != b.Scale || a.ParentBehaviour != b.ParentBehaviour; @@ -1941,6 +1989,7 @@ private bool HasChanged(TransformData a, TransformData b) /// /// Gets transform values that have changed against goalData. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private ChangedDelta GetChanged(TransformData transformData) { //If default return full changed. @@ -1960,39 +2009,41 @@ private ChangedDelta GetChanged(TransformData transformData) /// private ChangedDelta GetChanged(Vector3 lastPosition, Quaternion lastRotation, Vector3 lastScale, NetworkBehaviour lastParentBehaviour) { - ChangedDelta changed = ChangedDelta.Unset; - Transform t = _cachedTransform; + using (_pm_GetChanged.Auto()) + { + ChangedDelta changed = ChangedDelta.Unset; + Transform t = _cachedTransform; + t.GetLocalPositionAndRotation(out Vector3 localPosition, out Quaternion localRotation); - Vector3 position = t.localPosition; - if (Mathf.Abs(position.x - lastPosition.x) >= _positionSensitivity) - changed |= ChangedDelta.PositionX; - if (Mathf.Abs(position.y - lastPosition.y) >= _positionSensitivity) - changed |= ChangedDelta.PositionY; - if (Mathf.Abs(position.z - lastPosition.z) >= _positionSensitivity) - changed |= ChangedDelta.PositionZ; + if (Mathf.Abs(localPosition.x - lastPosition.x) >= _positionSensitivity) + changed |= ChangedDelta.PositionX; + if (Mathf.Abs(localPosition.y - lastPosition.y) >= _positionSensitivity) + changed |= ChangedDelta.PositionY; + if (Mathf.Abs(localPosition.z - lastPosition.z) >= _positionSensitivity) + changed |= ChangedDelta.PositionZ; - Quaternion rotation = t.localRotation; - if (!rotation.Matches(lastRotation, true)) - changed |= ChangedDelta.Rotation; + if (!localRotation.Matches(lastRotation, true)) + changed |= ChangedDelta.Rotation; - ChangedDelta startChanged = changed; + ChangedDelta startChanged = changed; - Vector3 scale = t.localScale; - if (Mathf.Abs(scale.x - lastScale.x) >= _scaleSensitivity) - changed |= ChangedDelta.ScaleX; - if (Mathf.Abs(scale.y - lastScale.y) >= _scaleSensitivity) - changed |= ChangedDelta.ScaleY; - if (Mathf.Abs(scale.z - lastScale.z) >= _scaleSensitivity) - changed |= ChangedDelta.ScaleZ; + Vector3 scale = t.localScale; + if (Mathf.Abs(scale.x - lastScale.x) >= _scaleSensitivity) + changed |= ChangedDelta.ScaleX; + if (Mathf.Abs(scale.y - lastScale.y) >= _scaleSensitivity) + changed |= ChangedDelta.ScaleY; + if (Mathf.Abs(scale.z - lastScale.z) >= _scaleSensitivity) + changed |= ChangedDelta.ScaleZ; - if (changed != ChangedDelta.Unset && ParentBehaviour != null) - changed |= ChangedDelta.Nested; + if (changed != ChangedDelta.Unset && ParentBehaviour != null) + changed |= ChangedDelta.Nested; - //If added scale or childed then also add extended. - if (startChanged != changed) - changed |= ChangedDelta.Extended; + //If added scale or childed then also add extended. + if (startChanged != changed) + changed |= ChangedDelta.Extended; - return changed; + return changed; + } } #endregion @@ -2008,36 +2059,38 @@ private void SnapProperties(TransformData transformData, bool force = false) transformData.SnappingChecked = true; Transform t = _cachedTransform; - + t.GetLocalPositionAndRotation(out Vector3 startPosition, out Quaternion startRotation); + //Position. if (_synchronizePosition) { - Vector3 startPosition = t.localPosition; Vector3 position; - position.x = force || _positionSnapping.X ? transformData.Position.x : t.localPosition.x; - position.y = force || _positionSnapping.Y ? transformData.Position.y : t.localPosition.y; - position.z = force || _positionSnapping.Z ? transformData.Position.z : t.localPosition.z; + position.x = force || _positionSnapping.X ? transformData.Position.x : startPosition.x; + position.y = force || _positionSnapping.Y ? transformData.Position.y : startPosition.y; + position.z = force || _positionSnapping.Z ? transformData.Position.z : startPosition.z; t.localPosition = position; } //Rotation. if (_synchronizeRotation) { - Vector3 eulers; + Vector3 startEulers = startRotation.eulerAngles; Vector3 goalEulers = transformData.Rotation.eulerAngles; - eulers.x = force || _rotationSnapping.X ? goalEulers.x : t.localEulerAngles.x; - eulers.y = force || _rotationSnapping.Y ? goalEulers.y : t.localEulerAngles.y; - eulers.z = force || _rotationSnapping.Z ? goalEulers.z : t.localEulerAngles.z; + Vector3 eulers; + eulers.x = force || _rotationSnapping.X ? goalEulers.x : startEulers.x; + eulers.y = force || _rotationSnapping.Y ? goalEulers.y : startEulers.y; + eulers.z = force || _rotationSnapping.Z ? goalEulers.z : startEulers.z; t.localEulerAngles = eulers; } //Scale. if (_synchronizeScale) { + var startScale = t.localScale; Vector3 scale; - scale.x = force || _scaleSnapping.X ? transformData.Scale.x : t.localScale.x; - scale.y = force || _scaleSnapping.Y ? transformData.Scale.y : t.localScale.y; - scale.z = force || _scaleSnapping.Z ? transformData.Scale.z : t.localScale.z; + scale.x = force || _scaleSnapping.X ? transformData.Scale.x : startScale.x; + scale.y = force || _scaleSnapping.Y ? transformData.Scale.y : startScale.y; + scale.z = force || _scaleSnapping.Z ? transformData.Scale.z : startScale.z; t.localScale = scale; } } @@ -2045,6 +2098,7 @@ private void SnapProperties(TransformData transformData, bool force = false) /// /// Sets move rates which will occur instantly. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetInstantRates(RateData rd, uint tickDifference, float timeRemaining) { //Was default to 1 tickDiff and -1 time remaining. @@ -2207,7 +2261,8 @@ 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) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool ChangedFullContains(ChangedFull whole, ChangedFull part) { return (whole & part) == part; } @@ -2216,7 +2271,8 @@ bool ChangedFullContains(ChangedFull whole, ChangedFull part) * This is used to decide if a property should be teleported. * When distances are exceptionally small smoothing rate * calculations may result as an invalid value. */ - bool LowDistance(float dist, bool rotation) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool LowDistance(float dist, bool rotation) { if (rotation) return dist < 1f; @@ -2228,6 +2284,7 @@ bool LowDistance(float dist, bool rotation) /// /// Gets the tick difference between two GoalDatas. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private uint GetTickDifference(TransformData prevTd, GoalData nextGd, uint minimum, out float timePassed) { TransformData nextTd = nextGd.Transforms; @@ -2251,12 +2308,12 @@ private uint GetTickDifference(TransformData prevTd, GoalData nextGd, uint minim /// /// Sets extrapolation data on next. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetExtrapolatedData(TransformData prev, TransformData next, Channel channel) { //Default value. next.ExtrapolationState = TransformData.ExtrapolateState.Disabled; - - } + } /// /// Updates a client with transform data. @@ -2323,108 +2380,112 @@ private void ServerUpdateTransform(ArraySegment data, Channel channel) } /// - /// Processes received data for lcients and server. + /// Processes received data for clients and server. /// private void DataReceived(ArraySegment data, Channel channel, bool asServer) { - if (IsDeinitializing) - return; - - TransformData prevTd = asServer ? _lastReceivedClientTransformData : _lastReceivedServerTransformData; - RateData prevRd = _lastCalculatedRateData; + using (_pm_DataReceived.Auto()) + { + if (IsDeinitializing) + return; - ChangedFull changedFull = ChangedFull.Unset; - GoalData nextGd = ResettableObjectCaches.Retrieve(); - TransformData nextTd = nextGd.Transforms; - UpdateTransformData(data, prevTd, nextTd, ref changedFull); + TransformData prevTd = asServer ? _lastReceivedClientTransformData : _lastReceivedServerTransformData; + RateData prevRd = _lastCalculatedRateData; - OnDataReceived?.Invoke(prevTd, nextTd); - SetExtrapolatedData(prevTd, nextTd, channel); + ChangedFull changedFull = ChangedFull.Unset; + GoalData nextGd = ResettableObjectCaches.Retrieve(); + TransformData nextTd = nextGd.Transforms; + UpdateTransformData(data, prevTd, nextTd, ref changedFull); - bool hasChanged = HasChanged(prevTd, nextTd); + OnDataReceived?.Invoke(prevTd, nextTd); + SetExtrapolatedData(prevTd, nextTd, channel); - //If server only teleport. - if (asServer && !IsClientStarted) - { - uint tickDifference = GetTickDifference(prevTd, nextGd, 1, out float timePassed); - SetInstantRates(nextGd.Rates, tickDifference, timePassed); - } - //Otherwise use timed. - else - { - SetCalculatedRates(prevTd, prevRd, nextGd, changedFull, hasChanged, channel); - } + bool hasChanged = HasChanged(prevTd, nextTd); - _lastReceiveReliable = channel == Channel.Reliable; - /* If channel is reliable then this is a settled packet. - * Set tick to UNSET. When this occurs time calculations - * assume only 1 tick has passed. */ - if (channel == Channel.Reliable) - nextTd.Tick = TimeManager.UNSET_TICK; - - prevTd.Update(nextTd); - prevRd.Update(nextGd.Rates); - - nextGd.ReceivedTick = _timeManager.LocalTick; - - bool currentDataNull = _currentGoalData == null; - /* If extrapolating then immediately break the extrapolation - * in favor of newest results. This will keep the buffer - * at 0 until the transform settles but the only other option is - * to stop the movement, which would defeat purpose of extrapolation, - * or slow down the transform while buffer rebuilds. Neither choice - * is great but later on I might try slowing down the transform slightly - * to give the buffer a chance to rebuild. */ - if (!currentDataNull && _currentGoalData.Transforms.ExtrapolationState == TransformData.ExtrapolateState.Active) - { - SetCurrentGoalData(nextGd); - } - /* If queue isn't started and its buffered enough - * to satisfy interpolation then set ready - * and set current data. - * - * Also if reliable then begin moving. */ - else if ((currentDataNull && _goalDataQueue.Count >= _interpolation) || channel == Channel.Reliable) - { - if (_goalDataQueue.Count > 0) + //If server only teleport. + if (asServer && !IsClientStarted) { - SetCurrentGoalData(_goalDataQueue.Dequeue()); - /* If is reliable and has changed then also - * enqueue latest. */ - if (hasChanged) - _goalDataQueue.Enqueue(nextGd); + uint tickDifference = GetTickDifference(prevTd, nextGd, 1, out float timePassed); + SetInstantRates(nextGd.Rates, tickDifference, timePassed); } + //Otherwise use timed. else { - SetCurrentGoalData(nextGd); + SetCalculatedRates(prevTd, prevRd, nextGd, changedFull, hasChanged, channel); } - } - /* If here then there's not enough in buffer to begin - * so add onto the buffer. */ - else - { - _goalDataQueue.Enqueue(nextGd); - } - /* If the queue is excessive beyond interpolation then - * dequeue extras to prevent from dropping behind too - * quickly. This shouldn't be an issue with normal movement - * as the NT speeds up if the buffer unexpectedly grows, but - * when connections are unstable results may come in chunks - * and for a better experience the older parts of the chunks - * will be dropped. */ - if (_goalDataQueue.Count > _interpolation + 3) - { - while (_goalDataQueue.Count > _interpolation) + _lastReceiveReliable = channel == Channel.Reliable; + /* If channel is reliable then this is a settled packet. + * Set tick to UNSET. When this occurs time calculations + * assume only 1 tick has passed. */ + if (channel == Channel.Reliable) + nextTd.Tick = TimeManager.UNSET_TICK; + + prevTd.Update(nextTd); + prevRd.Update(nextGd.Rates); + + nextGd.ReceivedTick = _timeManager.LocalTick; + + bool currentDataNull = _currentGoalData == null; + /* If extrapolating then immediately break the extrapolation + * in favor of newest results. This will keep the buffer + * at 0 until the transform settles but the only other option is + * to stop the movement, which would defeat purpose of extrapolation, + * or slow down the transform while buffer rebuilds. Neither choice + * is great but later on I might try slowing down the transform slightly + * to give the buffer a chance to rebuild. */ + if (!currentDataNull && _currentGoalData.Transforms.ExtrapolationState == + TransformData.ExtrapolateState.Active) { - GoalData tmpGd = _goalDataQueue.Dequeue(); - ResettableObjectCaches.Store(tmpGd); + SetCurrentGoalData(nextGd); + } + /* If queue isn't started and its buffered enough + * to satisfy interpolation then set ready + * and set current data. + * + * Also if reliable then begin moving. */ + else if ((currentDataNull && _goalDataQueue.Count >= _interpolation) || channel == Channel.Reliable) + { + if (_goalDataQueue.Count > 0) + { + SetCurrentGoalData(_goalDataQueue.Dequeue()); + /* If is reliable and has changed then also + * enqueue latest. */ + if (hasChanged) + _goalDataQueue.Enqueue(nextGd); + } + else + { + SetCurrentGoalData(nextGd); + } + } + /* If here then there's not enough in buffer to begin + * so add onto the buffer. */ + else + { + _goalDataQueue.Enqueue(nextGd); } - //Snap to the next data to fix any smoothing timings. - SetCurrentGoalData(_goalDataQueue.Dequeue()); - SetInstantRates(_currentGoalData!.Rates, 1, -1f); - SnapProperties(_currentGoalData.Transforms, true); + /* If the queue is excessive beyond interpolation then + * dequeue extras to prevent from dropping behind too + * quickly. This shouldn't be an issue with normal movement + * as the NT speeds up if the buffer unexpectedly grows, but + * when connections are unstable results may come in chunks + * and for a better experience the older parts of the chunks + * will be dropped. */ + if (_goalDataQueue.Count > _interpolation + 3) + { + while (_goalDataQueue.Count > _interpolation) + { + GoalData tmpGd = _goalDataQueue.Dequeue(); + ResettableObjectCaches.Store(tmpGd); + } + + //Snap to the next data to fix any smoothing timings. + SetCurrentGoalData(_goalDataQueue.Dequeue()); + SetInstantRates(_currentGoalData!.Rates, 1, -1f); + SnapProperties(_currentGoalData.Transforms, true); + } } } diff --git a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs index 9b38e82d..7dbb8bc2 100644 --- a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs +++ b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs @@ -120,7 +120,6 @@ public void SetRemoteServerTimeout(RemoteTimeoutType timeoutType, ushort duratio [SerializeField] private ushort _frameRate = NetworkManager.MAXIMUM_FRAMERATE; - /// /// Sets the maximum frame rate the client may run at. Calling this method will enable ChangeFrameRate. /// /// New value. @@ -144,11 +143,39 @@ public void SetFrameRate(ushort value) private SplitReader _splitReader = new(); /// /// - private NetworkTrafficStatistics _networkTrafficStatistics; + [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; #endregion #region Private Profiler Markers private static readonly ProfilerMarker _pm_OnPostTick = new("ClientManager.TimeManager_OnPostTick()"); + private static readonly ProfilerMarker _pm_Transport_OnClientConnectionState = + new("ClientManager.Transport_OnClientConnectionState(ClientConnectionStateArgs)"); + private static readonly ProfilerMarker _pm_Transport_OnClientReceivedData = + new("ClientManager.Transport_OnClientReceivedData(ClientReceivedDataArgs)"); + private static readonly ProfilerMarker _pm_TransportManager_OnIterateIncomingEnd = + new("ClientManager.TransportManager_OnIterateIncomingEnd(bool)"); + private static readonly ProfilerMarker _pm_ParseReceived = + new("ClientManager.ParseReceived(ClientReceivedDataArgs)"); + private static readonly ProfilerMarker _pm_ParseReader = + new("ClientManager.ParseReader(PooledReader, Channel, bool)"); + private static readonly ProfilerMarker _pm_ParseReader_ReadPacketId = + new("ClientManager.ParseReader.ReadPacketId()"); + private static readonly ProfilerMarker _pm_ParseReader_HandlePacket = + new("ClientManager.ParseReader.HandlePacket()"); + private static readonly ProfilerMarker _pm_ParseReader_StateUpdate = + new("ClientManager.ParseReader.StateUpdate()"); + private static readonly ProfilerMarker _pm_ParseReader_Broadcast = + new("ClientManager.ParseReader.Broadcast()"); + private static readonly ProfilerMarker _pm_ParseReader_PingPong = + new("ClientManager.ParseReader.PingPong()"); + private static readonly ProfilerMarker _pm_ParseReader_TimingUpdate = + new("ClientManager.ParseReader.TimingUpdate()"); + private static readonly ProfilerMarker _pm_ParseReader_Authenticated = + new("ClientManager.ParseReader.Authenticated()"); + private static readonly ProfilerMarker _pm_ParseReader_Disconnect = + new("ClientManager.ParseReader.Disconnect()"); + private static readonly ProfilerMarker _pm_ParseReader_Version = + new("ClientManager.ParseReader.Version()"); #endregion private void OnDestroy() @@ -310,39 +337,42 @@ public bool StartConnection(string address, ushort port) /// private void Transport_OnClientConnectionState(ClientConnectionStateArgs args) { - LocalConnectionState state = args.ConnectionState; - Started = state == LocalConnectionState.Started; - Objects.OnClientConnectionState(args); - - // Clear connection after so objects can update using current Connection value. - if (!Started) - { - Connection = NetworkManager.EmptyConnection; - NetworkManager.ClearClientsCollection(Clients); - } - else + using (_pm_Transport_OnClientConnectionState.Auto()) { - _lastPacketTime = Time.unscaledTime; - // Send version. - PooledWriter writer = WriterPool.Retrieve(); - writer.WritePacketIdUnpacked(PacketId.Version); - writer.WriteString(NetworkManager.FISHNET_VERSION); - NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment()); - WriterPool.Store(writer); - } + LocalConnectionState state = args.ConnectionState; + Started = state == LocalConnectionState.Started; + Objects.OnClientConnectionState(args); - if (NetworkManager.CanLog(LoggingType.Common)) - { - Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex); - string tName = t == null ? "Unknown" : t.GetType().Name; - string socketInformation = string.Empty; - if (state == LocalConnectionState.Starting) - socketInformation = $" Server IP is {t.GetClientAddress()}, port is {t.GetPort()}."; - NetworkManager.Log($"Local client is {state.ToString().ToLower()} for {tName}.{socketInformation}"); - } + // Clear connection after so objects can update using current Connection value. + if (!Started) + { + Connection = NetworkManager.EmptyConnection; + NetworkManager.ClearClientsCollection(Clients); + } + else + { + _lastPacketTime = Time.unscaledTime; + // Send version. + PooledWriter writer = WriterPool.Retrieve(); + writer.WritePacketIdUnpacked(PacketId.Version); + writer.WriteString(NetworkManager.FISHNET_VERSION); + NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment()); + WriterPool.Store(writer); + } + + if (NetworkManager.CanLog(LoggingType.Common)) + { + Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex); + string tName = t == null ? "Unknown" : t.GetType().Name; + string socketInformation = string.Empty; + if (state == LocalConnectionState.Starting) + socketInformation = $" Server IP is {t.GetClientAddress()}, port is {t.GetPort()}."; + NetworkManager.Log($"Local client is {state.ToString().ToLower()} for {tName}.{socketInformation}"); + } - NetworkManager.UpdateFramerate(); - OnClientConnectionState?.Invoke(args); + NetworkManager.UpdateFramerate(); + OnClientConnectionState?.Invoke(args); + } } /// @@ -350,7 +380,10 @@ private void Transport_OnClientConnectionState(ClientConnectionStateArgs args) /// private void Transport_OnClientReceivedData(ClientReceivedDataArgs args) { - ParseReceived(args); + using (_pm_Transport_OnClientReceivedData.Auto()) + { + ParseReceived(args); + } } /// @@ -358,15 +391,18 @@ private void Transport_OnClientReceivedData(ClientReceivedDataArgs args) /// private void TransportManager_OnIterateIncomingEnd(bool server) { - /* Should the last packet received be a spawn or despawn - * then the cache won't yet be iterated because it only - * iterates when a packet is anything but those two. Because - * of such if any object caches did come in they must be iterated - * at the end of the incoming cycle. This isn't as clean as I'd - * like but it does ensure there will be no missing network object - * references on spawned objects. */ - if (Started && !server) - Objects.IterateObjectCache(); + using (_pm_TransportManager_OnIterateIncomingEnd.Auto()) + { + /* Should the last packet received be a spawn or despawn + * then the cache won't yet be iterated because it only + * iterates when a packet is anything but those two. Because + * of such if any object caches did come in they must be iterated + * at the end of the incoming cycle. This isn't as clean as I'd + * like but it does ensure there will be no missing network object + * references on spawned objects. */ + if (Started && !server) + Objects.IterateObjectCache(); + } } /// @@ -374,198 +410,231 @@ private void TransportManager_OnIterateIncomingEnd(bool server) /// private void ParseReceived(ClientReceivedDataArgs args) { - #if DEVELOPMENT && !UNITY_SERVER - if (_networkTrafficStatistics != null) - _networkTrafficStatistics.PacketBundleReceived(asServer: false); - #endif + using (_pm_ParseReceived.Auto()) + { + #if DEVELOPMENT && !UNITY_SERVER + if (_networkTrafficStatistics != null) + _networkTrafficStatistics.PacketBundleReceived(asServer: false); + #endif - _lastPacketTime = Time.unscaledTime; + _lastPacketTime = Time.unscaledTime; - ArraySegment segment; - if (NetworkManager.TransportManager.HasIntermediateLayer) - segment = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, true); - else - segment = args.Data; + ArraySegment segment; + if (NetworkManager.TransportManager.HasIntermediateLayer) + segment = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, true); + else + segment = args.Data; - if (_networkTrafficStatistics != null) - _networkTrafficStatistics.AddInboundSocketData((ulong)segment.Count, asServer: false); + if (_networkTrafficStatistics != null) + _networkTrafficStatistics.AddInboundSocketData((ulong)segment.Count, asServer: false); - if (segment.Count <= TransportManager.UNPACKED_TICK_LENGTH) - return; + if (segment.Count <= TransportManager.UNPACKED_TICK_LENGTH) + return; - PooledReader reader = ReaderPool.Retrieve(segment, NetworkManager, Reader.DataSource.Server); - TimeManager tm = NetworkManager.TimeManager; - tm.LastPacketTick.Update(reader.ReadTickUnpacked(), EstimatedTick.OldTickOption.Discard, false); - ParseReader(reader, args.Channel); - ReaderPool.Store(reader); + PooledReader reader = ReaderPool.Retrieve(segment, NetworkManager, Reader.DataSource.Server); + TimeManager tm = NetworkManager.TimeManager; + tm.LastPacketTick.Update(reader.ReadTickUnpacked(), EstimatedTick.OldTickOption.Discard, false); + ParseReader(reader, args.Channel); + ReaderPool.Store(reader); + } } internal void ParseReader(PooledReader reader, Channel channel, bool print = false) { - PacketId packetId = PacketId.Unset; - #if !DEVELOPMENT - try - { - #endif - Reader.DataSource dataSource = Reader.DataSource.Server; - /* This is a special condition where a message may arrive split. - * When this occurs buffer each packet until all packets are - * received. */ - if (reader.PeekPacketId() == PacketId.Split) + using (_pm_ParseReader.Auto()) { - #if DEVELOPMENT - NetworkManager.PacketIdHistory.ReceivedPacket(PacketId.Split, packetFromServer: true); - #endif - // Skip packetId. - reader.ReadPacketId(); - int expectedMessages; - _splitReader.GetHeader(reader, out expectedMessages); - _splitReader.Write(NetworkManager.TimeManager.LastPacketTick.LastRemoteTick, reader, expectedMessages); - /* If fullMessage returns 0 count then the split - * has not written fully yet. Otherwise, if there is - * data within then reinitialize reader with the - * full message. */ - ArraySegment fullMessage = _splitReader.GetFullMessage(); - if (fullMessage.Count == 0) - return; - - reader.Initialize(fullMessage, NetworkManager, dataSource); - } - - while (reader.Remaining > 0) - { - packetId = reader.ReadPacketId(); - #if DEVELOPMENT - NetworkManager.PacketIdHistory.ReceivedPacket(packetId, packetFromServer: true); - // if (!NetworkManager.IsServerStarted) - // print = true; - // if (print) - // { - // if (packetId == PacketId.ObserversRpc) - // Debug.Log($"PacketId {packetId} - Remaining {reader.Remaining}."); - // else - // Debug.LogWarning($"PacketId {packetId} - Remaining {reader.Remaining}."); - // } - // print = false; + PacketId packetId = PacketId.Unset; + #if !DEVELOPMENT + try + { #endif - bool spawnOrDespawn = packetId == PacketId.ObjectSpawn || packetId == PacketId.ObjectDespawn; - /* Length of data. Only available if using unreliable. Unreliable packets - * can arrive out of order which means object orientated messages such as RPCs may - * arrive after the object for which they target has already been destroyed. When this happens - * on lesser solutions they just dump the entire packet. However, since FishNet batches data. - * it's very likely a packet will contain more than one packetId. With this mind, length is - * sent as well so if any reason the data does have to be dumped it will only be dumped for - * that single packetId but not the rest. Broadcasts don't need length either even if unreliable - * because they are not object bound. */ - - // Is spawn or despawn; cache packet. - if (spawnOrDespawn) + Reader.DataSource dataSource = Reader.DataSource.Server; + /* This is a special condition where a message may arrive split. + * When this occurs buffer each packet until all packets are + * received. */ + if (reader.PeekPacketId() == PacketId.Split) { - if (packetId == PacketId.ObjectSpawn) - Objects.ReadSpawn(reader); - else if (packetId == PacketId.ObjectDespawn) - Objects.CacheDespawn(reader); + #if DEVELOPMENT + NetworkManager.PacketIdHistory.ReceivedPacket(PacketId.Split, packetFromServer: true); + #endif + // Skip packetId. + reader.ReadPacketId(); + int expectedMessages; + _splitReader.GetHeader(reader, out expectedMessages); + _splitReader.Write(NetworkManager.TimeManager.LastPacketTick.LastRemoteTick, reader, expectedMessages); + /* If fullMessage returns 0 count then the split + * has not written fully yet. Otherwise, if there is + * data within then reinitialize reader with the + * full message. */ + ArraySegment fullMessage = _splitReader.GetFullMessage(); + if (fullMessage.Count == 0) + return; + + reader.Initialize(fullMessage, NetworkManager, dataSource); } - // Not spawn or despawn. - else + + while (reader.Remaining > 0) { - /* Iterate object cache should any of the - * incoming packets rely on it. Objects - * in cache will always be received before any messages - * that use them. */ - Objects.IterateObjectCache(); - // Then process packet normally. - if ((ushort)packetId >= NetworkManager.StartingRpcLinkIndex) - { - Objects.ParseRpcLink(reader, (ushort)packetId, channel); - } - else if (packetId == PacketId.StateUpdate) - { - NetworkManager.PredictionManager.ParseStateUpdate(reader, channel); - } - else if (packetId == PacketId.Replicate) - { - Objects.ParseReplicateRpc(reader, null, channel); - } - else if (packetId == PacketId.Reconcile) - { - Objects.ParseReconcileRpc(reader, channel); - } - else if (packetId == PacketId.ObserversRpc) - { - Objects.ParseObserversRpc(reader, channel); - } - else if (packetId == PacketId.TargetRpc) - { - Objects.ParseTargetRpc(reader, channel); - } - else if (packetId == PacketId.Broadcast) - { - ParseBroadcast(reader, channel); - } - else if (packetId == PacketId.PingPong) - { - ParsePingPong(reader); - } - else if (packetId == PacketId.SyncType) - { - Objects.ParseSyncType(reader, channel); - } - else if (packetId == PacketId.PredictedSpawnResult) + using (_pm_ParseReader_ReadPacketId.Auto()) { - Objects.ParsePredictedSpawnResult(reader); - } - else if (packetId == PacketId.TimingUpdate) - { - NetworkManager.TimeManager.ParseTimingUpdate(reader); - } - else if (packetId == PacketId.OwnershipChange) - { - Objects.ParseOwnershipChange(reader); - } - else if (packetId == PacketId.Authenticated) - { - ParseAuthenticated(reader); - } - else if (packetId == PacketId.Disconnect) - { - reader.Clear(); - StopConnection(); + packetId = reader.ReadPacketId(); + #if DEVELOPMENT + NetworkManager.PacketIdHistory.ReceivedPacket(packetId, packetFromServer: true); + // if (!NetworkManager.IsServerStarted) + // print = true; + // if (print) + // { + // if (packetId == PacketId.ObserversRpc) + // Debug.Log($"PacketId {packetId} - Remaining {reader.Remaining}."); + // else + // Debug.LogWarning($"PacketId {packetId} - Remaining {reader.Remaining}."); + // } + // print = false; + #endif } - else if (packetId == PacketId.Version) + bool spawnOrDespawn = packetId == PacketId.ObjectSpawn || packetId == PacketId.ObjectDespawn; + /* Length of data. Only available if using unreliable. Unreliable packets + * can arrive out of order which means object orientated messages such as RPCs may + * arrive after the object for which they target has already been destroyed. When this happens + * on lesser solutions they just dump the entire packet. However, since FishNet batches data. + * it's very likely a packet will contain more than one packetId. With this mind, length is + * sent as well so if any reason the data does have to be dumped it will only be dumped for + * that single packetId but not the rest. Broadcasts don't need length either even if unreliable + * because they are not object bound. */ + + // Is spawn or despawn; cache packet. + if (spawnOrDespawn) { - ParseVersion(reader); + if (packetId == PacketId.ObjectSpawn) + Objects.ReadSpawn(reader); + else if (packetId == PacketId.ObjectDespawn) + Objects.CacheDespawn(reader); } + // Not spawn or despawn. else { - NetworkManager.LogError($"Client received an unhandled PacketId of {(ushort)packetId} on channel {channel}. Remaining data has been purged."); - #if DEVELOPMENT - NetworkManager.LogError(NetworkManager.PacketIdHistory.GetReceivedPacketIds(packetsFromServer: true)); - #endif - return; + /* Iterate object cache should any of the + * incoming packets rely on it. Objects + * in cache will always be received before any messages + * that use them. */ + Objects.IterateObjectCache(); + using (_pm_ParseReader_HandlePacket.Auto()) + { + // Then process packet normally. + if ((ushort)packetId >= NetworkManager.StartingRpcLinkIndex) + { + Objects.ParseRpcLink(reader, (ushort)packetId, channel); + } + else if (packetId == PacketId.StateUpdate) + { + using (_pm_ParseReader_StateUpdate.Auto()) + { + NetworkManager.PredictionManager.ParseStateUpdate(reader, channel); + } + } + else if (packetId == PacketId.Replicate) + { + Objects.ParseReplicateRpc(reader, null, channel); + } + else if (packetId == PacketId.Reconcile) + { + Objects.ParseReconcileRpc(reader, channel); + } + else if (packetId == PacketId.ObserversRpc) + { + Objects.ParseObserversRpc(reader, channel); + } + else if (packetId == PacketId.TargetRpc) + { + Objects.ParseTargetRpc(reader, channel); + } + else if (packetId == PacketId.Broadcast) + { + using (_pm_ParseReader_Broadcast.Auto()) + { + ParseBroadcast(reader, channel); + } + } + else if (packetId == PacketId.PingPong) + { + using (_pm_ParseReader_PingPong.Auto()) + { + ParsePingPong(reader); + } + } + else if (packetId == PacketId.SyncType) + { + Objects.ParseSyncType(reader, channel); + } + else if (packetId == PacketId.PredictedSpawnResult) + { + Objects.ParsePredictedSpawnResult(reader); + } + else if (packetId == PacketId.TimingUpdate) + { + using (_pm_ParseReader_TimingUpdate.Auto()) + { + NetworkManager.TimeManager.ParseTimingUpdate(reader); + } + } + else if (packetId == PacketId.OwnershipChange) + { + Objects.ParseOwnershipChange(reader); + } + else if (packetId == PacketId.Authenticated) + { + using (_pm_ParseReader_Authenticated.Auto()) + { + ParseAuthenticated(reader); + } + } + else if (packetId == PacketId.Disconnect) + { + using (_pm_ParseReader_Disconnect.Auto()) + { + reader.Clear(); + StopConnection(); + } + } + else if (packetId == PacketId.Version) + { + using (_pm_ParseReader_Version.Auto()) + { + ParseVersion(reader); + } + } + else + { + NetworkManager.LogError($"Client received an unhandled PacketId of {(ushort)packetId} on channel {channel}. Remaining data has been purged."); + #if DEVELOPMENT + NetworkManager.LogError(NetworkManager.PacketIdHistory.GetReceivedPacketIds(packetsFromServer: true)); + #endif + return; + } + } } + + #if DEVELOPMENT + if (print) + Debug.Log($"Reader remaining {reader.Remaining}"); + #endif } - #if DEVELOPMENT - if (print) - Debug.Log($"Reader remaining {reader.Remaining}"); + /* Iterate cache when reader is emptied. + * This is incase the last packet received + * was a spawned, which wouldn't trigger + * the above iteration. There's no harm + * in doing this check multiple times as there's + * an exit early check. */ + Objects.IterateObjectCache(); + #if !DEVELOPMENT + } + catch (Exception e) + { + NetworkManager.LogError($"Client encountered an error while parsing data for packetId {packetId}. Message: {e.Message}."); + } #endif } - - /* Iterate cache when reader is emptied. - * This is incase the last packet received - * was a spawned, which wouldn't trigger - * the above iteration. There's no harm - * in doing this check multiple times as there's - * an exit early check. */ - Objects.IterateObjectCache(); - #if !DEVELOPMENT - } - catch (Exception e) - { - NetworkManager.LogError($"Client encountered an error while parsing data for packetId {packetId}. Message: {e.Message}."); - } - #endif } /// @@ -717,4 +786,4 @@ private void CheckServerTimeout() } } } -} \ No newline at end of file +} diff --git a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.RpcLinks.cs b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.RpcLinks.cs index f4ad7e60..46c95ad2 100644 --- a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.RpcLinks.cs +++ b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.RpcLinks.cs @@ -8,6 +8,7 @@ using FishNet.Transporting; using GameKit.Dependencies.Utilities; using System.Collections.Generic; +using Unity.Profiling; namespace FishNet.Managing.Client { @@ -23,6 +24,17 @@ public partial class ClientObjects : ManagedObjects private Dictionary _rpcLinks = new(); #endregion + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_ParseRpcLink = + new("ClientObjects.ParseRpcLink(PooledReader, ushort, Channel)"); + private static readonly ProfilerMarker _pm_ParseRpcLink_TargetRpc = + new("NetworkBehaviour.ReadTargetRpc()"); + private static readonly ProfilerMarker _pm_ParseRpcLink_ObserversRpc = + new("NetworkBehaviour.ReadObserversRpc()"); + private static readonly ProfilerMarker _pm_ParseRpcLink_Reconcile = + new("NetworkBehaviour.OnReconcileRpc()"); + #endregion + /// /// Parses a received RPCLink. /// @@ -30,49 +42,63 @@ public partial class ClientObjects : ManagedObjects /// internal void ParseRpcLink(PooledReader reader, ushort index, Channel channel) { -#if DEVELOPMENT - NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount); -#endif - int readerStartAfterDebug = reader.Position; - - int dataLength; - // Link index isn't stored. - if (!_rpcLinks.TryGetValueIL2CPP(index, out RpcLink link)) - { - dataLength = Packets.GetPacketLength(ushort.MaxValue, reader, channel); - SkipDataLength(index, reader, dataLength); - } - // Found NetworkObject for link. - else if (Spawned.TryGetValueIL2CPP(link.ObjectId, out NetworkObject nob)) + using (_pm_ParseRpcLink.Auto()) { - // Still call GetPacketLength to remove any extra bytes at the front of the reader. - NetworkBehaviour nb = nob.NetworkBehaviours[link.ComponentIndex]; - if (link.RpcPacketId == PacketId.TargetRpc) + #if DEVELOPMENT + NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, + out string rpcInformation, out uint expectedReadAmount); + #endif + int readerStartAfterDebug = reader.Position; + + int dataLength; + // Link index isn't stored. + if (!_rpcLinks.TryGetValueIL2CPP(index, out RpcLink link)) { - Packets.GetPacketLength((ushort)PacketId.TargetRpc, reader, channel); - nb.ReadTargetRpc(readerStartAfterDebug, fromRpcLink: true, link.RpcHash, reader, channel); + dataLength = Packets.GetPacketLength(ushort.MaxValue, reader, channel); + SkipDataLength(index, reader, dataLength); } - else if (link.RpcPacketId == PacketId.ObserversRpc) + // Found NetworkObject for link. + else if (Spawned.TryGetValueIL2CPP(link.ObjectId, out NetworkObject nob)) { - Packets.GetPacketLength((ushort)PacketId.ObserversRpc, reader, channel); - nb.ReadObserversRpc(readerStartAfterDebug, fromRpcLink: true, link.RpcHash, reader, channel); + // Still call GetPacketLength to remove any extra bytes at the front of the reader. + NetworkBehaviour nb = nob.NetworkBehaviours[link.ComponentIndex]; + if (link.RpcPacketId == PacketId.TargetRpc) + { + Packets.GetPacketLength((ushort)PacketId.TargetRpc, reader, channel); + using (_pm_ParseRpcLink_TargetRpc.Auto()) + { + nb.ReadTargetRpc(readerStartAfterDebug, fromRpcLink: true, link.RpcHash, reader, channel); + } + } + else if (link.RpcPacketId == PacketId.ObserversRpc) + { + Packets.GetPacketLength((ushort)PacketId.ObserversRpc, reader, channel); + using (_pm_ParseRpcLink_ObserversRpc.Auto()) + { + nb.ReadObserversRpc(readerStartAfterDebug, fromRpcLink: true, link.RpcHash, reader, channel); + } + } + else if (link.RpcPacketId == PacketId.Reconcile) + { + Packets.GetPacketLength((ushort)PacketId.Reconcile, reader, channel); + using (_pm_ParseRpcLink_Reconcile.Auto()) + { + nb.OnReconcileRpc(readerStartAfterDebug, link.RpcHash, reader, channel); + } + } } - else if (link.RpcPacketId == PacketId.Reconcile) + // Could not find NetworkObject. + else { - Packets.GetPacketLength((ushort)PacketId.Reconcile, reader, channel); - nb.OnReconcileRpc(readerStartAfterDebug, link.RpcHash, reader, channel); + dataLength = Packets.GetPacketLength(index, reader, channel); + SkipDataLength(index, reader, dataLength, link.ObjectId); } - } - // Could not find NetworkObject. - else - { - dataLength = Packets.GetPacketLength(index, reader, channel); - SkipDataLength(index, reader, dataLength, link.ObjectId); - } -#if DEVELOPMENT - NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: true, NetworkManager, reader, startReaderRemaining, rpcInformation, expectedReadAmount, channel); -#endif + #if DEVELOPMENT + NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: true, NetworkManager, reader, + startReaderRemaining, rpcInformation, expectedReadAmount, channel); + #endif + } } /// @@ -97,4 +123,4 @@ internal void RemoveLinkIndexes(List values) _rpcLinks.Remove(values[i]); } } -} \ No newline at end of file +} diff --git a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs index 461c5418..2af8855c 100644 --- a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs +++ b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs @@ -20,6 +20,7 @@ using FishNet.Serializing.Helping; using UnityEngine; using UnityEngine.SceneManagement; +using Unity.Profiling; namespace FishNet.Managing.Client { @@ -35,6 +36,27 @@ public partial class ClientObjects : ManagedObjects private ClientObjectCache _objectCache; #endregion + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_ParseOwnershipChange = + new("ClientObjects.ParseOwnershipChange(PooledReader)"); + private static readonly ProfilerMarker _pm_ParseSyncType = + new("ClientObjects.ParseSyncType(PooledReader, Channel)"); + private static readonly ProfilerMarker _pm_ParsePredictedSpawnResult = + new("ClientObjects.ParsePredictedSpawnResult(PooledReader)"); + private static readonly ProfilerMarker _pm_ParseReconcileRpc = + new("ClientObjects.ParseReconcileRpc(PooledReader, Channel)"); + private static readonly ProfilerMarker _pm_ParseObserversRpc = + new("ClientObjects.ParseObserversRpc(PooledReader, Channel)"); + private static readonly ProfilerMarker _pm_ParseTargetRpc = + new("ClientObjects.ParseTargetRpc(PooledReader, Channel)"); + private static readonly ProfilerMarker _pm_ReadSpawn = + new("ClientObjects.ReadSpawn(PooledReader)"); + private static readonly ProfilerMarker _pm_CacheDespawn = + new("ClientObjects.CacheDespawn(PooledReader)"); + private static readonly ProfilerMarker _pm_IterateObjectCache = + new("ClientObjects.IterateObjectCache()"); + #endregion + internal ClientObjects(NetworkManager networkManager) { base.Initialize(networkManager); @@ -99,7 +121,7 @@ internal void OnClientConnectionState(ClientConnectionStateArgs args) /* Clear spawned and scene objects as they will be rebuilt. * Spawned would have already be cleared if DespawnSpawned * was called but it won't hurt anything clearing an empty collection. */ - Spawned.Clear(); + HandleClear(); SceneObjects_Internal.Clear(); } } @@ -259,12 +281,15 @@ internal override void NetworkObjectDestroyed(NetworkObject nob, bool asServer) /// internal void ParseOwnershipChange(PooledReader reader) { - NetworkObject nob = reader.ReadNetworkObject(); - NetworkConnection newOwner = reader.ReadNetworkConnection(); - if (nob != null && nob.IsSpawned) - nob.GiveOwnership(newOwner, asServer: false, recursive: false); - else - NetworkManager.LogWarning($"NetworkBehaviour could not be found when trying to parse OwnershipChange packet."); + using (_pm_ParseOwnershipChange.Auto()) + { + NetworkObject nob = reader.ReadNetworkObject(); + NetworkConnection newOwner = reader.ReadNetworkConnection(); + if (nob != null && nob.IsSpawned) + nob.GiveOwnership(newOwner, asServer: false, recursive: false); + else + NetworkManager.LogWarning($"NetworkBehaviour could not be found when trying to parse OwnershipChange packet."); + } } /// @@ -273,24 +298,27 @@ internal void ParseOwnershipChange(PooledReader reader) /// internal void ParseSyncType(PooledReader reader, Channel channel) { - int readerPositionAfterDebug = reader.Position; + using (_pm_ParseSyncType.Auto()) + { + int readerPositionAfterDebug = reader.Position; - NetworkBehaviour nb = reader.ReadNetworkBehaviour(); - int length = (int)ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.SYNCTYPE_RESERVE_BYTES); + NetworkBehaviour nb = reader.ReadNetworkBehaviour(); + int length = (int)ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.SYNCTYPE_RESERVE_BYTES); - if (nb != null && nb.IsSpawned) - { - /* Length of data to be read for syncvars. - * This is important because syncvars are never - * a set length and data must be read through completion. - * The only way to know where completion of syncvar is, versus - * when another packet starts is by including the length. */ - if (length > 0) - nb.ReadSyncType(readerPositionAfterDebug, reader, length); - } - else - { - SkipDataLength((ushort)PacketId.SyncType, reader, length); + if (nb != null && nb.IsSpawned) + { + /* Length of data to be read for syncvars. + * This is important because syncvars are never + * a set length and data must be read through completion. + * The only way to know where completion of syncvar is, versus + * when another packet starts is by including the length. */ + if (length > 0) + nb.ReadSyncType(readerPositionAfterDebug, reader, length); + } + else + { + SkipDataLength((ushort)PacketId.SyncType, reader, length); + } } } @@ -300,30 +328,33 @@ internal void ParseSyncType(PooledReader reader, Channel channel) /// internal void ParsePredictedSpawnResult(PooledReader reader) { - int readerPositionAfterDebug = reader.Position; + using (_pm_ParsePredictedSpawnResult.Auto()) + { + int readerPositionAfterDebug = reader.Position; - bool success = reader.ReadBoolean(); - int usedObjectId = reader.ReadNetworkObjectId(); - int nextObjectId = reader.ReadNetworkObjectId(); + bool success = reader.ReadBoolean(); + int usedObjectId = reader.ReadNetworkObjectId(); + int nextObjectId = reader.ReadNetworkObjectId(); - #if DEVELOPMENT && !UNITY_SERVER - if (NetworkTrafficStatistics != null) - NetworkTrafficStatistics.AddInboundPacketIdData(PacketId.PredictedSpawnResult, string.Empty, reader.Position - readerPositionAfterDebug + Transporting.TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false); - #endif + #if DEVELOPMENT && !UNITY_SERVER + if (NetworkTrafficStatistics != null) + NetworkTrafficStatistics.AddInboundPacketIdData(PacketId.PredictedSpawnResult, string.Empty, reader.Position - readerPositionAfterDebug + Transporting.TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false); + #endif - if (nextObjectId != NetworkObject.UNSET_OBJECTID_VALUE) - NetworkManager.ClientManager.Connection.PredictedObjectIds.Enqueue(nextObjectId); + if (nextObjectId != NetworkObject.UNSET_OBJECTID_VALUE) + NetworkManager.ClientManager.Connection.PredictedObjectIds.Enqueue(nextObjectId); - //Server would not allow the predicted spawn. - if (!success) - { - if (Spawned.TryGetValueIL2CPP(usedObjectId, out NetworkObject nob)) + //Server would not allow the predicted spawn. + if (!success) { - //TODO support pooling. This first requires a rework of the initialization / clientHost message system. - nob.SetIsDestroying(DespawnType.Destroy); - UnityEngine.Object.Destroy(nob.gameObject); - //nob.Deinitialize(asServer: false); - //NetworkManager.StorePooledInstantiated(nob, false); + if (Spawned.TryGetValueIL2CPP(usedObjectId, out NetworkObject nob)) + { + //TODO support pooling. This first requires a rework of the initialization / clientHost message system. + nob.SetIsDestroying(DespawnType.Destroy); + UnityEngine.Object.Destroy(nob.gameObject); + //nob.Deinitialize(asServer: false); + //NetworkManager.StorePooledInstantiated(nob, false); + } } } } @@ -334,22 +365,25 @@ internal void ParsePredictedSpawnResult(PooledReader reader) /// internal void ParseReconcileRpc(PooledReader reader, Channel channel) { - #if DEVELOPMENT - NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int readerRemainingAfterLength, out string rpcInformation, out uint expectedReadAmount); - #endif - int readerStartAfterDebug = reader.Position; + using (_pm_ParseReconcileRpc.Auto()) + { + #if DEVELOPMENT + NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int readerRemainingAfterLength, out string rpcInformation, out uint expectedReadAmount); + #endif + int readerStartAfterDebug = reader.Position; - NetworkBehaviour nb = reader.ReadNetworkBehaviour(); - int dataLength = Packets.GetPacketLength((ushort)PacketId.Reconcile, reader, channel); + NetworkBehaviour nb = reader.ReadNetworkBehaviour(); + int dataLength = Packets.GetPacketLength((ushort)PacketId.Reconcile, reader, channel); - if (nb != null && nb.IsSpawned) - nb.OnReconcileRpc(readerStartAfterDebug, hash: null, reader, channel); - else - SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength); + if (nb != null && nb.IsSpawned) + nb.OnReconcileRpc(readerStartAfterDebug, hash: null, reader, channel); + else + SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength); - #if DEVELOPMENT - NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, readerRemainingAfterLength, rpcInformation, expectedReadAmount, channel); - #endif + #if DEVELOPMENT + NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, readerRemainingAfterLength, rpcInformation, expectedReadAmount, channel); + #endif + } } /// @@ -358,26 +392,29 @@ internal void ParseReconcileRpc(PooledReader reader, Channel channel) /// internal void ParseObserversRpc(PooledReader reader, Channel channel) { - #if DEVELOPMENT - NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount); - #endif - int readerStartAfterDebug = reader.Position; - - NetworkBehaviour nb = reader.ReadNetworkBehaviour(logException: false); - int dataLength = Packets.GetPacketLength((ushort)PacketId.ObserversRpc, reader, channel); - if (nb != null && nb.IsSpawned) + using (_pm_ParseObserversRpc.Auto()) { - nb.ReadObserversRpc(readerStartAfterDebug, fromRpcLink: false, hash: 0, reader, channel); - } - else - { - NetworkManager.Log($"NetworkBehaviour not found for an ObserverRpc. Rpc data will be discarded."); - SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength); - } + #if DEVELOPMENT + NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount); + #endif + int readerStartAfterDebug = reader.Position; + + NetworkBehaviour nb = reader.ReadNetworkBehaviour(logException: false); + int dataLength = Packets.GetPacketLength((ushort)PacketId.ObserversRpc, reader, channel); + if (nb != null && nb.IsSpawned) + { + nb.ReadObserversRpc(readerStartAfterDebug, fromRpcLink: false, hash: 0, reader, channel); + } + else + { + NetworkManager.Log($"NetworkBehaviour not found for an ObserverRpc. Rpc data will be discarded."); + SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength); + } - #if DEVELOPMENT - NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, startReaderRemaining, rpcInformation, expectedReadAmount, channel); - #endif + #if DEVELOPMENT + NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, startReaderRemaining, rpcInformation, expectedReadAmount, channel); + #endif + } } /// @@ -386,18 +423,21 @@ internal void ParseObserversRpc(PooledReader reader, Channel channel) /// internal void ParseTargetRpc(PooledReader reader, Channel channel) { - #if DEVELOPMENT - NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount); - #endif - int readerStartAfterDebug = reader.Position; + using (_pm_ParseTargetRpc.Auto()) + { + #if DEVELOPMENT + NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount); + #endif + int readerStartAfterDebug = reader.Position; - NetworkBehaviour nb = reader.ReadNetworkBehaviour(); - int dataLength = Packets.GetPacketLength((ushort)PacketId.TargetRpc, reader, channel); + NetworkBehaviour nb = reader.ReadNetworkBehaviour(); + int dataLength = Packets.GetPacketLength((ushort)PacketId.TargetRpc, reader, channel); - if (nb != null && nb.IsSpawned) - nb.ReadTargetRpc(readerStartAfterDebug, fromRpcLink: false, hash: 0, reader, channel); - else - SkipDataLength((ushort)PacketId.TargetRpc, reader, dataLength); + if (nb != null && nb.IsSpawned) + nb.ReadTargetRpc(readerStartAfterDebug, fromRpcLink: false, hash: 0, reader, channel); + else + SkipDataLength((ushort)PacketId.TargetRpc, reader, dataLength); + } } /// @@ -405,100 +445,107 @@ internal void ParseTargetRpc(PooledReader reader, Channel channel) /// internal void ReadSpawn(PooledReader reader) { - #if DEVELOPMENT && !UNITY_SERVER - int readerPositionAfterDebug = reader.Position; - #endif + using (_pm_ReadSpawn.Auto()) + { + #if DEVELOPMENT && !UNITY_SERVER + int readerPositionAfterDebug = reader.Position; + #endif - SpawnType st = (SpawnType)reader.ReadUInt8Unpacked(); + SpawnType st = (SpawnType)reader.ReadUInt8Unpacked(); - bool sceneObject = st.FastContains(SpawnType.Scene); + bool sceneObject = st.FastContains(SpawnType.Scene); - ReadNestedSpawnIds(reader, st, out byte? nobComponentId, out int? parentObjectId, out byte? parentComponentId, _objectCache.ReadSpawningObjects); + ReadNestedSpawnIds(reader, st, out byte? nobComponentId, out int? parentObjectId, + out byte? parentComponentId, _objectCache.ReadSpawningObjects); - //NeworkObject and owner information. - int objectId = reader.ReadNetworkObjectForSpawn(out int initializeOrder, out ushort collectionId); - int ownerId = reader.ReadNetworkConnectionId(); - //Read transform values which differ from serialized values. - Vector3? localPosition; - Quaternion? localRotation; - Vector3? localScale; - ReadTransformProperties(reader, out localPosition, out localRotation, out localScale); + //NeworkObject and owner information. + int objectId = reader.ReadNetworkObjectForSpawn(out int initializeOrder, out ushort collectionId); + int ownerId = reader.ReadNetworkConnectionId(); + //Read transform values which differ from serialized values. + Vector3? localPosition; + Quaternion? localRotation; + Vector3? localScale; + ReadTransformProperties(reader, out localPosition, out localRotation, out localScale); - int prefabId = 0; - ulong sceneId = 0; - string sceneName = string.Empty; - string objectName = string.Empty; + int prefabId = 0; + ulong sceneId = 0; + string sceneName = string.Empty; + string objectName = string.Empty; - if (sceneObject) - { - ReadSceneObjectId(reader, out sceneId); - #if DEVELOPMENT - if (NetworkManager.ClientManager.IsServerDevelopment) - CheckReadSceneObjectDetails(reader, ref sceneName, ref objectName); - #endif - } - else - { - prefabId = reader.ReadNetworkObjectId(); - } + if (sceneObject) + { + ReadSceneObjectId(reader, out sceneId); + #if DEVELOPMENT + if (NetworkManager.ClientManager.IsServerDevelopment) + CheckReadSceneObjectDetails(reader, ref sceneName, ref objectName); + #endif + } + else + { + prefabId = reader.ReadNetworkObjectId(); + } - ArraySegment payload = ReadPayload(reader); - ArraySegment rpcLinks = ReadRpcLinks(reader); - ArraySegment syncTypes = ReadSyncTypesForSpawn(reader); + ArraySegment payload = ReadPayload(reader); + ArraySegment rpcLinks = ReadRpcLinks(reader); + ArraySegment syncTypes = ReadSyncTypesForSpawn(reader); - #if DEVELOPMENT && !UNITY_SERVER - if (NetworkTrafficStatistics != null) - NetworkTrafficStatistics.AddInboundPacketIdData(PacketId.ObjectSpawn, string.Empty, reader.Position - readerPositionAfterDebug + Transporting.TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false); - #endif + #if DEVELOPMENT && !UNITY_SERVER + if (NetworkTrafficStatistics != null) + NetworkTrafficStatistics.AddInboundPacketIdData(PacketId.ObjectSpawn, string.Empty, + reader.Position - readerPositionAfterDebug + Transporting.TransportManager.PACKETID_LENGTH, + gameObject: null, asServer: false); + #endif - bool isPredictedSpawner = st.FastContains(SpawnType.IsPredictedSpawner); + bool isPredictedSpawner = st.FastContains(SpawnType.IsPredictedSpawner); - //If found in spawn already. - if (Spawned.TryGetValue(objectId, out NetworkObject nob)) - { - /* If not server then extra checks must be done. Client should never - * receive spawn messages for already spawned objects, unless they locally - * predicted spawned the object. */ - if (!NetworkManager.IsServerStarted) + //If found in spawn already. + if (Spawned.TryGetValue(objectId, out NetworkObject nob)) { - //Not predicted spawner. - if (!st.FastContains(SpawnType.IsPredictedSpawner)) - { - NetworkManager.LogWarning($"Received a spawn objectId of {objectId} which was already found in spawned, and was not predicted. This sometimes may occur on clientHost when the server destroys an object unexpectedly before the clientHost gets the spawn message."); - } - //Is predicted spawner. - else + /* If not server then extra checks must be done. Client should never + * receive spawn messages for already spawned objects, unless they locally + * predicted spawned the object. */ + if (!NetworkManager.IsServerStarted) { - PooledReader segmentReader = ReaderPool.Retrieve(ArraySegment.Empty, NetworkManager); + //Not predicted spawner. + if (!st.FastContains(SpawnType.IsPredictedSpawner)) + { + NetworkManager.LogWarning($"Received a spawn objectId of {objectId} which was already found in spawned, and was not predicted. This sometimes may occur on clientHost when the server destroys an object unexpectedly before the clientHost gets the spawn message."); + } + //Is predicted spawner. + else + { + PooledReader segmentReader = ReaderPool.Retrieve(ArraySegment.Empty, NetworkManager); - //RpcLinks. - segmentReader.Initialize(rpcLinks, NetworkManager, Reader.DataSource.Server); - ApplyRpcLinks(nob, segmentReader); + //RpcLinks. + segmentReader.Initialize(rpcLinks, NetworkManager, Reader.DataSource.Server); + ApplyRpcLinks(nob, segmentReader); - //Payload. - segmentReader.Initialize(payload, NetworkManager, Reader.DataSource.Server); - ReadPayload(sender: null, nob, segmentReader, segmentReader.Length); + //Payload. + segmentReader.Initialize(payload, NetworkManager, Reader.DataSource.Server); + ReadPayload(sender: null, nob, segmentReader, segmentReader.Length); - //SyncTypes. - segmentReader.Initialize(syncTypes, NetworkManager, Reader.DataSource.Server); - ApplySyncTypesForSpawn(nob, segmentReader); - } + //SyncTypes. + segmentReader.Initialize(syncTypes, NetworkManager, Reader.DataSource.Server); + ApplySyncTypesForSpawn(nob, segmentReader); + } - /* Nob isn't added to spawn if predicted spawner. - * We only wanted to read and apply initial data from the server. */ - return; + /* Nob isn't added to spawn if predicted spawner. + * We only wanted to read and apply initial data from the server. */ + return; + } + } + else + { + /* If predicted spawner and not in spawned then simply exit early. + * The predicted spawner destroyed the object locally. */ + if (isPredictedSpawner) + return; } - } - else - { - /* If predicted spawner and not in spawned then simply exit early. - * The predicted spawner destroyed the object locally. */ - if (isPredictedSpawner) - return; - } - - _objectCache.AddSpawn(NetworkManager, collectionId, objectId, initializeOrder, ownerId, st, nobComponentId, parentObjectId, parentComponentId, prefabId, localPosition, localRotation, localScale, sceneId, sceneName, objectName, payload, rpcLinks, syncTypes); + _objectCache.AddSpawn(NetworkManager, collectionId, objectId, initializeOrder, ownerId, st, + nobComponentId, parentObjectId, parentComponentId, prefabId, localPosition, localRotation, + localScale, sceneId, sceneName, objectName, payload, rpcLinks, syncTypes); + } } /// @@ -507,18 +554,23 @@ internal void ReadSpawn(PooledReader reader) /// internal void CacheDespawn(PooledReader reader) { - #if DEVELOPMENT && !UNITY_SERVER - int readerPositionAfterDebug = reader.Position; - #endif - - DespawnType despawnType; - int objectId = reader.ReadNetworkObjectForDespawn(out despawnType); - _objectCache.AddDespawn(objectId, despawnType); - - #if DEVELOPMENT && !UNITY_SERVER - if (NetworkTrafficStatistics != null) - NetworkTrafficStatistics.AddInboundPacketIdData(PacketId.ObjectDespawn, string.Empty, reader.Position - readerPositionAfterDebug + Transporting.TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false); - #endif + using (_pm_CacheDespawn.Auto()) + { + #if DEVELOPMENT && !UNITY_SERVER + int readerPositionAfterDebug = reader.Position; + #endif + + DespawnType despawnType; + int objectId = reader.ReadNetworkObjectForDespawn(out despawnType); + _objectCache.AddDespawn(objectId, despawnType); + + #if DEVELOPMENT && !UNITY_SERVER + if (NetworkTrafficStatistics != null) + NetworkTrafficStatistics.AddInboundPacketIdData(PacketId.ObjectDespawn, string.Empty, + reader.Position - readerPositionAfterDebug + Transporting.TransportManager.PACKETID_LENGTH, + gameObject: null, asServer: false); + #endif + } } /// @@ -528,7 +580,10 @@ internal void CacheDespawn(PooledReader reader) /// internal void IterateObjectCache() { - _objectCache.Iterate(); + using (_pm_IterateObjectCache.Auto()) + { + _objectCache.Iterate(); + } } /// @@ -750,4 +805,4 @@ internal NetworkObject GetSpawnedNetworkObject(CachedNetworkObject cnob) } } } -} \ No newline at end of file +} diff --git a/Assets/FishNet/Runtime/Managing/Client/Object/ObjectCaching.cs b/Assets/FishNet/Runtime/Managing/Client/Object/ObjectCaching.cs index 17dd0c81..87b53a15 100644 --- a/Assets/FishNet/Runtime/Managing/Client/Object/ObjectCaching.cs +++ b/Assets/FishNet/Runtime/Managing/Client/Object/ObjectCaching.cs @@ -501,7 +501,7 @@ internal NetworkObject GetSpawnedObject(int objectId) //If not found in Spawning then check Spawned. if (!IteratedSpawningObjects.TryGetValue(objectId, out result)) { - Dictionary spawned = _networkManager.IsHostStarted ? _networkManager.ServerManager.Objects.Spawned : _networkManager.ClientManager.Objects.Spawned; + IReadOnlyDictionary spawned = _networkManager.IsHostStarted ? _networkManager.ServerManager.Objects.Spawned : _networkManager.ClientManager.Objects.Spawned; spawned.TryGetValue(objectId, out result); } diff --git a/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs b/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs index 41d9c480..f8af2dad 100644 --- a/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs +++ b/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs @@ -17,16 +17,32 @@ using FishNet.Managing.Statistic; using UnityEngine; using UnityEngine.SceneManagement; +using Unity.Profiling; namespace FishNet.Managing.Object { public abstract partial class ManagedObjects { #region Public. + /// /// NetworkObjects which are currently active. /// - public Dictionary Spawned = new(); + + private readonly Dictionary _spawned = new(); + public IReadOnlyDictionary Spawned => _spawned; + + public delegate void OnSpawnedChanged(int objectId, NetworkObject networkObject); + + public event OnSpawnedChanged OnSpawnedAdd; + public event OnSpawnedChanged OnSpawnedRemove; + public event Action OnSpawnedClear; + + #endregion + + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_ParseReplicateRpc = + new("ManagedObjects.ParseReplicateRpc(PooledReader, NetworkConnection, Channel)"); #endregion #region Protected. @@ -55,7 +71,26 @@ protected internal virtual bool GetNextNetworkObjectId(out int nextNetworkObject public IReadOnlyDictionary SceneObjects => SceneObjects_Internal; /// /// - protected NetworkTrafficStatistics NetworkTrafficStatistics; + [NonSerialized] protected NetworkTrafficStatistics NetworkTrafficStatistics; + + protected void HandleAdd(NetworkObject nob) + { + _spawned[nob.ObjectId] = nob; + OnSpawnedAdd?.Invoke(nob.ObjectId, nob); + } + + protected void HandleRemove(NetworkObject nob) + { + if (_spawned.Remove(nob.ObjectId)) + OnSpawnedRemove?.Invoke(nob.ObjectId, nob); + } + + protected void HandleClear() + { + _spawned.Clear(); + OnSpawnedClear?.Invoke(); + } + #endregion #region Private. @@ -109,7 +144,7 @@ internal virtual void NetworkObjectDestroyed(NetworkObject nob, bool asServer) /// protected virtual void RemoveFromSpawned(NetworkObject nob, bool fromOnDestroy, bool asServer) { - Spawned.Remove(nob.ObjectId); + HandleRemove(nob); // Do the same with SceneObjects. if (fromOnDestroy && nob.IsSceneObject) RemoveFromSceneObjects(nob); @@ -316,7 +351,7 @@ internal virtual void DespawnWithoutSynchronization(bool recursive, bool asServe DespawnWithoutSynchronization(nob, recursive, asServer, nob.GetDefaultDespawnType(), removeFromSpawned: false); } - Spawned.Clear(); + HandleClear(); } /// @@ -371,7 +406,7 @@ protected virtual void DespawnWithoutSynchronization(NetworkObject nob, bool rec /// internal virtual void AddToSpawned(NetworkObject nob, bool asServer) { - Spawned[nob.ObjectId] = nob; + HandleAdd(nob); } /// @@ -408,7 +443,7 @@ protected internal void RemoveFromSceneObjects(ulong sceneId) protected internal NetworkObject GetSpawnedNetworkObject(int objectId) { NetworkObject r; - if (!Spawned.TryGetValueIL2CPP(objectId, out r)) + if (!_spawned.TryGetValueIL2CPP(objectId, out r)) NetworkManager.LogError($"Spawned NetworkObject not found for ObjectId {objectId}."); return r; @@ -468,21 +503,26 @@ protected internal void SkipDataLength(ushort packetId, PooledReader reader, int /// internal void ParseReplicateRpc(PooledReader reader, NetworkConnection conn, Channel channel) { -#if DEVELOPMENT - NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount); -#endif - int readerStartAfterDebug = reader.Position; - - NetworkBehaviour nb = reader.ReadNetworkBehaviour(); - int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel); - if (nb != null && nb.IsSpawned) - nb.OnReplicateRpc(readerStartAfterDebug, hash: null, reader, conn, channel); - else - SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength); + using (_pm_ParseReplicateRpc.Auto()) + { + #if DEVELOPMENT + NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, + out string rpcInformation, out uint expectedReadAmount); + #endif + int readerStartAfterDebug = reader.Position; + + NetworkBehaviour nb = reader.ReadNetworkBehaviour(); + int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel); + if (nb != null && nb.IsSpawned) + nb.OnReplicateRpc(readerStartAfterDebug, hash: null, reader, conn, channel); + else + SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength); -#if DEVELOPMENT - NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, startReaderRemaining, rpcInformation, expectedReadAmount, channel); -#endif + #if DEVELOPMENT + NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, + startReaderRemaining, rpcInformation, expectedReadAmount, channel); + #endif + } } #if DEVELOPMENT @@ -512,4 +552,4 @@ protected void CheckReadSceneObjectDetails(Reader r, ref string sceneName, ref s } #endif } -} \ No newline at end of file +} diff --git a/Assets/FishNet/Runtime/Managing/Server/ServerManager.QOL.cs b/Assets/FishNet/Runtime/Managing/Server/ServerManager.QOL.cs index 64ad0aa0..eeef7a1a 100644 --- a/Assets/FishNet/Runtime/Managing/Server/ServerManager.QOL.cs +++ b/Assets/FishNet/Runtime/Managing/Server/ServerManager.QOL.cs @@ -178,14 +178,15 @@ public void Despawn(NetworkObject networkObject, DespawnType? despawnType = null /// Reason client is being kicked. /// How to print logging as. /// Optional message to be debug logged. - public void Kick(NetworkConnection conn, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "") + /// + public void Kick(NetworkConnection conn, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "", bool immediately = true) { if (!conn.IsValid) return; OnClientKick?.Invoke(conn, conn.ClientId, kickReason); if (conn.IsActive) - conn.Disconnect(true); + conn.Disconnect(immediately); if (!string.IsNullOrEmpty(log)) NetworkManager.Log(loggingType, log); @@ -198,10 +199,11 @@ public void Kick(NetworkConnection conn, KickReason kickReason, LoggingType logg /// Reason client is being kicked. /// How to print logging as. /// Optional message to be debug logged. - public void Kick(int clientId, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "") + /// + public void Kick(int clientId, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "", bool immediately = true) { OnClientKick?.Invoke(null, clientId, kickReason); - NetworkManager.TransportManager.Transport.StopConnection(clientId, true); + NetworkManager.TransportManager.Transport.StopConnection(clientId, immediately); if (!string.IsNullOrEmpty(log)) NetworkManager.Log(loggingType, log); } @@ -214,10 +216,11 @@ public void Kick(int clientId, KickReason kickReason, LoggingType loggingType = /// Reason client is being kicked. /// How to print logging as. /// Optional message to be debug logged. - public void Kick(NetworkConnection conn, Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "") + /// + public void Kick(NetworkConnection conn, Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "", bool immediately = true) { reader.Clear(); - Kick(conn, kickReason, loggingType, log); + Kick(conn, kickReason, loggingType, log, immediately); } } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs b/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs index d9527bab..78f208c1 100644 --- a/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs +++ b/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs @@ -172,7 +172,6 @@ public void SetRemoteClientTimeout(RemoteTimeoutType timeoutType, ushort duratio [SerializeField] private ushort _frameRate = NetworkManager.MAXIMUM_FRAMERATE; - /// /// Sets the maximum frame rate the client may run at. Calling this method will enable ChangeFrameRate. /// /// New value. @@ -223,7 +222,7 @@ public void SetFrameRate(ushort value) private SplitReader _splitReader = new(); /// /// - private NetworkTrafficStatistics _networkTrafficStatistics; + [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; #if DEVELOPMENT /// /// Logs data about parser to help debug. @@ -234,6 +233,12 @@ public void SetFrameRate(ushort value) #region Private Profiler Markers private static readonly ProfilerMarker _pm_OnPostTick = new("ServerManager.TimeManager_OnPostTick()"); + private static readonly ProfilerMarker _pm_Transport_OnServerConnectionState = + new("ServerManager.Transport_OnServerConnectionState(ServerConnectionStateArgs)"); + private static readonly ProfilerMarker _pm_Transport_OnRemoteConnectionState = + new("ServerManager.Transport_OnRemoteConnectionState(RemoteConnectionStateArgs)"); + private static readonly ProfilerMarker _pm_Transport_OnServerReceivedData = + new("ServerManager.Transport_OnServerReceivedData(ServerReceivedDataArgs)"); #endregion #region Const. @@ -523,38 +528,41 @@ private void _authenticator_OnAuthenticationResult(NetworkConnection conn, bool /// private void Transport_OnServerConnectionState(ServerConnectionStateArgs args) { - /* Let the client manager know the server state is changing first. - * This gives the client an opportunity to clean-up or prepare - * before the server completes it's actions. */ - Started = IsAnyServerStarted(); - NetworkManager.ClientManager.Objects.OnServerConnectionState(args); - //If no servers are started then reset data. - if (!Started) + using (_pm_Transport_OnServerConnectionState.Auto()) { - MatchCondition.StoreCollections(NetworkManager); - //Despawn without synchronizing network objects. - Objects.DespawnWithoutSynchronization(recursive: true, asServer: true); - //Clear all clients. - Clients.Clear(); - //Clients as list. - _clientsList.Clear(); - } - Objects.OnServerConnectionState(args); + /* Let the client manager know the server state is changing first. + * This gives the client an opportunity to clean-up or prepare + * before the server completes it's actions. */ + Started = IsAnyServerStarted(); + NetworkManager.ClientManager.Objects.OnServerConnectionState(args); + //If no servers are started then reset data. + if (!Started) + { + MatchCondition.StoreCollections(NetworkManager); + //Despawn without synchronizing network objects. + Objects.DespawnWithoutSynchronization(recursive: true, asServer: true); + //Clear all clients. + Clients.Clear(); + //Clients as list. + _clientsList.Clear(); + } + Objects.OnServerConnectionState(args); - LocalConnectionState state = args.ConnectionState; + LocalConnectionState state = args.ConnectionState; - if (NetworkManager.CanLog(LoggingType.Common)) - { - Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex); - string tName = t == null ? "Unknown" : t.GetType().Name; - string socketInformation = string.Empty; - if (state == LocalConnectionState.Starting) - socketInformation = $" Listening on port {t.GetPort()}."; - NetworkManager.Log($"Local server is {state.ToString().ToLower()} for {tName}.{socketInformation}"); - } + if (NetworkManager.CanLog(LoggingType.Common)) + { + Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex); + string tName = t == null ? "Unknown" : t.GetType().Name; + string socketInformation = string.Empty; + if (state == LocalConnectionState.Starting) + socketInformation = $" Listening on port {t.GetPort()}."; + NetworkManager.Log($"Local server is {state.ToString().ToLower()} for {tName}.{socketInformation}"); + } - NetworkManager.UpdateFramerate(); - OnServerConnectionState?.Invoke(args); + NetworkManager.UpdateFramerate(); + OnServerConnectionState?.Invoke(args); + } } /// @@ -609,47 +617,50 @@ private void ParseVersion(PooledReader reader, NetworkConnection conn, int trans /// private void Transport_OnRemoteConnectionState(RemoteConnectionStateArgs args) { - //Sanity check to make sure transports are following proper types/ranges. - int id = args.ConnectionId; - if (id < 0 || id > NetworkConnection.MAXIMUM_CLIENTID_VALUE) - { - Kick(args.ConnectionId, KickReason.UnexpectedProblem, LoggingType.Error, $"The transport you are using supplied an invalid connection Id of {id}. Connection Id values must range between 0 and {NetworkConnection.MAXIMUM_CLIENTID_VALUE}. The client has been disconnected."); - return; - } - //Valid Id. - else + using (_pm_Transport_OnRemoteConnectionState.Auto()) { - //If started then add to authenticated clients. - if (args.ConnectionState == RemoteConnectionState.Started) + //Sanity check to make sure transports are following proper types/ranges. + int id = args.ConnectionId; + if (id < 0 || id > NetworkConnection.MAXIMUM_CLIENTID_VALUE) { - NetworkManager.Log($"Remote connection started for Id {id}."); - NetworkConnection conn = new(NetworkManager, id, args.TransportIndex, true); - Clients.Add(args.ConnectionId, conn); - _clientsList.Add(conn); - OnRemoteConnectionState?.Invoke(conn, args); - - //Do nothing else until the client sends it's version. + Kick(args.ConnectionId, KickReason.UnexpectedProblem, LoggingType.Error, $"The transport you are using supplied an invalid connection Id of {id}. Connection Id values must range between 0 and {NetworkConnection.MAXIMUM_CLIENTID_VALUE}. The client has been disconnected."); + return; } - //If stopping. - else if (args.ConnectionState == RemoteConnectionState.Stopped) + //Valid Id. + else { - /* If client's connection is found then clean - * them up from server. */ - if (Clients.TryGetValueIL2CPP(id, out NetworkConnection conn)) + //If started then add to authenticated clients. + if (args.ConnectionState == RemoteConnectionState.Started) { - conn.SetDisconnecting(true); + NetworkManager.Log($"Remote connection started for Id {id}."); + NetworkConnection conn = new(NetworkManager, id, args.TransportIndex, true); + Clients.Add(args.ConnectionId, conn); + _clientsList.Add(conn); OnRemoteConnectionState?.Invoke(conn, args); - Clients.Remove(id); - _clientsList.Remove(conn); - Objects.ClientDisconnected(conn); - BroadcastClientConnectionChange(false, conn); - //Return predictedObjectIds. - Queue pqId = conn.PredictedObjectIds; - while (pqId.Count > 0) - Objects.CacheObjectId(pqId.Dequeue()); - - conn.ResetState(); - NetworkManager.Log($"Remote connection stopped for Id {id}."); + + //Do nothing else until the client sends it's version. + } + //If stopping. + else if (args.ConnectionState == RemoteConnectionState.Stopped) + { + /* If client's connection is found then clean + * them up from server. */ + if (Clients.TryGetValueIL2CPP(id, out NetworkConnection conn)) + { + conn.SetDisconnecting(true); + OnRemoteConnectionState?.Invoke(conn, args); + Clients.Remove(id); + _clientsList.Remove(conn); + Objects.ClientDisconnected(conn); + BroadcastClientConnectionChange(false, conn); + //Return predictedObjectIds. + Queue pqId = conn.PredictedObjectIds; + while (pqId.Count > 0) + Objects.CacheObjectId(pqId.Dequeue()); + + conn.ResetState(); + NetworkManager.Log($"Remote connection stopped for Id {id}."); + } } } } @@ -700,7 +711,10 @@ private void SendAuthenticated(NetworkConnection conn) /// private void Transport_OnServerReceivedData(ServerReceivedDataArgs args) { - ParseReceived(args); + using (_pm_Transport_OnServerReceivedData.Auto()) + { + ParseReceived(args); + } } /// @@ -990,4 +1004,4 @@ private void BroadcastClientConnectionChange(bool connected, NetworkConnection c } } } -} \ No newline at end of file +} diff --git a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs index 931c6c63..bd4f623f 100644 --- a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs +++ b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs @@ -6,11 +6,9 @@ using FishNet.Transporting; using GameKit.Dependencies.Utilities; using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; -using FishNet.Managing.Predicting; using FishNet.Managing.Statistic; -using FishNet.Object; +using Unity.Mathematics; using Unity.Profiling; using UnityEngine; using SystemStopwatch = System.Diagnostics.Stopwatch; @@ -287,10 +285,12 @@ public void SetPhysicsTimeScale(float value) /// /// - private NetworkTrafficStatistics _networkTrafficStatistics; + [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; #endregion #region Private Profiler Markers + private static readonly ProfilerMarker _pm_IncreaseTick = new("TimeManager.IncreaseTick()"); + private static readonly ProfilerMarker _pm_TryIterateData = new("TimeManager.TryIterateData(bool)"); private static readonly ProfilerMarker _pm_OnFixedUpdate = new("TimeManager.OnFixedUpdate()"); private static readonly ProfilerMarker _pm_OnPostPhysicsSimulation = new("TimeManager.OnPostPhysicsSimulation(float)"); private static readonly ProfilerMarker _pm_OnPrePhysicsSimulation = new("TimeManager.OnPrePhysicsSimulation(float)"); @@ -686,92 +686,95 @@ internal void SendPong(NetworkConnection conn, uint clientTick) /// private void IncreaseTick() { - bool isClient = NetworkManager.IsClientStarted; - bool isServer = NetworkManager.IsServerStarted; - - double timePerSimulation = isServer ? TickDelta : _adjustedTickDelta; - if (timePerSimulation == 0d) + using (_pm_IncreaseTick.Auto()) { - NetworkManager.LogWarning($"Simulation delta cannot be 0. Network timing will not continue."); - return; - } + bool isClient = NetworkManager.IsClientStarted; + bool isServer = NetworkManager.IsServerStarted; - double time = Time.unscaledDeltaTime; - - _elapsedTickTime += time; - FrameTicked = _elapsedTickTime >= timePerSimulation; + double timePerSimulation = isServer ? TickDelta : _adjustedTickDelta; + if (timePerSimulation == 0d) + { + NetworkManager.LogWarning($"Simulation delta cannot be 0. Network timing will not continue."); + return; + } - // Number of ticks to occur this frame. - int ticksCount = Mathf.FloorToInt((float)(_elapsedTickTime / timePerSimulation)); - if (ticksCount > 1) - _lastMultipleTicksTime = Time.unscaledTime; + double time = Time.unscaledDeltaTime; - if (_allowTickDropping) - { - // If ticks require dropping. Set exactly to maximum ticks. - if (ticksCount > _maximumFrameTicks) - _elapsedTickTime = timePerSimulation * (double)_maximumFrameTicks; - } + _elapsedTickTime += time; + FrameTicked = _elapsedTickTime >= timePerSimulation; - bool variableTiming = _timingType == TimingType.Variable; - bool frameTicked = FrameTicked; - float tickDelta = (float)TickDelta * GetPhysicsTimeScale(); + // Number of ticks to occur this frame. + int ticksCount = Mathf.FloorToInt((float)(_elapsedTickTime / timePerSimulation)); + if (ticksCount > 1) + _lastMultipleTicksTime = Time.unscaledTime; - do - { - if (frameTicked) + if (_allowTickDropping) { - using (_pm_OnPreTick.Auto()) - OnPreTick?.Invoke(); + // If ticks require dropping. Set exactly to maximum ticks. + if (ticksCount > _maximumFrameTicks) + _elapsedTickTime = timePerSimulation * (double)_maximumFrameTicks; } - /* This has to be called inside the loop because - * OnPreTick promises data hasn't been read yet. - * Therefor iterate must occur after OnPreTick. - * Iteration will only run once per frame. */ - if (frameTicked || variableTiming) - TryIterateData(true); + bool variableTiming = _timingType == TimingType.Variable; + bool frameTicked = FrameTicked; + float tickDelta = (float)TickDelta * GetPhysicsTimeScale(); - if (frameTicked) + do { - // Tell predicted objecs to reconcile before OnTick. - NetworkManager.PredictionManager.ReconcileToStates(); + if (frameTicked) + { + using (_pm_OnPreTick.Auto()) + OnPreTick?.Invoke(); + } - using (_pm_OnTick.Auto()) - OnTick?.Invoke(); + /* This has to be called inside the loop because + * OnPreTick promises data hasn't been read yet. + * Therefor iterate must occur after OnPreTick. + * Iteration will only run once per frame. */ + if (frameTicked || variableTiming) + TryIterateData(true); - if (PhysicsMode == PhysicsMode.TimeManager && tickDelta > 0f) + if (frameTicked) { - InvokeOnSimulation(preSimulation: true, tickDelta); - SimulatePhysics(tickDelta); - InvokeOnSimulation(preSimulation: false, tickDelta); + // Tell predicted objecs to reconcile before OnTick. + NetworkManager.PredictionManager.ReconcileToStates(); + + using (_pm_OnTick.Auto()) + OnTick?.Invoke(); + + if (PhysicsMode == PhysicsMode.TimeManager && tickDelta > 0f) + { + InvokeOnSimulation(preSimulation: true, tickDelta); + SimulatePhysics(tickDelta); + InvokeOnSimulation(preSimulation: false, tickDelta); + } + + using (_pm_OnPostTick.Auto()) + OnPostTick?.Invoke(); + // After post tick send states. + NetworkManager.PredictionManager.SendStateUpdate(); + + /* If isClient this is the + * last tick during this loop. */ + bool lastTick = _elapsedTickTime < timePerSimulation * 2d; + if (isClient && lastTick) + TrySendPing(LocalTick + 1); + if (NetworkManager.IsServerStarted) + SendTimingAdjustment(); } - using (_pm_OnPostTick.Auto()) - OnPostTick?.Invoke(); - // After post tick send states. - NetworkManager.PredictionManager.SendStateUpdate(); - - /* If isClient this is the - * last tick during this loop. */ - bool lastTick = _elapsedTickTime < timePerSimulation * 2d; - if (isClient && lastTick) - TrySendPing(LocalTick + 1); - if (NetworkManager.IsServerStarted) - SendTimingAdjustment(); - } + // Send out data. + if (frameTicked || variableTiming) + TryIterateData(false); - // Send out data. - if (frameTicked || variableTiming) - TryIterateData(false); - - if (frameTicked) - { - _elapsedTickTime -= timePerSimulation; - Tick++; - LocalTick++; - } - } while (_elapsedTickTime >= timePerSimulation); + if (frameTicked) + { + _elapsedTickTime -= timePerSimulation; + Tick++; + LocalTick++; + } + } while (_elapsedTickTime >= timePerSimulation); + } } #region Tick conversions. @@ -793,12 +796,14 @@ public double GetTickPercentAsDouble() /// Returns the current elapsed amount for the next tick. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double GetTickElapsedAsDouble() => _elapsedTickTime; /// /// Returns the percentage of how far the TimeManager is into the next tick. /// Value will return between 0 and 100. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte GetTickPercentAsByte() { double result = GetTickPercentAsDouble(); @@ -809,6 +814,7 @@ public byte GetTickPercentAsByte() /// Converts a 0 to 100 byte value to a 0d to 1d percent value. /// This does not check for excessive byte values, such as anything over 100. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double GetTickPercentAsDouble(byte value) { return value / 100d; @@ -890,6 +896,7 @@ public double TicksToTime(TickType tickType = TickType.LocalTick) /// /// PreciseTick to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(PreciseTick pt) { double tickTime = TicksToTime(pt.Tick); @@ -902,6 +909,7 @@ public double TicksToTime(PreciseTick pt) /// /// Ticks to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(uint ticks) { return TickDelta * (double)ticks; @@ -991,16 +999,28 @@ public double TimePassed(uint previousTick, bool allowNegative = false) /// /// Time to convert as decimal. /// - public uint TimeToTicks(double time, TickRounding rounding = TickRounding.RoundNearest) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint TimeToTicks(double time, double tickDelta, TickRounding rounding = TickRounding.RoundNearest) { - double result = time / TickDelta; + double result = time / tickDelta; if (rounding == TickRounding.RoundNearest) - return (uint)Math.Round(result); + return (uint)math.round(result); else if (rounding == TickRounding.RoundDown) - return (uint)Math.Floor(result); + return (uint)math.floor(result); else - return (uint)Math.Ceiling(result); + return (uint)math.ceil(result); + } + + /// + /// Converts time to ticks. + /// + /// Time to convert as decimal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint TimeToTicks(double time, TickRounding rounding = TickRounding.RoundNearest) + { + return TimeToTicks(time, TickDelta, rounding); } /// @@ -1008,6 +1028,7 @@ public uint TimeToTicks(double time, TickRounding rounding = TickRounding.RoundN /// /// Time to convert as whole (milliseconds) /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint TimeToTicks(long time, TickRounding rounding = TickRounding.RoundNearest) { double dTime = (double)time / 1000d; @@ -1019,6 +1040,7 @@ public uint TimeToTicks(long time, TickRounding rounding = TickRounding.RoundNea /// /// Time to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public PreciseTick TimeToPreciseTick(double time) => time.AsPreciseTick(TickDelta); /// @@ -1086,35 +1108,38 @@ internal void SimulatePhysics(float delta) using (_pm_Physics2DSimulate.Auto()) Physics2D.Simulate(delta); } - + /// /// Tries to iterate incoming or outgoing data. /// /// True to iterate incoming. private void TryIterateData(bool incoming) { - if (incoming) + using (_pm_TryIterateData.Auto()) { - /* It's not possible for data to come in - * more than once per frame but there could - * be new data going out each tick, since - * movement is often based off the tick system. - * Because of this don't iterate incoming if - * it's the same frame, but the outgoing - * may iterate multiple times per frame due to - * there possibly being multiple ticks per frame. */ - int frameCount = Time.frameCount; - if (frameCount == _lastIncomingIterationFrame) - return; - _lastIncomingIterationFrame = frameCount; - - NetworkManager.TransportManager.IterateIncoming(asServer: true); - NetworkManager.TransportManager.IterateIncoming(asServer: false); - } - else - { - NetworkManager.TransportManager.IterateOutgoing(asServer: true); - NetworkManager.TransportManager.IterateOutgoing(asServer: false); + if (incoming) + { + /* It's not possible for data to come in + * more than once per frame but there could + * be new data going out each tick, since + * movement is often based off the tick system. + * Because of this don't iterate incoming if + * it's the same frame, but the outgoing + * may iterate multiple times per frame due to + * there possibly being multiple ticks per frame. */ + int frameCount = Time.frameCount; + if (frameCount == _lastIncomingIterationFrame) + return; + _lastIncomingIterationFrame = frameCount; + + NetworkManager.TransportManager.IterateIncoming(asServer: true); + NetworkManager.TransportManager.IterateIncoming(asServer: false); + } + else + { + NetworkManager.TransportManager.IterateOutgoing(asServer: true); + NetworkManager.TransportManager.IterateOutgoing(asServer: false); + } } } diff --git a/Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs b/Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs index d1ca6a57..3e2bf1e3 100644 --- a/Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs +++ b/Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using FishNet.Managing.Statistic; using GameKit.Dependencies.Utilities; +using Unity.Profiling; using UnityEngine; namespace FishNet.Managing.Transporting @@ -122,7 +123,12 @@ public LatencySimulator LatencySimulator private int _customMtuReserve = MINIMUM_MTU_RESERVE; /// /// - private NetworkTrafficStatistics _networkTrafficStatistics; + [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; + #endregion + + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_IterateIncoming = new("TimeManager.IterateIncoming(bool)"); + private static readonly ProfilerMarker _pm_IterateOutgoing = new("TimeManager.IterateOutgoing(bool)"); #endregion #region Consts. @@ -746,9 +752,12 @@ private void SendSplitData(NetworkConnection conn, ref ArraySegment segmen /// True to read data from clients, false to read data from the server. internal void IterateIncoming(bool asServer) { - OnIterateIncomingStart?.Invoke(asServer); - Transport.IterateIncoming(asServer); - OnIterateIncomingEnd?.Invoke(asServer); + using (_pm_IterateIncoming.Auto()) + { + OnIterateIncomingStart?.Invoke(asServer); + Transport.IterateIncoming(asServer); + OnIterateIncomingEnd?.Invoke(asServer); + } } /// @@ -757,156 +766,159 @@ internal void IterateIncoming(bool asServer) /// True to send data from the local server to clients, false to send from the local client to server. internal void IterateOutgoing(bool asServer) { - if (asServer && _networkManager.ServerManager.AreAllServersStopped()) - return; + using (_pm_IterateOutgoing.Auto()) + { + if (asServer && _networkManager.ServerManager.AreAllServersStopped()) + return; - OnIterateOutgoingStart?.Invoke(); - int channelCount = CHANNEL_COUNT; - ulong sentBytes = 0; + OnIterateOutgoingStart?.Invoke(); + int channelCount = CHANNEL_COUNT; + ulong sentBytes = 0; #if DEVELOPMENT - bool latencySimulatorEnabled = LatencySimulator.CanSimulate; + bool latencySimulatorEnabled = LatencySimulator.CanSimulate; #endif - if (asServer) - SendAsServer(); - else - SendAsClient(); - - // Sends data as server. - void SendAsServer() - { - TimeManager tm = _networkManager.TimeManager; - uint localTick = tm.LocalTick; - // Write any dirty syncTypes. - _networkManager.ServerManager.Objects.WriteDirtySyncTypes(); - - int dirtyCount = _dirtyToClients.Count; + if (asServer) + SendAsServer(); + else + SendAsClient(); - // Run through all dirty connections to send data to. - for (int z = 0; z < dirtyCount; z++) + // Sends data as server. + void SendAsServer() { - NetworkConnection conn = _dirtyToClients[z]; - if (conn == null || !conn.IsValid) - continue; + TimeManager tm = _networkManager.TimeManager; + uint localTick = tm.LocalTick; + // Write any dirty syncTypes. + _networkManager.ServerManager.Objects.WriteDirtySyncTypes(); - // Get packets for every channel. - for (byte channel = 0; channel < channelCount; channel++) + int dirtyCount = _dirtyToClients.Count; + + // Run through all dirty connections to send data to. + for (int z = 0; z < dirtyCount; z++) { - if (conn.GetPacketBundle(channel, out PacketBundle pb)) - { - ProcessPacketBundle(pb); - ProcessPacketBundle(pb.GetSendLastBundle(), true); + NetworkConnection conn = _dirtyToClients[z]; + if (conn == null || !conn.IsValid) + continue; - void ProcessPacketBundle(PacketBundle ppb, bool isLast = false) + // Get packets for every channel. + for (byte channel = 0; channel < channelCount; channel++) + { + if (conn.GetPacketBundle(channel, out PacketBundle pb)) { - for (int i = 0; i < ppb.WrittenBuffers; i++) + ProcessPacketBundle(pb); + ProcessPacketBundle(pb.GetSendLastBundle(), true); + + void ProcessPacketBundle(PacketBundle ppb, bool isLast = false) { - // Length should always be more than 0 but check to be safe. - if (ppb.GetBuffer(i, out ByteBuffer bb)) + for (int i = 0; i < ppb.WrittenBuffers; i++) { - ArraySegment segment = new(bb.Data, 0, bb.Length); - if (HasIntermediateLayer) - segment = ProcessIntermediateOutgoing(segment, false); + // Length should always be more than 0 but check to be safe. + if (ppb.GetBuffer(i, out ByteBuffer bb)) + { + ArraySegment segment = new(bb.Data, 0, bb.Length); + if (HasIntermediateLayer) + segment = ProcessIntermediateOutgoing(segment, false); #if DEVELOPMENT - if (latencySimulatorEnabled) - _latencySimulator.AddOutgoing(channel, segment, false, conn.ClientId); - else + if (latencySimulatorEnabled) + _latencySimulator.AddOutgoing(channel, segment, false, conn.ClientId); + else #endif - Transport.SendToClient(channel, segment, conn.ClientId); - sentBytes += (ulong)segment.Count; + Transport.SendToClient(channel, segment, conn.ClientId); + sentBytes += (ulong)segment.Count; + } } - } - ppb.Reset(false); + ppb.Reset(false); + } } } - } - /* When marked as disconnecting data will still be sent - * this iteration but the connection will be marked as invalid. - * This will prevent future data from going out/coming in. - * Also the connection will be added to a disconnecting collection - * so it will it disconnected briefly later to allow data from - * this tick to send. */ - if (conn.Disconnecting) - { - uint requiredTicks = tm.TimeToTicks(0.1d, TickRounding.RoundUp); - /* Require 100ms or 2 ticks to pass - * before disconnecting to allow for the - * higher chance of success that remaining - * data is sent. */ - requiredTicks = Math.Max(requiredTicks, 2); - _disconnectingClients.Add(new(requiredTicks + localTick, conn)); - } + /* When marked as disconnecting data will still be sent + * this iteration but the connection will be marked as invalid. + * This will prevent future data from going out/coming in. + * Also the connection will be added to a disconnecting collection + * so it will it disconnected briefly later to allow data from + * this tick to send. */ + if (conn.Disconnecting) + { + uint requiredTicks = tm.TimeToTicks(0.1d, TickRounding.RoundUp); + /* Require 100ms or 2 ticks to pass + * before disconnecting to allow for the + * higher chance of success that remaining + * data is sent. */ + requiredTicks = Math.Max(requiredTicks, 2); + _disconnectingClients.Add(new(requiredTicks + localTick, conn)); + } - conn.ResetServerDirty(); - } + conn.ResetServerDirty(); + } - // Iterate disconnects. - for (int i = 0; i < _disconnectingClients.Count; i++) - { - DisconnectingClient dc = _disconnectingClients[i]; - if (localTick >= dc.Tick) + // Iterate disconnects. + for (int i = 0; i < _disconnectingClients.Count; i++) { - _networkManager.TransportManager.Transport.StopConnection(dc.Connection.ClientId, true); - _disconnectingClients.RemoveAt(i); - i--; + DisconnectingClient dc = _disconnectingClients[i]; + if (localTick >= dc.Tick) + { + _networkManager.TransportManager.Transport.StopConnection(dc.Connection.ClientId, true); + _disconnectingClients.RemoveAt(i); + i--; + } } - } - if (_networkTrafficStatistics != null) - _networkTrafficStatistics.AddOutboundSocketData(sentBytes, asServer: true); + if (_networkTrafficStatistics != null) + _networkTrafficStatistics.AddOutboundSocketData(sentBytes, asServer: true); - if (dirtyCount == _dirtyToClients.Count) - _dirtyToClients.Clear(); - else if (dirtyCount > 0) - _dirtyToClients.RemoveRange(0, dirtyCount); - } + if (dirtyCount == _dirtyToClients.Count) + _dirtyToClients.Clear(); + else if (dirtyCount > 0) + _dirtyToClients.RemoveRange(0, dirtyCount); + } - // Sends data as client. - void SendAsClient() - { - for (byte channel = 0; channel < channelCount; channel++) + // Sends data as client. + void SendAsClient() { - if (PacketBundle.GetPacketBundle(channel, _toServerBundles, out PacketBundle pb)) + for (byte channel = 0; channel < channelCount; channel++) { - ProcessPacketBundle(pb); - ProcessPacketBundle(pb.GetSendLastBundle()); - - void ProcessPacketBundle(PacketBundle ppb) + if (PacketBundle.GetPacketBundle(channel, _toServerBundles, out PacketBundle pb)) { - for (int i = 0; i < ppb.WrittenBuffers; i++) + ProcessPacketBundle(pb); + ProcessPacketBundle(pb.GetSendLastBundle()); + + void ProcessPacketBundle(PacketBundle ppb) { - if (ppb.GetBuffer(i, out ByteBuffer bb)) + for (int i = 0; i < ppb.WrittenBuffers; i++) { - ArraySegment segment = new(bb.Data, 0, bb.Length); - if (HasIntermediateLayer) - segment = ProcessIntermediateOutgoing(segment, true); + if (ppb.GetBuffer(i, out ByteBuffer bb)) + { + ArraySegment segment = new(bb.Data, 0, bb.Length); + if (HasIntermediateLayer) + segment = ProcessIntermediateOutgoing(segment, true); #if DEVELOPMENT - if (latencySimulatorEnabled) - _latencySimulator.AddOutgoing(channel, segment); - else + if (latencySimulatorEnabled) + _latencySimulator.AddOutgoing(channel, segment); + else #endif - Transport.SendToServer(channel, segment); - sentBytes += (ulong)segment.Count; + Transport.SendToServer(channel, segment); + sentBytes += (ulong)segment.Count; + } } - } - ppb.Reset(false); + ppb.Reset(false); + } } } - } - if (_networkTrafficStatistics != null) - _networkTrafficStatistics.AddOutboundSocketData(sentBytes, asServer: false); - } + if (_networkTrafficStatistics != null) + _networkTrafficStatistics.AddOutboundSocketData(sentBytes, asServer: false); + } #if DEVELOPMENT - if (latencySimulatorEnabled) - _latencySimulator.IterateOutgoing(asServer); + if (latencySimulatorEnabled) + _latencySimulator.IterateOutgoing(asServer); #endif - Transport.IterateOutgoing(asServer); - OnIterateOutgoingEnd?.Invoke(); + Transport.IterateOutgoing(asServer); + OnIterateOutgoingEnd?.Invoke(); + } } #region Editor. diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Callbacks.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Callbacks.cs index ddcfbbfa..d3e73e31 100644 --- a/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Callbacks.cs +++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Callbacks.cs @@ -1,8 +1,11 @@ -using FishNet.Connection; +using System; +using FishNet.Connection; using FishNet.Documenting; using FishNet.Object.Synchronizing.Internal; using FishNet.Serializing; using System.Runtime.CompilerServices; +using FishNet.Managing; +using Unity.Profiling; using UnityEngine; namespace FishNet.Object @@ -23,6 +26,24 @@ public abstract partial class NetworkBehaviour : MonoBehaviour #endregion #region Private. + + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_InvokeSyncTypeOnStartCallbacks = new("NetworkBehaviour.InvokeSyncTypeOnStartCallbacks(bool)"); + private static readonly ProfilerMarker _pm_InvokeSyncTypeOnStopCallbacks = new("NetworkBehaviour.InvokeSyncTypeOnStopCallbacks(bool)"); + + private static readonly ProfilerMarker _pm_InvokeOnNetwork_Internal = new("NetworkBehaviour.InvokeOnNetwork_Internal(bool)"); + private static readonly ProfilerMarker _pm_OnStartNetwork_Internal = new("NetworkBehaviour.OnStartNetwork_Internal(bool)"); + private static readonly ProfilerMarker _pm_OnStopNetwork_Internal = new("NetworkBehaviour.OnStopNetwork_Internal(bool)"); + + private static readonly ProfilerMarker _pm_OnStartServer_Internal = new("NetworkBehaviour.OnStartServer_Internal(bool)"); + private static readonly ProfilerMarker _pm_OnStopServer_Internal = new("NetworkBehaviour.OnStopServer_Internal(bool)"); + private static readonly ProfilerMarker _pm_OnOwnershipServer_Internal = new("NetworkBehaviour.OnOwnershipServer_Internal(NetworkConnection)"); + + private static readonly ProfilerMarker _pm_OnStartClient_Internal = new("NetworkBehaviour.OnStartClient_Internal(bool)"); + private static readonly ProfilerMarker _pm_OnStopClient_Internal = new("NetworkBehaviour.OnStopClient_Internal(bool)"); + private static readonly ProfilerMarker _pm_OnOwnershipClient_Internal = new("NetworkBehaviour.OnOwnershipClient_Internal(NetworkConnection)"); + #endregion + /// /// True if OnStartNetwork has been called. /// @@ -51,8 +72,20 @@ public virtual void ReadPayload(NetworkConnection connection, Reader reader) { } /// internal void InvokeSyncTypeOnStartCallbacks(bool asServer) { - foreach (SyncBase item in _syncTypes.Values) - item.OnStartCallback(asServer); + using (_pm_InvokeSyncTypeOnStartCallbacks.Auto()) + { + foreach (SyncBase item in _syncTypes.Values) + { + try + { + item.OnStartCallback(asServer); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } + } } /// @@ -60,10 +93,22 @@ internal void InvokeSyncTypeOnStartCallbacks(bool asServer) /// internal void InvokeSyncTypeOnStopCallbacks(bool asServer) { - // if (_syncTypes == null) - // return; - foreach (SyncBase item in _syncTypes.Values) - item.OnStopCallback(asServer); + using (_pm_InvokeSyncTypeOnStopCallbacks.Auto()) + { + // if (_syncTypes == null) + // return; + foreach (SyncBase item in _syncTypes.Values) + { + try + { + item.OnStopCallback(asServer); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } + } } /// @@ -71,31 +116,45 @@ internal void InvokeSyncTypeOnStopCallbacks(bool asServer) /// internal void InvokeOnNetwork_Internal(bool start) { - if (start) + using (_pm_InvokeOnNetwork_Internal.Auto()) { - if (_onStartNetworkCalled) - return; + if (start) + { + if (_onStartNetworkCalled) + return; - if (!gameObject.activeInHierarchy) + if (!gameObject.activeInHierarchy) + { + NetworkInitialize___Early(); + NetworkInitialize___Late(); + } + + OnStartNetwork_Internal(); + } + else { - NetworkInitialize___Early(); - NetworkInitialize___Late(); + if (_onStopNetworkCalled) + return; + OnStopNetwork_Internal(); } - OnStartNetwork_Internal(); - } - else - { - if (_onStopNetworkCalled) - return; - OnStopNetwork_Internal(); } } internal virtual void OnStartNetwork_Internal() { - _onStartNetworkCalled = true; - _onStopNetworkCalled = false; - OnStartNetwork(); + using (_pm_OnStartNetwork_Internal.Auto()) + { + _onStartNetworkCalled = true; + _onStopNetworkCalled = false; + try + { + OnStartNetwork(); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } } /// @@ -107,10 +166,20 @@ public virtual void OnStartNetwork() { } internal virtual void OnStopNetwork_Internal() { - _onStopNetworkCalled = true; - _onStartNetworkCalled = false; + using (_pm_OnStopNetwork_Internal.Auto()) + { + _onStopNetworkCalled = true; + _onStartNetworkCalled = false; - OnStopNetwork(); + try + { + OnStopNetwork(); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } } /// @@ -122,8 +191,18 @@ public virtual void OnStopNetwork() { } internal void OnStartServer_Internal() { - OnStartServerCalled = true; - OnStartServer(); + using (_pm_OnStartServer_Internal.Auto()) + { + OnStartServerCalled = true; + try + { + OnStartServer(); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } } /// @@ -134,9 +213,19 @@ public virtual void OnStartServer() { } internal void OnStopServer_Internal() { - OnStartServerCalled = false; - ReturnRpcLinks(); - OnStopServer(); + using (_pm_OnStopServer_Internal.Auto()) + { + OnStartServerCalled = false; + ReturnRpcLinks(); + try + { + OnStopServer(); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } } /// @@ -146,8 +235,18 @@ public virtual void OnStopServer() { } internal void OnOwnershipServer_Internal(NetworkConnection prevOwner) { - ResetState_Prediction(true); - OnOwnershipServer(prevOwner); + using (_pm_OnOwnershipServer_Internal.Auto()) + { + ResetState_Prediction(true); + try + { + OnOwnershipServer(prevOwner); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } } /// @@ -171,8 +270,18 @@ public virtual void OnDespawnServer(NetworkConnection connection) { } internal void OnStartClient_Internal() { - OnStartClientCalled = true; - OnStartClient(); + using (_pm_OnStartClient_Internal.Auto()) + { + OnStartClientCalled = true; + try + { + OnStartClient(); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } } /// @@ -182,8 +291,18 @@ public virtual void OnStartClient() { } internal void OnStopClient_Internal() { - OnStartClientCalled = false; - OnStopClient(); + using (_pm_OnStopClient_Internal.Auto()) + { + OnStartClientCalled = false; + try + { + OnStopClient(); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } } /// @@ -193,13 +312,23 @@ public virtual void OnStopClient() { } internal void OnOwnershipClient_Internal(NetworkConnection prevOwner) { - // If losing or gaining ownership then clear replicate cache. - if (IsOwner || prevOwner == LocalConnection) + using (_pm_OnOwnershipClient_Internal.Auto()) { - ResetState_Prediction(false); - } + // If losing or gaining ownership then clear replicate cache. + if (IsOwner || prevOwner == LocalConnection) + { + ResetState_Prediction(false); + } - OnOwnershipClient(prevOwner); + try + { + OnOwnershipClient(prevOwner); + } + catch (Exception e) + { + NetworkManager.LogError(e.ToString()); + } + } } /// diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs index b08be1e8..44867ace 100644 --- a/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs +++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs @@ -101,7 +101,7 @@ public byte ComponentIndex /// public override string ToString() { - return $"Name [{gameObject.name}] ComponentId [{ComponentIndex}] NetworkObject Name [{_networkObjectCache.name}] NetworkObject Id [{_networkObjectCache.ObjectId}]"; + return $"Name [{gameObject.name}] ComponentId [{ComponentIndex}] NetworkObject Name [{_networkObjectCache?.name ?? string.Empty}] NetworkObject Id [{_networkObjectCache?.ObjectId ?? -1}]"; } [MakePublic] diff --git a/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs b/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs index 0c6c2e57..f8a12645 100644 --- a/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs +++ b/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs @@ -1,12 +1,22 @@ using FishNet.Connection; using System.Runtime.CompilerServices; +using System; using FishNet.Serializing; using UnityEngine; +using Unity.Profiling; namespace FishNet.Object { public partial class NetworkObject : MonoBehaviour { + #region Types + + public delegate void NetworkObjectCallback(NetworkObject nb); + + public delegate void NetworkObjectInvokeCallback(NetworkObject nb, bool asServer, bool invokeSyncTypeCallbacks); + + #endregion + #region Private. /// /// True if OnStartServer was called. @@ -16,6 +26,133 @@ public partial class NetworkObject : MonoBehaviour /// True if OnStartClient was called. /// private bool _onStartClientCalled; + /// + /// True if OnStartSyncTypeCallbacks was called. + /// + private bool _onStartSyncTypeCallbacksCalled; + + /// + /// True if OnStartServer was called. + /// + public bool OnStartServerCalled + { + get => _onStartServerCalled; + private set + { + if (_onStartServerCalled != value) + { + _onStartServerCalled = value; + if (value) + { + using (_pm_OnStartServerEvent.Auto()) + OnStartServerEvent?.Invoke(this); + } + else + { + using (_pm_OnStopServerEvent.Auto()) + OnStopServerEvent?.Invoke(this); + } + } + } + } + + /// + /// True if OnStartClient was called. + /// + public bool OnStartClientCalled + { + get => _onStartClientCalled; + private set + { + if (_onStartClientCalled != value) + { + _onStartClientCalled = value; + if (value) + { + using (_pm_OnStartClientEvent.Auto()) + OnStartClientEvent?.Invoke(this); + } + else + { + using (_pm_OnStopClientEvent.Auto()) + OnStopClientEvent?.Invoke(this); + } + } + } + } + + /// + /// True if OnStartSyncTypeCallbacks was called. + /// + public bool OnStartSyncTypeCallbacksCalled + { + get => _onStartSyncTypeCallbacksCalled; + private set + { + if (_onStartSyncTypeCallbacksCalled != value) + { + _onStartSyncTypeCallbacksCalled = value; + if (value) + { + using (_pm_OnStartSyncTypeCallbacksEvent.Auto()) + OnStartSyncTypeCallbacks?.Invoke(this); + } + else + { + using (_pm_OnStopSyncTypeCallbacksEvent.Auto()) + OnStopSyncTypeCallbacks?.Invoke(this); + } + } + } + } + + public event NetworkObjectCallback OnStartServerEvent; + public event NetworkObjectCallback OnStopServerEvent; + public event NetworkObjectCallback OnStartClientEvent; + public event NetworkObjectCallback OnStopClientEvent; + public event NetworkObjectCallback OnStartSyncTypeCallbacks; + public event NetworkObjectCallback OnStopSyncTypeCallbacks; + public event NetworkObjectCallback OnServerInitializedEvent; + public event NetworkObjectCallback OnClientInitializedEvent; + public event NetworkObjectCallback OnServerDeinitializedEvent; + public event NetworkObjectCallback OnClientDeinitializedEvent; + public event NetworkObjectInvokeCallback PreInvokeStartCallbacks; + public event NetworkObjectInvokeCallback PostInvokeStartCallbacks; + public event NetworkObjectInvokeCallback PreInvokeStopCallbacks; + public event NetworkObjectInvokeCallback PostInvokeStopCallbacks; + + #region Profiling. + private static readonly ProfilerMarker _pm_OnStartServerEvent = + new("NetworkObject.OnStartServerEvent"); + private static readonly ProfilerMarker _pm_OnStopServerEvent = + new("NetworkObject.OnStopServerEvent"); + private static readonly ProfilerMarker _pm_OnStartClientEvent = + new("NetworkObject.OnStartClientEvent"); + private static readonly ProfilerMarker _pm_OnStopClientEvent = + new("NetworkObject.OnStopClientEvent"); + private static readonly ProfilerMarker _pm_OnStartSyncTypeCallbacksEvent = + new("NetworkObject.OnStartSyncTypeCallbacks"); + private static readonly ProfilerMarker _pm_OnStopSyncTypeCallbacksEvent = + new("NetworkObject.OnStopSyncTypeCallbacks"); + private static readonly ProfilerMarker _pm_OnServerInitializedEvent = + new("NetworkObject.OnServerInitializedEvent"); + private static readonly ProfilerMarker _pm_OnClientInitializedEvent = + new("NetworkObject.OnClientInitializedEvent"); + private static readonly ProfilerMarker _pm_OnServerDeinitializedEvent = + new("NetworkObject.OnServerDeinitializedEvent"); + private static readonly ProfilerMarker _pm_OnClientDeinitializedEvent = + new("NetworkObject.OnClientDeinitializedEvent"); + private static readonly ProfilerMarker _pm_PreInvokeStartCallbacksEvent = + new("NetworkObject.PreInvokeStartCallbacks"); + private static readonly ProfilerMarker _pm_PostInvokeStartCallbacksEvent = + new("NetworkObject.PostInvokeStartCallbacks"); + private static readonly ProfilerMarker _pm_PreInvokeStopCallbacksEvent = + new("NetworkObject.PreInvokeStopCallbacks"); + private static readonly ProfilerMarker _pm_PostInvokeStopCallbacksEvent = + new("NetworkObject.PostInvokeStopCallbacks"); + + #endregion + #endregion // ReSharper disable Unity.PerformanceAnalysis @@ -24,6 +161,9 @@ public partial class NetworkObject : MonoBehaviour /// private void InvokeStartCallbacks(bool asServer, bool invokeSyncTypeCallbacks) { + using (_pm_PreInvokeStartCallbacksEvent.Auto()) + PreInvokeStartCallbacks?.Invoke(this, asServer, invokeSyncTypeCallbacks); + /* Note: When invoking OnOwnership here previous owner will * always be an empty connection, since the object is just * now initializing. */ @@ -41,7 +181,7 @@ private void InvokeStartCallbacks(bool asServer, bool invokeSyncTypeCallbacks) { for (int i = 0; i < NetworkBehaviours.Count; i++) NetworkBehaviours[i].OnStartServer_Internal(); - _onStartServerCalled = true; + OnStartServerCalled = true; for (int i = 0; i < NetworkBehaviours.Count; i++) NetworkBehaviours[i].OnOwnershipServer_Internal(Managing.NetworkManager.EmptyConnection); } @@ -50,15 +190,21 @@ private void InvokeStartCallbacks(bool asServer, bool invokeSyncTypeCallbacks) { for (int i = 0; i < NetworkBehaviours.Count; i++) NetworkBehaviours[i].OnStartClient_Internal(); - _onStartClientCalled = true; + OnStartClientCalled = true; for (int i = 0; i < NetworkBehaviours.Count; i++) NetworkBehaviours[i].OnOwnershipClient_Internal(Managing.NetworkManager.EmptyConnection); } if (invokeSyncTypeCallbacks) + { InvokeOnStartSyncTypeCallbacks(true); + OnStartSyncTypeCallbacksCalled = true; + } InvokeStartCallbacks_Prediction(asServer); + + using (_pm_PostInvokeStartCallbacksEvent.Auto()) + PostInvokeStartCallbacks?.Invoke(this, asServer, invokeSyncTypeCallbacks); } /// @@ -109,22 +255,28 @@ internal void InvokeOnServerDespawn(NetworkConnection conn) /// internal void InvokeStopCallbacks(bool asServer, bool invokeSyncTypeCallbacks) { + using (_pm_PreInvokeStopCallbacksEvent.Auto()) + PreInvokeStopCallbacks?.Invoke(this, asServer, invokeSyncTypeCallbacks); + InvokeStopCallbacks_Prediction(asServer); if (invokeSyncTypeCallbacks) + { InvokeOnStopSyncTypeCallbacks(asServer); + OnStartSyncTypeCallbacksCalled = false; + } - if (asServer && _onStartServerCalled) + if (asServer && OnStartServerCalled) { for (int i = 0; i < NetworkBehaviours.Count; i++) NetworkBehaviours[i].OnStopServer_Internal(); - if (!_onStartClientCalled) + if (!OnStartClientCalled) InvokeOnNetwork(); - _onStartServerCalled = false; + OnStartServerCalled = false; } - else if (!asServer && _onStartClientCalled) + else if (!asServer && OnStartClientCalled) { for (int i = 0; i < NetworkBehaviours.Count; i++) NetworkBehaviours[i].OnStopClient_Internal(); @@ -133,11 +285,14 @@ internal void InvokeStopCallbacks(bool asServer, bool invokeSyncTypeCallbacks) * that means this is still intialized on the server. This would * happen if the object despawned for the clientHost but not on the * server. */ - if (!_onStartServerCalled) + if (!OnStartServerCalled) InvokeOnNetwork(); - _onStartClientCalled = false; + OnStartClientCalled = false; } + + using (_pm_PostInvokeStopCallbacksEvent.Auto()) + PostInvokeStopCallbacks?.Invoke(this, asServer, invokeSyncTypeCallbacks); void InvokeOnNetwork() { @@ -179,4 +334,4 @@ private void InvokeManualOwnershipChange(NetworkConnection prevOwner, bool asSer } } } -} \ No newline at end of file +} diff --git a/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.QOL.cs b/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.QOL.cs index ea647b28..e6749bc2 100644 --- a/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.QOL.cs +++ b/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.QOL.cs @@ -68,7 +68,32 @@ internal void SetIsDestroying(DespawnType? despawnType = null) /// True if this object has been initialized on the client side. /// This is set true right before client start callbacks and after stop callbacks. /// - public bool IsClientInitialized { get; private set; } + private bool _isClientInitialized; + /// + /// True if this object has been initialized on the client side. + /// This is set true right before client start callbacks and after stop callbacks. + /// + public bool IsClientInitialized + { + get => _isClientInitialized; + private set + { + if (_isClientInitialized == value) + return; + + _isClientInitialized = value; + if (value) + { + using (_pm_OnClientInitializedEvent.Auto()) + OnClientInitializedEvent?.Invoke(this); + } + else + { + using (_pm_OnClientDeinitializedEvent.Auto()) + OnClientDeinitializedEvent?.Invoke(this); + } + } + } /// /// True if the client is started and authenticated. This will return true on clientHost even if the object has not initialized yet for the client. /// To check if this object has been initialized for the client use IsClientInitialized. @@ -87,7 +112,32 @@ internal void SetIsDestroying(DespawnType? despawnType = null) /// True if this object has been initialized on the server side. /// This is set true right before server start callbacks and after stop callbacks. /// - public bool IsServerInitialized { get; private set; } + private bool _isServerInitialized; + /// + /// True if this object has been initialized on the server side. + /// This is set true right before server start callbacks and after stop callbacks. + /// + public bool IsServerInitialized + { + get => _isServerInitialized; + private set + { + if (_isServerInitialized == value) + return; + + _isServerInitialized = value; + if (value) + { + using (_pm_OnServerInitializedEvent.Auto()) + OnServerInitializedEvent?.Invoke(this); + } + else + { + using (_pm_OnServerDeinitializedEvent.Auto()) + OnServerDeinitializedEvent?.Invoke(this); + } + } + } /// /// True if the server is active. This will return true on clientHost even if the object is being deinitialized on the server. /// To check if this object has been initialized for the server use IsServerInitialized. @@ -401,4 +451,4 @@ public void SetLocalOwnership(NetworkConnection caller, bool recursive) public void UnregisterInstance() where T : UnityEngine.Component => NetworkManager.UnregisterInstance(); #endregion } -} \ No newline at end of file +} diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs index 3be6a8db..a74c956c 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs @@ -28,8 +28,7 @@ public class SyncBase /// /// The settings for this SyncVar. /// - [MakePublic] - internal SyncTypeSettings Settings; + [MakePublic] public SyncTypeSettings Settings; /// /// How often updates may send. /// diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs index 3dbdbc8b..564e6ef5 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs @@ -446,6 +446,11 @@ protected internal override void ResetState(bool asServer) foreach (KeyValuePair item in _initialValues) Collection[item.Key] = item.Value; } + + if (asServer) + _serverOnChanges.Clear(); + else + _clientOnChanges.Clear(); } /// diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSet.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSet.cs index aab05229..d6aee36c 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSet.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSet.cs @@ -415,6 +415,11 @@ protected internal override void ResetState(bool asServer) foreach (T item in _initialValues) Collection.Add(item); } + + if (asServer) + _serverOnChanges.Clear(); + else + _clientOnChanges.Clear(); } /// diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs index 9254ddad..510b785c 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs @@ -460,6 +460,9 @@ protected internal override void ResetState(bool asServer) foreach (T item in _initialValues) Collection.Add(item); } + + if (asServer) _serverOnChanges.Clear(); + else _clientOnChanges.Clear(); } /// diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs index 06a86a33..864fdcdd 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs @@ -10,7 +10,7 @@ namespace FishNet.Object.Synchronizing { - internal interface ISyncVar { } + public interface ISyncVar { } [APIExclude] [System.Serializable] @@ -468,6 +468,9 @@ protected internal override void ResetState(bool asServer) _value = _initialValue; _valueSetAfterInitialized = false; } + + if (asServer) _serverOnChange = null; + else _clientOnChange = null; } } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef index 438b416f..751eaca6 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef @@ -2,7 +2,10 @@ "name": "GameKit.Dependencies", "rootNamespace": "", "references": [ - "GUID:6055be8ebefd69e48b49212b09b47b2f" + "GUID:6055be8ebefd69e48b49212b09b47b2f", + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:2665a8d13d1b3f18800f46e256720795", + "GUID:e0cd26848372d4e5c891c569017e11f1" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs index b6a56652..d7d65070 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs @@ -8,7 +8,7 @@ public static class DictionaryFN /// Uses a hacky way to TryGetValue on a dictionary when using IL2CPP and on mobile. /// This is to support older devices that don't properly handle IL2CPP builds. /// - public static bool TryGetValueIL2CPP(this IDictionary dict, TKey key, out TValue value) + public static bool TryGetValueIL2CPP(this IReadOnlyDictionary dict, TKey key, out TValue value) { #if ENABLE_IL2CPP && UNITY_IOS || UNITY_ANDROID if (dict.ContainsKey(key)) @@ -30,7 +30,7 @@ public static bool TryGetValueIL2CPP(this IDictionary /// - public static List ValuesToList(this IDictionary dict, bool useCache) + public static List ValuesToList(this IReadOnlyDictionary dict, bool useCache) { List result = useCache ? CollectionCaches.RetrieveList() : new(dict.Count); @@ -43,7 +43,7 @@ public static List ValuesToList(this IDictionary /// Adds values to a list. /// - public static void ValuesToList(this IDictionary dict, ref List result, bool clearLst) + public static void ValuesToList(this IReadOnlyDictionary dict, ref List result, bool clearLst) { if (clearLst) result.Clear(); @@ -55,7 +55,7 @@ public static void ValuesToList(this IDictionary dic /// /// Returns keys as a list. /// - public static List KeysToList(this IDictionary dict, bool useCache) + public static List KeysToList(this IReadOnlyDictionary dict, bool useCache) { List result = useCache ? CollectionCaches.RetrieveList() : new(dict.Count); @@ -68,7 +68,7 @@ public static List KeysToList(this IDictionary /// /// Adds keys to a list. /// - public static void KeysToList(this IDictionary dict, ref List result, bool clearLst) + public static void KeysToList(this IReadOnlyDictionary dict, ref List result, bool clearLst) { result.Clear(); diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Guids.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Guids.cs new file mode 100644 index 00000000..cb7d3635 --- /dev/null +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Guids.cs @@ -0,0 +1,10 @@ +namespace GameKit.Dependencies.Utilities +{ + public static class Guids + { + /// + /// A buffer convert data and discard. + /// + public static byte[] Buffer = new byte[16]; + } +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Serializing/Reader.cs b/Assets/FishNet/Runtime/Serializing/Reader.cs index 935b1122..aa24f747 100644 --- a/Assets/FishNet/Runtime/Serializing/Reader.cs +++ b/Assets/FishNet/Runtime/Serializing/Reader.cs @@ -97,10 +97,6 @@ public enum DataSource /// Used to convert bytes to a string. /// private static readonly UTF8Encoding _encoding = new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); - /// - /// Used to convert bytes to a GUID. - /// - private static readonly byte[] _guidBuffer = new byte[16]; #endregion public Reader() { } @@ -899,7 +895,7 @@ public byte[] ReadUInt8ArrayAllocated(int count) [DefaultReader] public Guid ReadGuid() { - byte[] buffer = _guidBuffer; + byte[] buffer = Guids.Buffer; ReadUInt8Array(ref buffer, 16); return new(buffer); } diff --git a/Assets/FishNet/Runtime/Serializing/Writer.cs b/Assets/FishNet/Runtime/Serializing/Writer.cs index 39ce4904..5a0a4ced 100644 --- a/Assets/FishNet/Runtime/Serializing/Writer.cs +++ b/Assets/FishNet/Runtime/Serializing/Writer.cs @@ -857,6 +857,17 @@ public void WriteMatrix4x4Unpacked(Matrix4x4 value) /// /// [DefaultWriter] + public void WriteGuid(Guid value) + { + byte[] data = Guids.Buffer; + value.TryWriteBytes(data); + WriteUInt8Array(data, 0, data.Length); + } + + /// + /// Writes a Guid. + /// + /// public void WriteGuidAllocated(Guid value) { byte[] data = value.ToByteArray(); diff --git a/Assets/FishNet/Runtime/Serializing/WriterExtensions.cs b/Assets/FishNet/Runtime/Serializing/WriterExtensions.cs index fcd41774..b1ecaeab 100644 --- a/Assets/FishNet/Runtime/Serializing/WriterExtensions.cs +++ b/Assets/FishNet/Runtime/Serializing/WriterExtensions.cs @@ -75,7 +75,7 @@ // // public static void WriteRay(this Writer writer, Ray value) => writer.WriteRay(value); // // public static void WriteRay2D(this Writer writer, Ray2D value) => writer.WriteRay2D(value); // // public static void WriteMatrix4x4(this Writer writer, Matrix4x4 value) => writer.WriteMatrix4x4(value); -// // public static void WriteGuidAllocated(this Writer writer, System.Guid value) => writer.WriteGuidAllocated(value); +// // public static void WriteGuid(this Writer writer, System.Guid value) => writer.WriteGuid(value); // // public static void WriteGameObject(this Writer writer, GameObject value) => writer.WriteGameObject(value); // // public static void WriteTransform(this Writer writer, Transform value) => writer.WriteTransform(value); // // public static void WriteNetworkObject(this Writer writer, NetworkObject value) => writer.WriteNetworkObject(value); diff --git a/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs b/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs index 301b71e2..b92f9bd9 100644 --- a/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs +++ b/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs @@ -3,6 +3,7 @@ using FishNet.Object; using System.Runtime.CompilerServices; using UnityEngine; +using UnityEngine.Jobs; namespace FishNet.Utility.Extension { @@ -10,48 +11,124 @@ namespace FishNet.Utility.Extension public static class TransformFN { /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets correct values of Vector3 pos and Quaternion rot + /// + public static void GetCorrectLocalPositionAndRotation(this TransformAccess t, out Vector3 pos, out Quaternion rot) + { + // https://issuetracker.unity3d.com/issues/wrong-position-and-rotation-values-are-returned-when-using-transformaccess-dot-getlocalpositionandrotation + pos = t.localPosition; + rot = t.localRotation; + } + + /// + /// Sets correct values of Vector3 pos and Quaternion rot + /// + public static void SetCorrectLocalPositionAndRotation(this TransformAccess t, Vector3 pos, Quaternion rot) + { + // https://issuetracker.unity3d.com/issues/wrong-position-and-rotation-values-are-returned-when-using-transformaccess-dot-getlocalpositionandrotation + t.localPosition = pos; + t.localRotation = rot; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformProperties GetWorldProperties(this Transform t) { - TransformProperties tp = new(t.position, t.rotation, t.localScale); + t.GetPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformProperties GetWorldProperties(this TransformAccess t) + { + t.GetPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); return tp; } /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformProperties GetWorldProperties(this Transform t, TransformProperties offset) { - TransformProperties tp = new(t.position, t.rotation, t.localScale); + t.GetPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); + tp.Add(offset); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformProperties GetWorldProperties(this TransformAccess t, TransformProperties offset) + { + t.GetPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); tp.Add(offset); return tp; } /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformPropertiesCls GetWorldPropertiesCls(this Transform t) { - TransformPropertiesCls tp = new(t.position, t.rotation, t.localScale); + t.GetPositionAndRotation(out var pos, out var rot); + TransformPropertiesCls tp = new(pos, rot, t.localScale); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformPropertiesCls GetWorldPropertiesCls(this TransformAccess t) + { + t.GetPositionAndRotation(out var pos, out var rot); + TransformPropertiesCls tp = new(pos, rot, t.localScale); return tp; } /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformProperties GetLocalProperties(this Transform t) { - TransformProperties tp = new(t.localPosition, t.localRotation, t.localScale); + t.GetLocalPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformProperties GetLocalProperties(this TransformAccess t) + { + t.GetCorrectLocalPositionAndRotation(out var pos, out var rot); + TransformProperties tp = new(pos, rot, t.localScale); return tp; } /// - /// Sets values of TransformProperties to a transforms world properties. + /// Gets values of TransformProperties from the transforms world properties. /// public static TransformPropertiesCls GetLocalPropertiesCls(this Transform t) { - TransformPropertiesCls tp = new(t.localPosition, t.localRotation, t.localScale); + t.GetLocalPositionAndRotation(out var pos, out var rot); + TransformPropertiesCls tp = new(pos, rot, t.localScale); + return tp; + } + + /// + /// Gets values of TransformProperties from the transforms world properties. + /// + public static TransformPropertiesCls GetLocalPropertiesCls(this TransformAccess t) + { + t.GetCorrectLocalPositionAndRotation(out var pos, out var rot); + TransformPropertiesCls tp = new(pos, rot, t.localScale); return tp; } @@ -70,8 +147,10 @@ public static void SetTransformOffsets(this Transform t, Transform target, ref V { if (target == null) return; - pos = target.position - t.position; - rot = target.rotation * Quaternion.Inverse(t.rotation); + t.GetPositionAndRotation(out var tPos, out var tRot); + target.GetPositionAndRotation(out var targetPos, out var targetRot); + pos = targetPos - tPos; + rot = targetRot * Quaternion.Inverse(tRot); } /// @@ -83,7 +162,9 @@ public static TransformProperties GetTransformOffsets(this Transform t, Transfor if (target == null) return default; - return new(target.position - t.position, target.rotation * Quaternion.Inverse(t.rotation), target.localScale - t.localScale); + t.GetPositionAndRotation(out var tPos, out var tRot); + target.GetPositionAndRotation(out var targetPos, out var targetRot); + return new(targetPos - tPos, targetRot * Quaternion.Inverse(tRot), target.localScale - t.localScale); } /// @@ -91,8 +172,16 @@ public static TransformProperties GetTransformOffsets(this Transform t, Transfor /// public static void SetLocalProperties(this Transform t, TransformPropertiesCls tp) { - t.localPosition = tp.Position; - t.localRotation = tp.Rotation; + t.SetLocalPositionAndRotation(tp.Position, tp.Rotation); + t.localScale = tp.LocalScale; + } + + /// + /// Sets a transform to local properties. + /// + public static void SetLocalProperties(this TransformAccess t, TransformPropertiesCls tp) + { + t.SetCorrectLocalPositionAndRotation(tp.Position, tp.Rotation); t.localScale = tp.LocalScale; } @@ -101,8 +190,16 @@ public static void SetLocalProperties(this Transform t, TransformPropertiesCls t /// public static void SetLocalProperties(this Transform t, TransformProperties tp) { - t.localPosition = tp.Position; - t.localRotation = tp.Rotation; + t.SetLocalPositionAndRotation(tp.Position, tp.Rotation); + t.localScale = tp.Scale; + } + + /// + /// Sets a transform to local properties. + /// + public static void SetLocalProperties(this TransformAccess t, TransformProperties tp) + { + t.SetCorrectLocalPositionAndRotation(tp.Position, tp.Rotation); t.localScale = tp.Scale; } @@ -111,8 +208,16 @@ public static void SetLocalProperties(this Transform t, TransformProperties tp) /// public static void SetWorldProperties(this Transform t, TransformPropertiesCls tp) { - t.position = tp.Position; - t.rotation = tp.Rotation; + t.SetPositionAndRotation(tp.Position, tp.Rotation); + t.localScale = tp.LocalScale; + } + + /// + /// Sets a transform to world properties. + /// + public static void SetWorldProperties(this TransformAccess t, TransformPropertiesCls tp) + { + t.SetPositionAndRotation(tp.Position, tp.Rotation); t.localScale = tp.LocalScale; } @@ -121,8 +226,16 @@ public static void SetWorldProperties(this Transform t, TransformPropertiesCls t /// public static void SetWorldProperties(this Transform t, TransformProperties tp) { - t.position = tp.Position; - t.rotation = tp.Rotation; + t.SetPositionAndRotation(tp.Position, tp.Rotation); + t.localScale = tp.Scale; + } + + /// + /// Sets a transform to world properties. + /// + public static void SetWorldProperties(this TransformAccess t, TransformProperties tp) + { + t.SetPositionAndRotation(tp.Position, tp.Rotation); t.localScale = tp.Scale; } @@ -131,8 +244,15 @@ public static void SetWorldProperties(this Transform t, TransformProperties tp) /// public static void SetLocalPositionAndRotation(this Transform t, Vector3 pos, Quaternion rot) { - t.localPosition = pos; - t.localRotation = rot; + t.SetLocalPositionAndRotation(pos, rot); + } + + /// + /// Sets local position and rotation for a transform. + /// + public static void SetLocalPositionAndRotation(this TransformAccess t, Vector3 pos, Quaternion rot) + { + t.SetCorrectLocalPositionAndRotation(pos, rot); } /// @@ -140,8 +260,16 @@ public static void SetLocalPositionAndRotation(this Transform t, Vector3 pos, Qu /// public static void SetLocalPositionRotationAndScale(this Transform t, Vector3 pos, Quaternion rot, Vector3 scale) { - t.localPosition = pos; - t.localRotation = rot; + t.SetLocalPositionAndRotation(pos, rot); + t.localScale = scale; + } + + /// + /// Sets local position, rotation, and scale for a transform. + /// + public static void SetLocalPositionRotationAndScale(this TransformAccess t, Vector3 pos, Quaternion rot, Vector3 scale) + { + t.SetCorrectLocalPositionAndRotation(pos, rot); t.localScale = scale; } @@ -151,8 +279,29 @@ public static void SetLocalPositionRotationAndScale(this Transform t, Vector3 po public static void SetLocalPositionRotationAndScale(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale) { if (nullablePos.HasValue) - t.localPosition = nullablePos.Value; - if (nullableRot.HasValue) + { + if (nullableRot.HasValue) + t.SetLocalPositionAndRotation(nullablePos.Value, nullableRot.Value); + else t.localPosition = nullablePos.Value; + } + else if (nullableRot.HasValue) + t.localRotation = nullableRot.Value; + if (nullableScale.HasValue) + t.localScale = nullableScale.Value; + } + + /// + /// Sets local position, rotation, and scale using nullables for a transform. If a value is null then that property is skipped. + /// + public static void SetLocalPositionRotationAndScale(this TransformAccess t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale) + { + if (nullablePos.HasValue) + { + if (nullableRot.HasValue) + t.SetCorrectLocalPositionAndRotation(nullablePos.Value, nullableRot.Value); + else t.localPosition = nullablePos.Value; + } + else if (nullableRot.HasValue) t.localRotation = nullableRot.Value; if (nullableScale.HasValue) t.localScale = nullableScale.Value; @@ -164,8 +313,29 @@ public static void SetLocalPositionRotationAndScale(this Transform t, Vector3? n public static void SetWorldPositionRotationAndScale(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale) { if (nullablePos.HasValue) - t.position = nullablePos.Value; - if (nullableRot.HasValue) + { + if (nullableRot.HasValue) + t.SetPositionAndRotation(nullablePos.Value, nullableRot.Value); + else t.position = nullablePos.Value; + } + else if (nullableRot.HasValue) + t.rotation = nullableRot.Value; + if (nullableScale.HasValue) + t.localScale = nullableScale.Value; + } + + /// + /// Sets world position, rotation, and scale using nullables for a transform. If a value is null then that property is skipped. + /// + public static void SetWorldPositionRotationAndScale(this TransformAccess t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale) + { + if (nullablePos.HasValue) + { + if (nullableRot.HasValue) + t.SetPositionAndRotation(nullablePos.Value, nullableRot.Value); + else t.position = nullablePos.Value; + } + else if (nullableRot.HasValue) t.rotation = nullableRot.Value; if (nullableScale.HasValue) t.localScale = nullableScale.Value; @@ -176,8 +346,56 @@ public static void SetWorldPositionRotationAndScale(this Transform t, Vector3? n /// public static void OutLocalPropertyValues(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale) { - pos = nullablePos == null ? t.localPosition : nullablePos.Value; - rot = nullableRot == null ? t.localRotation : nullableRot.Value; + if (!nullablePos.HasValue) + { + if (!nullableRot.HasValue) + t.GetLocalPositionAndRotation(out pos, out rot); + else + { + pos = t.localPosition; + rot = nullableRot.Value; + } + } + else if (!nullableRot.HasValue) + { + pos = nullablePos.Value; + rot = t.localRotation; + } + else + { + pos = nullablePos.Value; + rot = nullableRot.Value; + } + + scale = nullableScale == null ? t.localScale : nullableScale.Value; + } + + /// + /// Oututs properties to use for a transform. When a nullable property has value that value is used, otherwise the transforms current property is used. + /// + public static void OutLocalPropertyValues(this TransformAccess t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale) + { + if (!nullablePos.HasValue) + { + if (!nullableRot.HasValue) + t.GetCorrectLocalPositionAndRotation(out pos, out rot); + else + { + pos = t.localPosition; + rot = nullableRot.Value; + } + } + else if (!nullableRot.HasValue) + { + pos = nullablePos.Value; + rot = t.localRotation; + } + else + { + pos = nullablePos.Value; + rot = nullableRot.Value; + } + scale = nullableScale == null ? t.localScale : nullableScale.Value; } @@ -186,8 +404,56 @@ public static void OutLocalPropertyValues(this Transform t, Vector3? nullablePos /// public static void OutWorldPropertyValues(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale) { - pos = nullablePos == null ? t.position : nullablePos.Value; - rot = nullableRot == null ? t.rotation : nullableRot.Value; + if (!nullablePos.HasValue) + { + if (!nullableRot.HasValue) + t.GetPositionAndRotation(out pos, out rot); + else + { + pos = t.position; + rot = nullableRot.Value; + } + } + else if (!nullableRot.HasValue) + { + pos = nullablePos.Value; + rot = t.rotation; + } + else + { + pos = nullablePos.Value; + rot = nullableRot.Value; + } + + scale = nullableScale == null ? t.localScale : nullableScale.Value; + } + + /// + /// Oututs properties to use for a transform. When a nullable property has value that value is used, otherwise the transforms current property is used. + /// + public static void OutWorldPropertyValues(this TransformAccess t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale) + { + if (!nullablePos.HasValue) + { + if (!nullableRot.HasValue) + t.GetPositionAndRotation(out pos, out rot); + else + { + pos = t.position; + rot = nullableRot.Value; + } + } + else if (!nullableRot.HasValue) + { + pos = nullablePos.Value; + rot = t.rotation; + } + else + { + pos = nullablePos.Value; + rot = nullableRot.Value; + } + scale = nullableScale == null ? t.localScale : nullableScale.Value; } }