From 195c288e140387c84773c77c84cd2385262a8aae Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:19:05 +0300 Subject: [PATCH 01/23] feat: first part of QOLs --- .../Runtime/CodeGenerating/Attributes.cs | 2 +- .../Connection/NetworkConnection.QOL.cs | 8 +- .../NetworkAnimator/NetworkAnimator.cs | 93 +- .../NetworkTransform/NetworkTransform.cs | 922 ++++++++++-------- .../Dependencies/Utilities/Dictionaries.cs | 10 +- .../Runtime/Utility/Extension/Transforms.cs | 332 ++++++- 6 files changed, 896 insertions(+), 471 deletions(-) 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/Generated/Component/NetworkAnimator/NetworkAnimator.cs b/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs index 4c312763..71670295 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."); @@ -668,6 +701,12 @@ private void InitializeOnce() foreach (AnimatorControllerParameter item in _animator.parameters) { bool process = !_animator.IsParameterControlledByCurve(item.name); + //PROSTART + /* This is done in a weird way for processing + * to work with the pro tool stripper. */ + if (IgnoredParameters.Contains(item.name)) + process = false; + //PROEND if (process) { //Over 250 parameters; who would do this!? @@ -708,6 +747,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 +894,7 @@ private void CheckSendToClients() SendSegment(new(buffer, 0, bufferLength)); } + //Reset client auth buffer. _clientAuthoritativeUpdates.Reset(); } @@ -978,6 +1027,7 @@ private bool AnimatorUpdated(out ArraySegment updatedBytes, bool forceAll _writer.WriteUInt8Unpacked(_triggerUpdates[i].ParameterIndex); _writer.WriteBoolean(_triggerUpdates[i].Setting); } + _triggerUpdates.Clear(); /* States. */ @@ -1069,7 +1119,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 +1275,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 +1285,7 @@ public void SendAll() /// /// Plays a state. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Play(string name) { Play(Animator.StringToHash(name)); @@ -1242,6 +1294,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 +1304,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 +1313,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 +1322,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 +1345,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 +1354,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 +1364,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 +1394,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 +1427,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 +1458,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 +1470,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 +1480,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 +1490,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 5e8daa71..1f2de518 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 @@ -371,13 +385,13 @@ public void InitializeState() { } Rotation = AutoPackType.Packed, Scale = AutoPackType.Unpacked }; - /// /// True to use scaled deltaTime when smoothing. /// [Tooltip("True to use scaled deltaTime when smoothing.")] [SerializeField] private bool _useScaledTime = true; /// + /// /// How many ticks to interpolate. /// [Tooltip("How many ticks to interpolate.")] @@ -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. /// @@ -646,19 +677,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. /// @@ -779,7 +797,6 @@ private void TryClearGoalDatas_OwnershipChange(NetworkConnection prevOwner, bool * follow the queue. */ } - private void TimeManager_OnUpdate() { using (_pm_OnUpdate.Auto()) @@ -1026,6 +1043,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. @@ -1044,6 +1062,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()) @@ -1063,9 +1082,12 @@ 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) { +#if UNITY_EDITOR || DEVELOPMENT_BUILD using (_pm_ForceSend1.Auto()) +#endif { /* If there is a pending delayed force send then queue it * immediately and set a new delay tick. */ @@ -1078,6 +1100,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()) @@ -1109,6 +1132,7 @@ public void SetInterval(byte value) /// Updates the interval value. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetIntervalInternal(byte value) { value = (byte)Mathf.Max(value, 1); @@ -1165,17 +1189,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); } } @@ -1192,220 +1218,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; + bool canUpdateData = dataToUpdate != null; + if (canUpdateData && changed != ChangedDelta.Unset) + dataToUpdate.SetIsDefaultToFalse(); - if (canUpdateData) - dataToUpdate.Position.x = original; - - 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); - } - } - - //PositionY - if (ChangedContains(changed, ChangedDelta.PositionY)) - { - original = t.localPosition.y; + 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; - 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; + + if (canUpdateData) + dataToUpdate.Scale.y = original; + + 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; - //Insert flags. - writer.InsertUInt8Unpacked((byte)flagsA, startIndexA); + 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; - bool ChangedContains(ChangedDelta whole, ChangedDelta part) + 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; } @@ -1501,29 +1533,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; } @@ -1614,118 +1649,147 @@ private void MoveToTarget(float delta) { using (_pm_MoveToTarget.Auto()) { - if (_currentGoalData == null) - return; - - //Cannot move if neither is active. - if (!IsServerInitialized && !IsClientInitialized) - return; - - //If client auth and the owner don't move towards target. - if (_clientAuthoritative) - { - if (IsOwner || TakenOwnership) + if (_currentGoalData == null) return; - } - else - { - //If not client authoritative, is owner, and don't sync to owner. - if (IsOwner && !_sendToOwner) - return; - } - - //True if not client controlled. - bool controlledByClient = _clientAuthoritative && Owner.IsActive; - //If not controlled by client and is server then no reason to move. - if (!controlledByClient && IsServerInitialized) - return; - /* Once here it's safe to assume the object will be moving. - * Any checks which would stop it from moving be it client - * auth and owner, or server controlled and server, ect, - * would have already been run. */ - TransformData td = _currentGoalData.Transforms; - RateData rd = _currentGoalData.Rates; - - //Set parent. - if (_synchronizeParent) - SetParent(td.ParentBehaviour, rd); - - float multiplier = 1f; - int queueCount = _goalDataQueue.Count; - //Increase move rate slightly if over queue count. - if (queueCount > _interpolation + 1) - multiplier += 0.05f; + //Cannot move if neither is active. + if (!IsServerInitialized && !IsClientInitialized) + return; - //Rate to update. Changes per property. - float rate; - Transform t = _cachedTransform; + //If client auth and the owner don't move towards target. + if (_clientAuthoritative) + { + if (IsOwner || TakenOwnership) + return; + } + else + { + //If not client authoritative, is owner, and don't sync to owner. + if (IsOwner && !_sendToOwner) + return; + } - //Snap any bits of the transform that should be. - SnapProperties(td); + //True if not client controlled. + bool controlledByClient = _clientAuthoritative && Owner.IsActive; + //If not controlled by client and is server then no reason to move. + if (!controlledByClient && IsServerInitialized) + return; - //Position. - if (_synchronizePosition) - { - rate = rd.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); - } + /* Once here it's safe to assume the object will be moving. + * Any checks which would stop it from moving be it client + * auth and owner, or server controlled and server, ect, + * would have already been run. */ + TransformData td = _currentGoalData.Transforms; + RateData rd = _currentGoalData.Rates; + + //Set parent. + if (_synchronizeParent) + SetParent(td.ParentBehaviour, rd); + + float multiplier = 1f; + int queueCount = _goalDataQueue.Count; + //Increase move rate slightly if over queue count. + if (queueCount > _interpolation + 1) + multiplier += 0.05f; + + //Rate to update. Changes per property. + float rate; + Transform t = _cachedTransform; + t.GetLocalPositionAndRotation(out Vector3 localPosition, out Quaternion localRotation); + Vector3 localScale = t.localScale; - //Rotation. - if (_synchronizeRotation) - { - rate = rd.Rotation; - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (rate == -1f) - t.localRotation = td.Rotation; - else - t.localRotation = Quaternion.RotateTowards(t.localRotation, td.Rotation, rate * delta); - } + //Snap any bits of the transform that should be. + SnapProperties(td); - //Scale. - if (_synchronizeScale) - { - rate = rd.Scale; - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (rate == -1f) - t.localScale = td.Scale; - else - t.localScale = Vector3.MoveTowards(t.localScale, td.Scale, rate * delta); - } + //Position. + if (_synchronizePosition) + { + rate = rd.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(localPosition, posGoal, rate * delta * multiplier); + } - float timeRemaining = rd.TimeRemaining - delta * multiplier; - if (timeRemaining < -delta) - timeRemaining = -delta; - rd.TimeRemaining = timeRemaining; + //Rotation. + if (_synchronizeRotation) + { + rate = rd.Rotation; + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (rate == -1f) + t.localRotation = td.Rotation; + else + t.localRotation = Quaternion.RotateTowards(localRotation, td.Rotation, rate * delta); + } - if (rd.TimeRemaining <= 0f) - { - float leftOver = Mathf.Abs(rd.TimeRemaining); - //If more in buffer then run next buffer. - if (queueCount > 0) + //Scale. + if (_synchronizeScale) { - SetCurrentGoalData(_goalDataQueue.Dequeue()); - if (leftOver > 0f) - MoveToTarget(leftOver); + rate = rd.Scale; + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (rate == -1f) + t.localScale = td.Scale; + else + t.localScale = Vector3.MoveTowards(localScale, td.Scale, rate * delta); } - //No more in buffer, see if can extrapolate. - else + + float timeRemaining = rd.TimeRemaining - delta * multiplier; + if (timeRemaining < -delta) + timeRemaining = -delta; + rd.TimeRemaining = timeRemaining; + + if (rd.TimeRemaining <= 0f) { - /* 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 - * and it's thrown off. */ - if (!HasChanged(td)) - _currentGoalData = null; - OnInterpolationComplete?.Invoke(); + float leftOver = Mathf.Abs(rd.TimeRemaining); + //If more in buffer then run next buffer. + if (queueCount > 0) + { + SetCurrentGoalData(_goalDataQueue.Dequeue()); + if (leftOver > 0f) + MoveToTarget(leftOver); + } + //No more in buffer, see if can extrapolate. + else + { + //PROSTART + //Can extrapolate. + if (td.ExtrapolationState == TransformData.ExtrapolateState.Available) + { + rd.TimeRemaining = (float)(_extrapolation * _timeManager.TickDelta); + td.ExtrapolationState = TransformData.ExtrapolateState.Active; + if (leftOver > 0f) + MoveToTarget(leftOver); } - } + //Ran out of extrapolate. + else if (td.ExtrapolationState == TransformData.ExtrapolateState.Active) + { + rd.TimeRemaining = (float)(_extrapolation * _timeManager.TickDelta); + td.ExtrapolationState = TransformData.ExtrapolateState.Disabled; + if (leftOver > 0f) + MoveToTarget(leftOver); + } + //Extrapolation has ended or was never enabled. + else + { + //PROEND + /* 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 + * and it's thrown off. */ + if (!HasChanged(td)) + _currentGoalData = null; + OnInterpolationComplete?.Invoke(); + //PROSTART + } + //PROEND + } + } } } @@ -1748,7 +1812,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. */ @@ -1765,7 +1830,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; } @@ -1804,7 +1870,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); @@ -1861,7 +1928,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. @@ -1878,10 +1946,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; } @@ -1889,6 +1959,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; @@ -1926,6 +1997,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. @@ -1945,39 +2017,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; - - if (changed != ChangedDelta.Unset && ParentBehaviour != null) - changed |= ChangedDelta.Nested; - - //If added scale or childed then also add extended. - if (startChanged != changed) - changed |= ChangedDelta.Extended; - - return 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; + + if (changed != ChangedDelta.Unset && ParentBehaviour != null) + changed |= ChangedDelta.Nested; + + //If added scale or childed then also add extended. + if (startChanged != changed) + changed |= ChangedDelta.Extended; + + return changed; + } } #endregion @@ -1993,36 +2067,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; } } @@ -2030,6 +2106,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. @@ -2192,7 +2269,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; } @@ -2201,7 +2279,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; @@ -2213,6 +2292,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; @@ -2236,12 +2316,22 @@ 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; - } + //PROSTART + //Teleports cannot extrapolate. + if (_extrapolation == 0 || !_synchronizePosition || channel == Channel.Reliable || next.Position == prev.Position) + return; + + Vector3 offet = (next.Position - prev.Position) * _extrapolation; + next.ExtrapolatedPosition = next.Position + offet; + next.ExtrapolationState = TransformData.ExtrapolateState.Available; + //PROEND + } /// /// Updates a client with transform data. @@ -2308,108 +2398,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/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/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; } } From f3b0638136e8112d0f7776d651e2e842d4d245d6 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:23:52 +0300 Subject: [PATCH 02/23] feat second pard of QOLs [NonSerialized] required because in other case it cause infinity errors in Logs when you select networkmanager in hierarchy ManagedObjects now observable. --- .../NetworkAnimator/NetworkAnimator.cs | 6 - .../NetworkAnimator/NetworkAnimator.cs.bak | 1621 +++++++++++++++++ .../Runtime/Managing/Client/ClientManager.cs | 2 +- .../Managing/Client/Object/ClientObjects.cs | 2 +- .../Managing/Client/Object/ObjectCaching.cs | 2 +- .../Runtime/Managing/Object/ManagedObjects.cs | 41 +- .../Managing/Server/ServerManager.QOL.cs | 15 +- .../Runtime/Managing/Server/ServerManager.cs | 2 +- .../Runtime/Managing/Timing/TimeManager.cs | 184 +- .../Managing/Transporting/TransportManager.cs | 2 +- 10 files changed, 1774 insertions(+), 103 deletions(-) create mode 100644 Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs.bak diff --git a/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs b/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs index 71670295..1bd718e9 100644 --- a/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs +++ b/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs @@ -701,12 +701,6 @@ private void InitializeOnce() foreach (AnimatorControllerParameter item in _animator.parameters) { bool process = !_animator.IsParameterControlledByCurve(item.name); - //PROSTART - /* This is done in a weird way for processing - * to work with the pro tool stripper. */ - if (IgnoredParameters.Contains(item.name)) - process = false; - //PROEND if (process) { //Over 250 parameters; who would do this!? diff --git a/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs.bak b/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs.bak new file mode 100644 index 00000000..822d0ccd --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs.bak @@ -0,0 +1,1621 @@ +#if UNITY_EDITOR || DEVELOPMENT_BUILD +#define DEVELOPMENT +#endif +using FishNet.Component.Transforming; +using FishNet.Connection; +using FishNet.Documenting; +using FishNet.Managing.Logging; +using FishNet.Managing.Server; +using FishNet.Object; +using FishNet.Serializing; +using FishNet.Utility; +using FishNet.Utility.Performance; +using GameKit.Dependencies.Utilities; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using FishNet.Managing; +using UnityEngine; +using UnityEngine.Profiling; +using Unity.Profiling; +using TimeManagerCls = FishNet.Managing.Timing.TimeManager; + +namespace FishNet.Component.Animating +{ + [AddComponentMenu("FishNet/Component/NetworkAnimator")] + public sealed class NetworkAnimator : NetworkBehaviour + { + #region Types. + /// + /// Data received from the server. + /// + private struct ReceivedServerData + { + /// + /// Gets an Arraysegment of received data. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArraySegment GetArraySegment() => new(_data, 0, _length); + + /// + /// How much data written. + /// + private int _length; + /// + /// Buffer which contains data. + /// + private byte[] _data; + + public ReceivedServerData(ArraySegment segment) + { + _length = segment.Count; + _data = ByteArrayPool.Retrieve(_length); + Buffer.BlockCopy(segment.Array, segment.Offset, _data, 0, _length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + if (_data != null) + ByteArrayPool.Store(_data); + } + } + + private struct StateChange + { + /// + /// Frame which the state was changed. + /// + public int FrameCount; + /// + /// True if a crossfade. + /// + public bool IsCrossfade; + /// + /// Hash to crossfade into. + /// + public int Hash; + /// + /// True if using FixedTime. + /// + public bool FixedTime; + /// + /// Duration of crossfade. + /// + public float DurationTime; + /// + /// Offset time of crossfade. + /// + public float OffsetTime; + /// + /// Normalized transition time of crossfade. + /// + public float NormalizedTransitionTime; + + public StateChange(int frame) + { + FrameCount = frame; + IsCrossfade = default; + Hash = default; + FixedTime = default; + DurationTime = default; + OffsetTime = default; + NormalizedTransitionTime = default; + } + + public StateChange(int frame, int hash, bool fixedTime, float duration, float offset, float normalizedTransition) + { + FrameCount = frame; + IsCrossfade = true; + Hash = hash; + FixedTime = fixedTime; + DurationTime = duration; + OffsetTime = offset; + NormalizedTransitionTime = normalizedTransition; + } + } + + /// + /// Animator updates received from clients when using Client Authoritative. + /// + private class ClientAuthoritativeUpdate + { + /// + /// + public ClientAuthoritativeUpdate() + { + // Start buffers off at 8 bytes nad grow them as needed. + for (int i = 0; i < MAXIMUM_BUFFER_COUNT; i++) + _buffers.Add(new byte[MAXIMUM_DATA_SIZE]); + + _bufferLengths = new int[MAXIMUM_BUFFER_COUNT]; + } + + #region Public. + /// + /// True to force all animator data and ignore buffers. + /// + public bool ForceAll { get; private set; } + /// + /// Number of entries in Buffers. + /// + public int BufferCount = 0; + #endregion + + #region Private. + /// + /// Length of buffers. + /// + private int[] _bufferLengths; + /// + /// Buffers. + /// + private List _buffers = new(); + #endregion + + #region Const. + /// + /// Maximum size data may be. + /// + private const int MAXIMUM_DATA_SIZE = 1000; + /// + /// Maximum number of allowed buffers. + /// + public const int MAXIMUM_BUFFER_COUNT = 2; + #endregion + + public void AddToBuffer(ref ArraySegment data) + { + int dataCount = data.Count; + /* Data will never get this large, it's quite impossible. + * Just ignore the data if it does, client is likely performing + * an attack. */ + if (dataCount > MAXIMUM_DATA_SIZE) + return; + + // If index exceeds buffer count. + if (BufferCount >= MAXIMUM_BUFFER_COUNT) + { + ForceAll = true; + return; + } + + /* If here, can write to buffer. */ + byte[] buffer = _buffers[BufferCount]; + Buffer.BlockCopy(data.Array, data.Offset, buffer, 0, dataCount); + _bufferLengths[BufferCount] = dataCount; + BufferCount++; + } + + /// + /// Sets referenced data to buffer and it's length for index. + /// + /// + /// + /// + public void GetBuffer(int index, ref byte[] buffer, ref int length) + { + if (index > _buffers.Count) + { + NetworkManagerExtensions.LogWarning("Index exceeds Buffers count."); + return; + } + if (index > _bufferLengths.Length) + { + NetworkManagerExtensions.LogWarning("Index exceeds BufferLengths count."); + return; + } + + buffer = _buffers[index]; + length = _bufferLengths[index]; + } + + /// + /// Resets buffers. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + BufferCount = 0; + ForceAll = false; + } + } + + /// + /// Information on how to smooth to a float value. + /// + private struct SmoothedFloat + { + public SmoothedFloat(float rate, float target) + { + Rate = rate; + Target = target; + } + + public readonly float Rate; + public readonly float Target; + } + + /// + /// Details about a trigger update. + /// + private struct TriggerUpdate + { + public byte ParameterIndex; + public bool Setting; + + public TriggerUpdate(byte parameterIndex, bool setting) + { + ParameterIndex = parameterIndex; + Setting = setting; + } + } + + /// + /// Details about an animator parameter. + /// + private class ParameterDetail + { + /// + /// Parameter information. + /// + public readonly AnimatorControllerParameter ControllerParameter = null; + /// + /// Index within the types collection for this parameters value. The exception is with triggers; if the parameter type is a trigger then a value of 1 is set, 0 is unset. + /// + public readonly byte TypeIndex = 0; + /// + /// Hash for the animator string. + /// + public readonly int Hash; + + public ParameterDetail(AnimatorControllerParameter controllerParameter, byte typeIndex) + { + ControllerParameter = controllerParameter; + TypeIndex = typeIndex; + Hash = controllerParameter.nameHash; + } + } + #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. + /// + [SerializeField] + [HideInInspector] + internal List IgnoredParameters = new(); + #endregion + + #region Serialized. + /// + /// The animator component to synchronize. + /// + [Tooltip("The animator component to synchronize.")] + [SerializeField] + private Animator _animator; + /// + /// The animator component to synchronize. + /// + public Animator Animator + { + get { return _animator; } + } + /// + /// True to synchronize changes even when the animator component is disabled. + /// + [Tooltip("True to synchronize changes even when the animator component is disabled.")] + [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.")] + [SerializeField] + private bool _smoothFloats = true; + /// + /// How many ticks to interpolate. + /// + [Tooltip("How many ticks to interpolate.")] + [Range(1, NetworkTransform.MAX_INTERPOLATION)] + [SerializeField] + private ushort _interpolation = 2; + /// + /// + [Tooltip("True if using client authoritative animations.")] + [SerializeField] + private bool _clientAuthoritative = true; + /// + /// True if using client authoritative animations. + /// + public bool ClientAuthoritative + { + get { return _clientAuthoritative; } + } + /// + /// 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. + /// + [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. + /// + /// All parameter values, excluding triggers. + /// + private readonly List _parameterDetails = new(); + /// + /// Last int values. + /// + private readonly List _ints = new(); + /// + /// Last float values. + /// + private readonly List _floats = new(); + /// + /// Last bool values. + /// + private readonly List _bools = new(); + /// + /// Last layer weights. + /// + private float[] _layerWeights; + /// + /// Last speed. + /// + private float _speed; + /// + /// Trigger values set by using SetTrigger and ResetTrigger. + /// + private readonly List _triggerUpdates = new(); + // /// + // /// Updates going to clients. + // /// + // 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; + + if (_animator.enabled || _synchronizeWhenDisabled) + return true; + + return false; + } + } + /// + /// True if the animator is valid but not enabled. + /// + private bool _isAnimatorSet + { + get + { + bool failedChecks = _animator == null || _animator.runtimeAnimatorController == null; + return !failedChecks; + } + } + /// + /// Float valeus to smooth towards. + /// + private Dictionary _smoothedFloats = new(); + /// + /// Returns if floats can be smoothed for this client. + /// + private bool _canSmoothFloats + { + get + { + // Don't smooth on server only. + if (!IsClientStarted) + return false; + // Smoothing is disabled. + if (!_smoothFloats) + return false; + // No reason to smooth for self. + if (IsOwner && ClientAuthoritative) + return false; + + //Fall through. + return true; + } + } + /// + /// Layers which need to have their state synchronized. Key is the layer, Value is the state change information. + /// + private Dictionary _unsynchronizedLayerStates = new(); + /// + /// Last animator set. + /// + private Animator _lastAnimator; + /// + /// Last Controller set. + /// + private RuntimeAnimatorController _lastController; + /// + /// PooledWriter for this animator. + /// + private PooledWriter _writer = new(); + /// + /// Holds client authoritative updates received to send to other clients. + /// + private ClientAuthoritativeUpdate _clientAuthoritativeUpdates; + /// + /// True to forceAll next timed send. + /// + private bool _forceAllOnTimed; + /// + /// Animations received which should be applied. + /// + private Queue _fromServerBuffer = new(); + /// + /// Tick when the buffer may begin to run. + /// + private uint _startTick = TimeManagerCls.UNSET_TICK; + /// + /// True if subscribed to TimeManager for ticks. + /// + private bool _subscribedToTicks; + #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. + ///// + //private const float INTERPOLATION = 0.02f; + /// + /// ParameterDetails index which indicates a layer weight change. + /// + private const byte LAYER_WEIGHT = 240; + /// + /// ParameterDetails index which indicates an animator speed change. + /// + private const byte SPEED = 241; + /// + /// ParameterDetails index which indicates a layer state change. + /// + private const byte STATE = 242; + /// + /// ParameterDetails index which indicates a crossfade change. + /// + private const byte CROSSFADE = 243; + #endregion + + private void Awake() + { + InitializeOnce(); + } + + private void OnDestroy() + { + ChangeTickSubscription(false); + } + + [APIExclude] + public override void OnSpawnServer(NetworkConnection connection) + { + if (!_canSynchronizeAnimator) + return; + if (AnimatorUpdated(out ArraySegment updatedBytes, true)) + TargetAnimatorUpdated(connection, updatedBytes); + } + + public override void OnStartNetwork() + { + ChangeTickSubscription(true); + _isSynchronizationEnabled = true; + } + + [APIExclude] + public override void OnStartServer() + { + //If using client authoritative then initialize clientAuthoritativeUpdates. + if (_clientAuthoritative) + { + _clientAuthoritativeUpdates = new(); + // //Expand to clients buffer count to however many buffers can be held. + // for (int i = 0; i < ClientAuthoritativeUpdate.MAXIMUM_BUFFER_COUNT; i++) + // _toClientsBuffer.Add(new byte[0]); + } + // else + // { + // _toClientsBuffer.Add(new byte[0]); + // } + } + + public override void OnStartClient() + { + TimeManager.OnUpdate += TimeManager_OnUpdate; + } + + public override void OnStopClient() + { + if (TimeManager != null) + TimeManager.OnUpdate -= TimeManager_OnUpdate; + } + + public override void OnStopNetwork() + { + _unsynchronizedLayerStates.Clear(); + ChangeTickSubscription(false); + } + + /// + /// Tries to subscribe to TimeManager ticks. + /// + private void ChangeTickSubscription(bool subscribe) + { + if (subscribe == _subscribedToTicks || NetworkManager == null) + return; + + _subscribedToTicks = subscribe; + if (subscribe) + { + NetworkManager.TimeManager.OnPreTick += TimeManager_OnPreTick; + NetworkManager.TimeManager.OnPostTick += TimeManager_OnPostTick; + } + else + { + NetworkManager.TimeManager.OnPreTick -= TimeManager_OnPreTick; + NetworkManager.TimeManager.OnPostTick -= TimeManager_OnPostTick; + } + } + + /// + /// Called right before a tick occurs, as well before data is read. + /// + private void TimeManager_OnPreTick() + { + using (_pm_OnPreTick.Auto()) + { + if (!_canSynchronizeAnimator) + { + _fromServerBuffer.Clear(); + return; + } + + //Disabled/cannot start. + if (_startTick == 0) + return; + //Nothing in queue. + if (_fromServerBuffer.Count == 0) + { + _startTick = 0; + return; + } + + //Not enough time has passed to start queue. + if (TimeManager.LocalTick < _startTick) + return; + + ReceivedServerData rd = _fromServerBuffer.Dequeue(); + ArraySegment segment = rd.GetArraySegment(); + ApplyParametersUpdated(ref segment); + rd.Dispose(); + } + } + + /* Use post tick values are checked after + * client has an opportunity to use OnTick. */ + /// + /// Called after a tick occurs; physics would have simulated if using PhysicsMode.TimeManager. + /// + private void TimeManager_OnPostTick() + { + using (_pm_OnPostTick.Auto()) + { + //One check rather than per each method. + if (!_canSynchronizeAnimator) + return; + + CheckSendToServer(); + CheckSendToClients(); + } + } + + private void TimeManager_OnUpdate() + { + using (_pm_OnUpdate.Auto()) + { + if (!_canSynchronizeAnimator) + return; + + if (IsClientStarted) + SmoothFloats(); + } + } + + /// + /// Initializes this script for use. + /// + private void InitializeOnce() + { + if (_animator == null) + _animator = GetComponent(); + + //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."); + return; + } + + //Speed. + _speed = _animator.speed; + + //Build layer weights. + _layerWeights = new float[_animator.layerCount]; + for (int i = 0; i < _layerWeights.Length; i++) + _layerWeights[i] = _animator.GetLayerWeight(i); + + _parameterDetails.Clear(); + _bools.Clear(); + _floats.Clear(); + _ints.Clear(); + //Create a parameter detail for each parameter that can be synchronized. + foreach (AnimatorControllerParameter item in _animator.parameters) + { + bool process = !_animator.IsParameterControlledByCurve(item.name); + //PROSTART + + //PROEND + if (process) + { + //Over 250 parameters; who would do this!? + if (_parameterDetails.Count == 240) + { + NetworkManager.LogError($"Parameter {item.name} exceeds the allowed 240 parameter count and is being ignored."); + continue; + } + + int typeIndex = 0; + //Bools. + if (item.type == AnimatorControllerParameterType.Bool) + { + typeIndex = _bools.Count; + _bools.Add(_animator.GetBool(item.nameHash)); + } + //Floats. + else if (item.type == AnimatorControllerParameterType.Float) + { + typeIndex = _floats.Count; + _floats.Add(_animator.GetFloat(item.name)); + } + //Ints. + else if (item.type == AnimatorControllerParameterType.Int) + { + typeIndex = _ints.Count; + _ints.Add(_animator.GetInteger(item.nameHash)); + } + //Triggers. + else if (item.type == AnimatorControllerParameterType.Trigger) + { + /* Triggers aren't persistent so they don't use stored values + * but I do need to make a parameter detail to track the hash. */ + typeIndex = -1; + } + + _parameterDetails.Add(new(item, (byte)typeIndex)); + } + } + } + + /// + /// 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. + /// + /// + public void SetAnimator(Animator animator) + { + //No update required. + if (animator == _lastAnimator) + return; + + _animator = animator; + InitializeOnce(); + _lastAnimator = animator; + } + + /// + /// Sets which controller to use. You must call this with the appropriate controller on all clients and server. This change is not automatically synchronized. + /// + /// + public void SetController(RuntimeAnimatorController controller) + { + //No update required. + if (controller == _lastController) + return; + + _animator.runtimeAnimatorController = controller; + InitializeOnce(); + _lastController = controller; + } + + /// + /// Checks to send animator data from server to clients. + /// + private void CheckSendToServer() + { + using (_pm_CheckSendToServer.Auto()) + { + //Cannot send to server if is server or not client. + if (IsServerStarted || !IsClientInitialized) + return; + //Cannot send to server if not client authoritative or don't have authority. + if (!ClientAuthoritative || !IsOwner) + return; + + /* If there are updated parameters to send. + * Don't really need to worry about mtu here + * because there's no way the sent bytes are + * ever going to come close to the mtu + * when sending a single update. */ + if (AnimatorUpdated(out ArraySegment updatedBytes, _forceAllOnTimed)) + ServerAnimatorUpdated(updatedBytes); + + _forceAllOnTimed = false; + } + } + + /// + /// Checks to send animator data from server to clients. + /// + private void CheckSendToClients() + { + using (_pm_CheckSendToClients.Auto()) + { + //Cannot send to clients if not server initialized. + if (!IsServerInitialized) + return; + + bool sendFromServer; + //If client authoritative. + if (ClientAuthoritative) + { + //If has no owner then use latest values on server. + if (!Owner.IsValid) + { + sendFromServer = true; + } + //If has a owner. + else + { + //If is owner then send latest values on server. + if (IsOwner) + { + sendFromServer = true; + } + //Not owner. + else + { + //Haven't received any data from clients, cannot send yet. + if (_clientAuthoritativeUpdates.BufferCount == 0) + { + return; + } + //Data was received from client; check eligibility to send it. + else + { + /* If forceAll is true then the latest values on + * server must be used, rather than what was received + * from client. This can occur if the client is possibly + * trying to use an attack or if the client is + * excessively sending updates. To prevent relaying that + * same data to others the server will send it's current + * animator settings in this scenario. */ + if (_clientAuthoritativeUpdates.ForceAll) + { + sendFromServer = true; + _clientAuthoritativeUpdates.Reset(); + } + else + { + sendFromServer = false; + } + } + } + } + } + //Not client authoritative, always send from server. + else + { + sendFromServer = true; + } + + /* If client authoritative then use what was received from clients + * if data exist. */ + if (!sendFromServer) + { + byte[] buffer = null; + int bufferLength = 0; + for (int i = 0; i < _clientAuthoritativeUpdates.BufferCount; i++) + { + _clientAuthoritativeUpdates.GetBuffer(i, ref buffer, ref bufferLength); + + //If null was returned then something went wrong. + if (buffer == null || bufferLength == 0) + continue; + + SendSegment(new(buffer, 0, bufferLength)); + } + + //Reset client auth buffer. + _clientAuthoritativeUpdates.Reset(); + } + //Sending from server, send what's changed. + else + { + if (AnimatorUpdated(out ArraySegment updatedBytes, _forceAllOnTimed)) + SendSegment(updatedBytes); + + _forceAllOnTimed = false; + } + + //Sends segment to clients + void SendSegment(ArraySegment data) + { + foreach (NetworkConnection nc in Observers) + { + //If to not send to owner. + if (!_sendToOwner && nc == Owner) + continue; + TargetAnimatorUpdated(nc, data); + } + } + } + } + + /// + /// Smooths floats on clients. + /// + private void SmoothFloats() + { + using (_pm_SmoothFloats.Auto()) + { + //Don't need to smooth on authoritative client. + if (!_canSmoothFloats) + return; + //Nothing to smooth. + if (_smoothedFloats.Count == 0) + return; + + float deltaTime = Time.deltaTime; + + List finishedEntries = new(); + + /* Cycle through each target float and move towards it. + * Once at a target float mark it to be removed from floatTargets. */ + foreach (KeyValuePair item in _smoothedFloats) + { + float current = _animator.GetFloat(item.Key); + float next = Mathf.MoveTowards(current, item.Value.Target, item.Value.Rate * deltaTime); + _animator.SetFloat(item.Key, next); + + if (next == item.Value.Target) + finishedEntries.Add(item.Key); + } + + //Remove finished entries from dictionary. + for (int i = 0; i < finishedEntries.Count; i++) + _smoothedFloats.Remove(finishedEntries[i]); + } + } + + /// + /// Returns if animator is updated and bytes of updated values. + /// + /// + private bool AnimatorUpdated(out ArraySegment updatedBytes, bool forceAll = false) + { + using (_pm_AnimatorUpdated.Auto()) + { + updatedBytes = default; + //Something isn't setup right. + if (_layerWeights == null) + return false; + //Reset the writer. + _writer.Clear(); + + /* Every time a parameter is updated a byte is added + * for it's index, this is why requiredBytes increases + * by 1 when a value updates. ChangedParameter contains + * the index updated and the new value. The requiresBytes + * is increased also by however many bytes are required + * for the type which has changed. Some types use special parameter + * detail indexes, such as layer weights; these can be found under const. */ + for (byte parameterIndex = 0; parameterIndex < _parameterDetails.Count; parameterIndex++) + { + ParameterDetail pd = _parameterDetails[parameterIndex]; + /* Bool. */ + if (pd.ControllerParameter.type == AnimatorControllerParameterType.Bool) + { + bool next = _animator.GetBool(pd.Hash); + //If changed. + if (forceAll || _bools[pd.TypeIndex] != next) + { + _writer.WriteUInt8Unpacked(parameterIndex); + _writer.WriteBoolean(next); + _bools[pd.TypeIndex] = next; + } + } + /* Float. */ + else if (pd.ControllerParameter.type == AnimatorControllerParameterType.Float) + { + float next = _animator.GetFloat(pd.Hash); + //If changed. + if (forceAll || _floats[pd.TypeIndex] != next) + { + _writer.WriteUInt8Unpacked(parameterIndex); + _writer.WriteSingle(next); + _floats[pd.TypeIndex] = next; + } + } + /* Int. */ + else if (pd.ControllerParameter.type == AnimatorControllerParameterType.Int) + { + int next = _animator.GetInteger(pd.Hash); + //If changed. + if (forceAll || _ints[pd.TypeIndex] != next) + { + _writer.WriteUInt8Unpacked(parameterIndex); + _writer.WriteInt32(next); + _ints[pd.TypeIndex] = next; + } + } + } + + /* Don't need to force trigger sends since + * they're one-shots. */ + for (int i = 0; i < _triggerUpdates.Count; i++) + { + _writer.WriteUInt8Unpacked(_triggerUpdates[i].ParameterIndex); + _writer.WriteBoolean(_triggerUpdates[i].Setting); + } + + _triggerUpdates.Clear(); + + /* States. */ + if (forceAll) + { + //Add all layers to layer states. + for (int i = 0; i < _animator.layerCount; i++) + _unsynchronizedLayerStates[i] = new(Time.frameCount); + } + + /* Only iterate if the collection has values. This is to avoid some + * unnecessary caching when collection is empty. */ + if (_unsynchronizedLayerStates.Count > 0) + { + int frameCount = Time.frameCount; + List sentLayers = CollectionCaches.RetrieveList(); + //Go through each layer which needs to be synchronized. + foreach (KeyValuePair item in _unsynchronizedLayerStates) + { + /* If a frame has not passed since the state was created + * then do not send it until next tick. State changes take 1 frame + * to be processed by Unity, this check ensures that. */ + if (frameCount == item.Value.FrameCount) + continue; + + //Add to layers being sent. This is so they can be removed from the collection later. + sentLayers.Add(item.Key); + int layerIndex = item.Key; + StateChange sc = item.Value; + //If a regular state change. + if (!sc.IsCrossfade) + { + if (ReturnCurrentLayerState(out int stateHash, out float normalizedTime, layerIndex)) + { + _writer.WriteUInt8Unpacked(STATE); + _writer.WriteUInt8Unpacked((byte)layerIndex); + //Current hash will always be too large to compress. + _writer.WriteInt32Unpacked(stateHash); + _writer.WriteSingle(normalizedTime); + } + } + //When it's a crossfade then send crossfade data. + else + { + _writer.WriteUInt8Unpacked(CROSSFADE); + _writer.WriteUInt8Unpacked((byte)layerIndex); + //Current hash will always be too large to compress. + _writer.WriteInt32(sc.Hash); + _writer.WriteBoolean(sc.FixedTime); + //Times usually can be compressed. + _writer.WriteSingle(sc.DurationTime); + _writer.WriteSingle(sc.OffsetTime); + _writer.WriteSingle(sc.NormalizedTransitionTime); + } + } + + if (sentLayers.Count > 0) + { + for (int i = 0; i < sentLayers.Count; i++) + _unsynchronizedLayerStates.Remove(sentLayers[i]); + //Store cache. + CollectionCaches.Store(sentLayers); + } + } + + /* Layer weights. */ + for (int layerIndex = 0; layerIndex < _layerWeights.Length; layerIndex++) + { + float next = _animator.GetLayerWeight(layerIndex); + if (forceAll || _layerWeights[layerIndex] != next) + { + _writer.WriteUInt8Unpacked(LAYER_WEIGHT); + _writer.WriteUInt8Unpacked((byte)layerIndex); + _writer.WriteSingle(next); + _layerWeights[layerIndex] = next; + } + } + + /* Speed is similar to layer weights but we don't need the index, + * only the indicator and value. */ + float speedNext = _animator.speed; + if (forceAll || _speed != speedNext) + { + _writer.WriteUInt8Unpacked(SPEED); + _writer.WriteSingle(speedNext); + _speed = speedNext; + } + + //Nothing to update. + if (_writer.Position == 0) + return false; + + updatedBytes = _writer.GetArraySegment(); + return true; + } + } + + /// + /// Applies changed parameters to the animator. + /// + /// + private void ApplyParametersUpdated(ref ArraySegment updatedParameters) + { + using (_pm_ApplyParametersUpdated.Auto()) + { + if (!_canSynchronizeAnimator) + return; + if (_layerWeights == null) + return; + if (updatedParameters.Count == 0) + return; + + PooledReader reader = ReaderPool.Retrieve(updatedParameters, NetworkManager); + + try + { + while (reader.Remaining > 0) + { + byte parameterIndex = reader.ReadUInt8Unpacked(); + //Layer weight + if (parameterIndex == LAYER_WEIGHT) + { + byte layerIndex = reader.ReadUInt8Unpacked(); + float value = reader.ReadSingle(); + _animator.SetLayerWeight((int)layerIndex, value); + } + //Speed. + else if (parameterIndex == SPEED) + { + float value = reader.ReadSingle(); + _animator.speed = value; + } + //State. + else if (parameterIndex == STATE) + { + byte layerIndex = reader.ReadUInt8Unpacked(); + //Hashes will always be too large to compress. + int hash = reader.ReadInt32Unpacked(); + float normalizedTime = reader.ReadSingle(); + //Play results. + _animator.Play(hash, layerIndex, normalizedTime); + } + //Crossfade. + else if (parameterIndex == CROSSFADE) + { + byte layerIndex = reader.ReadUInt8Unpacked(); + //Hashes will always be too large to compress. + int hash = reader.ReadInt32(); + bool useFixedTime = reader.ReadBoolean(); + //Get time values. + float durationTime = reader.ReadSingle(); + float offsetTime = reader.ReadSingle(); + float normalizedTransitionTime = reader.ReadSingle(); + //If using fixed. + if (useFixedTime) + _animator.CrossFadeInFixedTime(hash, durationTime, layerIndex, offsetTime, normalizedTransitionTime); + else + _animator.CrossFade(hash, durationTime, layerIndex, offsetTime, normalizedTransitionTime); + } + //Not a predetermined index, is an actual parameter. + else + { + AnimatorControllerParameterType acpt = _parameterDetails[parameterIndex].ControllerParameter.type; + if (acpt == AnimatorControllerParameterType.Bool) + { + bool value = reader.ReadBoolean(); + _animator.SetBool(_parameterDetails[parameterIndex].Hash, value); + } + //Float. + else if (acpt == AnimatorControllerParameterType.Float) + { + float value = reader.ReadSingle(); + //If able to smooth floats. + if (_canSmoothFloats) + { + float currentValue = _animator.GetFloat(_parameterDetails[parameterIndex].Hash); + float past = (float)TimeManager.TickDelta; + //float past = _synchronizeInterval + INTERPOLATION; + float rate = Mathf.Abs(currentValue - value) / past; + _smoothedFloats[_parameterDetails[parameterIndex].Hash] = new(rate, value); + } + else + { + _animator.SetFloat(_parameterDetails[parameterIndex].Hash, value); + } + } + //Integer. + else if (acpt == AnimatorControllerParameterType.Int) + { + int value = reader.ReadInt32(); + _animator.SetInteger(_parameterDetails[parameterIndex].Hash, value); + } + //Trigger. + else if (acpt == AnimatorControllerParameterType.Trigger) + { + bool value = reader.ReadBoolean(); + if (value) + _animator.SetTrigger(_parameterDetails[parameterIndex].Hash); + else + _animator.ResetTrigger(_parameterDetails[parameterIndex].Hash); + } + //Unhandled. + else + { + NetworkManager.LogWarning($"Unhandled parameter type of {acpt}."); + } + } + } + } + catch + { + NetworkManager.LogWarning("An error occurred while applying updates. This may occur when malformed data is sent or when you change the animator or controller but not on all connections."); + } + finally + { + reader?.Store(); + } + } + } + + /// + /// Outputs the current state and time for a layer. Returns true if stateHash is not 0. + /// + /// + /// + /// + /// + /// + private bool ReturnCurrentLayerState(out int stateHash, out float normalizedTime, int layerIndex) + { + stateHash = 0; + normalizedTime = 0f; + + if (!_canSynchronizeAnimator) + return false; + + AnimatorStateInfo st = _animator.GetCurrentAnimatorStateInfo(layerIndex); + stateHash = st.fullPathHash; + normalizedTime = st.normalizedTime; + + return stateHash != 0; + } + + /// + /// Immediately sends all variables and states of layers. + /// This is a very bandwidth intensive operation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SendAll() + { + _forceAllOnTimed = true; + } + + #region Play. + /// + /// Plays a state. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Play(string name) + { + Play(Animator.StringToHash(name)); + } + + /// + /// Plays a state. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Play(int hash) + { + for (int i = 0; i < _animator.layerCount; i++) + Play(hash, i, 0f); + } + + /// + /// Plays a state. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Play(string name, int layer) + { + Play(Animator.StringToHash(name), layer); + } + + /// + /// Plays a state. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Play(int hash, int layer) + { + Play(hash, layer, 0f); + } + + /// + /// Plays a state. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Play(string name, int layer, float normalizedTime) + { + Play(Animator.StringToHash(name), layer, normalizedTime); + } + + /// + /// Plays a state. + /// + public void Play(int hash, int layer, float normalizedTime) + { + if (!_canSynchronizeAnimator) + return; + if (_animator.HasState(layer, hash) || hash == 0) + { + _animator.Play(hash, layer, normalizedTime); + _unsynchronizedLayerStates[layer] = new(Time.frameCount); + } + } + + /// + /// Plays a state. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PlayInFixedTime(string name, float fixedTime) + { + PlayInFixedTime(Animator.StringToHash(name), fixedTime); + } + + /// + /// Plays a state. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PlayInFixedTime(int hash, float fixedTime) + { + for (int i = 0; i < _animator.layerCount; i++) + PlayInFixedTime(hash, i, fixedTime); + } + + /// + /// Plays a state. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PlayInFixedTime(string name, int layer, float fixedTime) + { + PlayInFixedTime(Animator.StringToHash(name), layer, fixedTime); + } + + /// + /// Plays a state. + /// + public void PlayInFixedTime(int hash, int layer, float fixedTime) + { + if (!_canSynchronizeAnimator) + return; + if (_animator.HasState(layer, hash) || hash == 0) + { + _animator.PlayInFixedTime(hash, layer, fixedTime); + _unsynchronizedLayerStates[layer] = new(Time.frameCount); + } + } + #endregion + + #region Crossfade. + /// + /// Creates a crossfade from the current state to any other state using normalized times. + /// + /// + /// + /// + /// + /// + [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); + } + + /// + /// Creates a crossfade from the current state to any other state using normalized times. + /// + /// + /// + /// + /// + /// + public void CrossFade(int hash, float normalizedTransitionDuration, int layer, float normalizedTimeOffset = 0.0f, float normalizedTransitionTime = 0.0f) + { + if (!_canSynchronizeAnimator) + return; + if (_animator.HasState(layer, hash) || hash == 0) + { + _animator.CrossFade(hash, normalizedTransitionDuration, layer, normalizedTimeOffset, normalizedTransitionTime); + _unsynchronizedLayerStates[layer] = new(Time.frameCount, hash, false, normalizedTransitionDuration, normalizedTimeOffset, normalizedTransitionTime); + } + } + + /// + /// Creates a crossfade from the current state to any other state using times in seconds. + /// + /// + /// + /// + /// + /// + [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); + } + + /// + /// Creates a crossfade from the current state to any other state using times in seconds. + /// + /// + /// + /// + /// + /// + public void CrossFadeInFixedTime(int hash, float fixedTransitionDuration, int layer, float fixedTimeOffset = 0.0f, float normalizedTransitionTime = 0.0f) + { + if (!_canSynchronizeAnimator) + return; + if (_animator.HasState(layer, hash) || hash == 0) + { + _animator.CrossFadeInFixedTime(hash, fixedTransitionDuration, layer, fixedTimeOffset, normalizedTransitionTime); + _unsynchronizedLayerStates[layer] = new(Time.frameCount, hash, true, fixedTransitionDuration, fixedTimeOffset, normalizedTransitionTime); + } + } + #endregion + + #region Triggers. + /// + /// Sets a trigger on the animator and sends it over the network. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetTrigger(int hash) + { + if (!_canSynchronizeAnimator) + return; + UpdateTrigger(hash, true); + } + + /// + /// Sets a trigger on the animator and sends it over the network. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetTrigger(string name) + { + SetTrigger(Animator.StringToHash(name)); + } + + /// + /// Resets a trigger on the animator and sends it over the network. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResetTrigger(int hash) + { + UpdateTrigger(hash, false); + } + + /// + /// Resets a trigger on the animator and sends it over the network. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResetTrigger(string name) + { + ResetTrigger(Animator.StringToHash(name)); + } + + /// + /// Updates a trigger, sets or resets. + /// + /// + private void UpdateTrigger(int hash, bool set) + { + if (!_canSynchronizeAnimator) + return; + + bool clientAuth = ClientAuthoritative; + //If there is an owner perform checks. + if (Owner.IsValid) + { + //If client auth and not owner. + if (clientAuth && !IsOwner) + return; + } + //There is no owner. + else + { + if (!IsServerStarted) + return; + } + + //Update locally. + if (set) + _animator.SetTrigger(hash); + else + _animator.ResetTrigger(hash); + + /* Can send if any of the following are true: + * ClientAuth + Owner. + * ClientAuth + No Owner + IsServer + * !ClientAuth + IsServer. */ + bool canSend = (clientAuth && IsOwner) || (clientAuth && !Owner.IsValid) || (!clientAuth && IsServerStarted); + + //Only queue a send if proper side. + if (canSend) + { + for (byte i = 0; i < _parameterDetails.Count; i++) + { + if (_parameterDetails[i].Hash == hash) + { + _triggerUpdates.Add(new(i, set)); + return; + } + } + //Fall through, hash not found. + NetworkManager.LogWarning($"Hash {hash} not found while trying to update a trigger."); + } + } + #endregion + + #region Remote actions. + /// + /// Called on clients to receive an animator update. + /// + /// + [TargetRpc(ValidateTarget = false)] + private void TargetAnimatorUpdated(NetworkConnection connection, ArraySegment data) + { + if (!_canSynchronizeAnimator) + return; + + //If receiver is client host then do nothing, clientHost need not process. + if (IsServerInitialized && connection.IsLocalClient) + return; + + bool clientAuth = ClientAuthoritative; + bool isOwner = IsOwner; + /* If set for client auth and owner then do not process. + * This could be the case if an update was meant to come before + * ownership gain but came out of late due to out of order when using unreliable. + * Cannot check sendToOwner given clients may not + * always be aware of owner depending on ShareIds setting. */ + if (clientAuth && isOwner) + return; + /* If not client auth and not to send to owner, and is owner + * then also return. */ + if (!clientAuth && !_sendToOwner && isOwner) + return; + + ReceivedServerData rd = new(data); + _fromServerBuffer.Enqueue(rd); + + if (_startTick == 0) + _startTick = TimeManager.LocalTick + _interpolation; + } + + /// + /// Called on server to receive an animator update. + /// + /// + [ServerRpc] + private void ServerAnimatorUpdated(ArraySegment data) + { + if (!_canSynchronizeAnimator) + return; + if (!ClientAuthoritative) + { + Owner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection Id {Owner.ClientId} has been kicked for trying to update this object without client authority."); + return; + } + + /* Server does not need to apply interpolation. + * Even as clientHost when CSP is being used the + * clientHost will always be on the latest tick. + * Spectators on the other hand will remain behind + * a little depending on their components interpolation. */ + ApplyParametersUpdated(ref data); + _clientAuthoritativeUpdates.AddToBuffer(ref data); + } + #endregion + + #region Editor. + #if UNITY_EDITOR + protected override void Reset() + { + base.Reset(); + if (_animator == null) + SetAnimator(GetComponent()); + } + #endif + #endregion + } +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs index d3e6ca6d..6e717c86 100644 --- a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs +++ b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs @@ -143,7 +143,7 @@ public void SetFrameRate(ushort value) private SplitReader _splitReader = new(); /// /// - private NetworkTrafficStatistics _networkTrafficStatistics; + [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; #endregion #region Private Profiler Markers diff --git a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs index 461c5418..9435fbca 100644 --- a/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs +++ b/Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs @@ -99,7 +99,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(); } } 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..5e52c507 100644 --- a/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs +++ b/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs @@ -23,10 +23,20 @@ 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 Protected. @@ -55,7 +65,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)) + OnSpawnedAdd?.Invoke(nob.ObjectId, nob); + } + + protected void HandleClear() + { + _spawned.Clear(); + OnSpawnedClear?.Invoke(); + } + #endregion #region Private. @@ -109,7 +138,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 +345,7 @@ internal virtual void DespawnWithoutSynchronization(bool recursive, bool asServe DespawnWithoutSynchronization(nob, recursive, asServer, nob.GetDefaultDespawnType(), removeFromSpawned: false); } - Spawned.Clear(); + HandleClear(); } /// @@ -371,7 +400,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 +437,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; 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 55d879d0..1365a5b8 100644 --- a/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs +++ b/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs @@ -222,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. diff --git a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs index c81ec7d1..58c37480 100644 --- a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs +++ b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs @@ -8,6 +8,7 @@ using System; using System.Runtime.CompilerServices; using FishNet.Managing.Statistic; +using Unity.Mathematics; using Unity.Profiling; using UnityEngine; using SystemStopwatch = System.Diagnostics.Stopwatch; @@ -284,10 +285,11 @@ 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_OnFixedUpdate = new("TimeManager.OnFixedUpdate()"); private static readonly ProfilerMarker _pm_OnPostPhysicsSimulation = new("TimeManager.OnPostPhysicsSimulation(float)"); private static readonly ProfilerMarker _pm_OnPrePhysicsSimulation = new("TimeManager.OnPrePhysicsSimulation(float)"); @@ -683,97 +685,100 @@ 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; - } - - double time = Time.unscaledDeltaTime; + bool isClient = NetworkManager.IsClientStarted; + bool isServer = NetworkManager.IsServerStarted; - _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) { - using (_pm_OnPrePhysicsSimulation.Auto()) - OnPrePhysicsSimulation?.Invoke(tickDelta); - using (_pm_PhysicsSimulate.Auto()) - Physics.Simulate(tickDelta); - using (_pm_Physics2DSimulate.Auto()) - Physics2D.Simulate(tickDelta); - using (_pm_OnPostPhysicsSimulation.Auto()) - OnPostPhysicsSimulation?.Invoke(tickDelta); + // Tell predicted objecs to reconcile before OnTick. + NetworkManager.PredictionManager.ReconcileToStates(); + + using (_pm_OnTick.Auto()) + OnTick?.Invoke(); + + if (PhysicsMode == PhysicsMode.TimeManager && tickDelta > 0f) + { + using (_pm_OnPrePhysicsSimulation.Auto()) + OnPrePhysicsSimulation?.Invoke(tickDelta); + using (_pm_PhysicsSimulate.Auto()) + Physics.Simulate(tickDelta); + using (_pm_Physics2DSimulate.Auto()) + Physics2D.Simulate(tickDelta); + using (_pm_OnPostPhysicsSimulation.Auto()) + OnPostPhysicsSimulation?.Invoke(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. @@ -795,12 +800,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(); @@ -811,6 +818,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; @@ -892,6 +900,7 @@ public double TicksToTime(TickType tickType = TickType.LocalTick) /// /// PreciseTick to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(PreciseTick pt) { double tickTime = TicksToTime(pt.Tick); @@ -904,6 +913,7 @@ public double TicksToTime(PreciseTick pt) /// /// Ticks to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(uint ticks) { return TickDelta * (double)ticks; @@ -993,16 +1003,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); } /// @@ -1010,6 +1032,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; @@ -1021,6 +1044,7 @@ public uint TimeToTicks(long time, TickRounding rounding = TickRounding.RoundNea /// /// Time to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public PreciseTick TimeToPreciseTick(double time) => time.AsPreciseTick(TickDelta); /// diff --git a/Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs b/Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs index d1ca6a57..8f947191 100644 --- a/Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs +++ b/Assets/FishNet/Runtime/Managing/Transporting/TransportManager.cs @@ -122,7 +122,7 @@ public LatencySimulator LatencySimulator private int _customMtuReserve = MINIMUM_MTU_RESERVE; /// /// - private NetworkTrafficStatistics _networkTrafficStatistics; + [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; #endregion #region Consts. From 3b02d1f01262531187848fcd3340be446df0c359 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:26:31 +0300 Subject: [PATCH 03/23] feat: third part of QOLs Now we can observer for NetworkObject server/client start/stop events Now NetworkBehaviour callback safe for error inside user code --- .../NetworkBehaviour.Callbacks.cs | 213 ++++++++++++++---- .../NetworkBehaviour/NetworkBehaviour.cs | 5 +- .../NetworkObject/NetworkObject.Callbacks.cs | 56 ++++- 3 files changed, 222 insertions(+), 52 deletions(-) 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 29b3530b..fd455750 100644 --- a/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs +++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs @@ -1,6 +1,7 @@ #if UNITY_EDITOR || DEVELOPMENT_BUILD #define DEVELOPMENT #endif +using System; using FishNet.CodeGenerating; using FishNet.Documenting; using FishNet.Managing.Transporting; @@ -76,7 +77,7 @@ public byte ComponentIndex #if !UNITY_SERVER /// /// - private NetworkTrafficStatistics _networkTrafficStatistics; + [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; /// /// Name of this NetworkBehaviour. /// @@ -101,7 +102,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..6d6d0f4e 100644 --- a/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs +++ b/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs @@ -7,6 +7,12 @@ namespace FishNet.Object { public partial class NetworkObject : MonoBehaviour { + #region Types + + public delegate void NetworkObjectCallback(NetworkObject nb); + + #endregion + #region Private. /// /// True if OnStartServer was called. @@ -16,6 +22,40 @@ public partial class NetworkObject : MonoBehaviour /// True if OnStartClient was called. /// private bool _onStartClientCalled; + + private bool OnStartServerCalled + { + get => _onStartServerCalled; + set + { + if (_onStartServerCalled != value) + { + _onStartServerCalled = value; + if (value) OnStartServerEvent?.Invoke(this); + else OnStopServerEvent?.Invoke(this); + } + } + } + + private bool OnStartClientCalled + { + get => _onStartClientCalled; + set + { + if (_onStartClientCalled != value) + { + _onStartClientCalled = value; + if (value) OnStartClientEvent?.Invoke(this); + else OnStopClientEvent?.Invoke(this); + } + } + } + + public event NetworkObjectCallback OnStartServerEvent; + public event NetworkObjectCallback OnStopServerEvent; + public event NetworkObjectCallback OnStartClientEvent; + public event NetworkObjectCallback OnStopClientEvent; + #endregion // ReSharper disable Unity.PerformanceAnalysis @@ -41,7 +81,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,7 +90,7 @@ 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); } @@ -114,17 +154,17 @@ internal void InvokeStopCallbacks(bool asServer, bool invokeSyncTypeCallbacks) if (invokeSyncTypeCallbacks) InvokeOnStopSyncTypeCallbacks(asServer); - 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,10 +173,10 @@ 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; } void InvokeOnNetwork() From 74c9a65d3b61715d6468cad9e0f716a926aa9114 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:28:47 +0300 Subject: [PATCH 04/23] fix sync collections These things need to be cleaned up, otherwise after ResetState and respawn they fire and OnChange fires when it shouldn't. Also... Make sync type settings public explicit --- Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs | 3 +-- .../FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs | 5 +++++ Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSet.cs | 5 +++++ Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs | 3 +++ Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs | 5 ++++- 5 files changed, 18 insertions(+), 3 deletions(-) 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 From 269fcfce6a6e71ea1c10b321fc77c1b420af78f8 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:37:48 +0300 Subject: [PATCH 05/23] feat: fix asmdefs --- Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef | 5 ++++- Assets/FishNet/Runtime/FishNet.Runtime.asmdef | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) 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/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": [], From 643a95ac1d0dba7246a0b47009526df3b9931897 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:38:44 +0300 Subject: [PATCH 06/23] fix asmdefs --- .../Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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": [], From 9fed811995fbfa296d6ed0a0efc26dd1c0b22c6c Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:40:00 +0300 Subject: [PATCH 07/23] fix asmdefs --- Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef | 5 ++++- Assets/FishNet/Runtime/FishNet.Runtime.asmdef | 4 +++- .../Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) 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/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/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": [], From a1082f1b2f3c24d5be83e21f2441583fd6701cb3 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:40:22 +0300 Subject: [PATCH 08/23] feat: add QOL --- .../Dependencies/Utilities/Dictionaries.cs | 10 +- .../GameKit/Dependencies/Utilities/Floats.cs | 9 + .../Dependencies/Utilities/Quaternions.cs | 47 ++- .../Utilities/Types/StripedRingQueue.cs | 370 ++++++++++++++++++ .../GameKit/Dependencies/Utilities/Vectors.cs | 28 ++ .../Runtime/Utility/Extension/Transforms.cs | 332 ++++++++++++++-- 6 files changed, 757 insertions(+), 39 deletions(-) create mode 100644 Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs 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/Floats.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs index c082ac25..5b7a908f 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using UnityEngine; namespace GameKit.Dependencies.Utilities @@ -16,6 +17,7 @@ public static class Floats /// Float to check against tolerance. /// Tolerance float must be equal to or greater than to change to value. /// Value source is set to when breaking tolerance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SetIfOverTolerance(this float source, float tolerance, float value) { if (source >= tolerance) @@ -30,6 +32,7 @@ public static float SetIfOverTolerance(this float source, float tolerance, float /// Float to check against tolerance. /// Tolerance float must be equal to or less than to change to value. /// Value source is set to when breaking tolerance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SetIfUnderTolerance(this float source, float tolerance, float value) { if (source <= tolerance) @@ -42,6 +45,7 @@ public static float SetIfUnderTolerance(this float source, float tolerance, floa /// Returns how much time is left on an endTime. Returns -1 if no time is left. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float TimeRemainingValue(this float endTime) { float remaining = endTime - Time.time; @@ -56,6 +60,7 @@ public static float TimeRemainingValue(this float endTime) /// Returns how much time is left on an endTime. Returns -1 if no time is left. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TimeRemainingValue(this float endTime, bool useFloor = true) { float remaining = endTime - Time.time; @@ -136,6 +141,7 @@ public static float Random01() /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Near(this float a, float b, float tolerance = 0.01f) { return Mathf.Abs(a - b) <= tolerance; @@ -149,6 +155,7 @@ public static bool Near(this float a, float b, float tolerance = 0.01f) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Clamp(float value, float min, float max, ref bool clamped) { clamped = value < min; @@ -192,6 +199,7 @@ public static void Variance(this float source, float variance, ref float result) /// /// Value to sign. /// Precise sign. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float PreciseSign(float value) { if (value == 0f) @@ -207,6 +215,7 @@ public static float PreciseSign(float value) /// Minimum of range. /// Maximum of range. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InRange(this float source, float rangeMin, float rangeMax) { return source >= rangeMin && source <= rangeMax; diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs index d302deb4..bfac65d1 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using Unity.Mathematics; +using UnityEngine; namespace GameKit.Dependencies.Utilities { @@ -16,16 +17,39 @@ public static float GetRate(this Quaternion a, Quaternion goal, float duration, angle = a.Angle(goal, true); return angle / (duration * interval); } + + /// + /// Returns how fast an object must rotate over duration to reach goal. + /// + /// Quaternion to measure distance against. + /// How long it should take to move to goal. + /// A multiplier applied towards interval. Typically this is used for ticks passed. + /// + public static float GetRate(this quaternion a, quaternion goal, float duration, out float angle, uint interval = 1, float tolerance = 0f) + { + angle = a.Angle(goal, true); + return angle / (duration * interval); + } /// /// Subtracts b quaternion from a. /// public static Quaternion Subtract(this Quaternion a, Quaternion b) => Quaternion.Inverse(b) * a; + + /// + /// Subtracts b quaternion from a. + /// + public static quaternion Subtract(this quaternion a, quaternion b) => math.mul(math.inverse(b), a); /// /// Adds quaternion b onto quaternion a. /// public static Quaternion Add(this Quaternion a, Quaternion b) => a * b; + + /// + /// Adds quaternion b onto quaternion a. + /// + public static quaternion Add(this quaternion a, quaternion b) => math.mul(a, b); /// /// Returns if two quaternions match. @@ -58,5 +82,26 @@ public static float Angle(this Quaternion a, Quaternion b, bool precise = false) return Quaternion.Angle(a, b); } } + + /// + /// Returns the angle between two quaterions. + /// + /// True to use a custom implementation with no error tolerance. False to use Unity's implementation which may return 0f due to error tolerance, even while there is a difference. + /// + public static float Angle(this quaternion a, quaternion b, bool precise = false) + { + if (!precise) + { + float d = math.dot(a.value, b.value); + float c = math.saturate(math.abs(d)); + return math.degrees(2f * math.acos(c)); + } + + quaternion an = math.normalize(a); + quaternion bn = math.normalize(b); + float dn = math.dot(an.value, bn.value); + float cn = math.saturate(math.abs(dn)); + return math.degrees(2f * math.acos(cn)); + } } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs new file mode 100644 index 00000000..72b87937 --- /dev/null +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs @@ -0,0 +1,370 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace GameKit.Dependencies.Utilities +{ + /// + /// A striped ring-buffer that stores N independent queues addressed by i = 0..N-1. + /// Backing storage uses NativeList-based stripes with a fixed per-queue capacity. + /// Designed for job-friendly, per-index parallel work without cross contention. + /// + public struct StripedRingQueue : IDisposable + where T : unmanaged + { + /// + /// Backing storage for all stripes; length equals _queueCount * _capacity. + /// + [NativeDisableParallelForRestriction] private NativeList _data; + /// + /// Per-queue head (read index) + /// Advances on dequeue operations modulo _capacity. + /// + [NativeDisableParallelForRestriction] private NativeList _head; + /// + /// Per-queue item count. + /// Always clamped to the range [0.._capacity]. + /// + [NativeDisableParallelForRestriction] private NativeList _count; + /// + /// Compact metadata buffer stored in native memory: + /// [0] = fixed per-queue capacity, [1] = current queue count. + /// + [NativeDisableParallelForRestriction] private NativeArray _meta; + + /// + /// True when internal lists are allocated and usable. + /// + public bool IsCreated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data.IsCreated && _head.IsCreated && _count.IsCreated && _meta.IsCreated; + } + /// + /// Fixed capacity per queue (ring size). + /// + public int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _meta[0]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private set => _meta[0] = value; + } + /// + /// Number of independent queues (stripes). + /// + public int QueueCount + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _meta[1]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private set => _meta[1] = value; + } + /// + /// Total addressable storage, equal to QueueCount * Capacity. + /// + public int TotalCapacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => QueueCount * Capacity; + } + + /// + /// Indexer for direct access by queue index and raw ring index (0..Capacity-1). + /// Does not account for head/count; use GetCount/Clear/Enqueue/Dequeue for logical queue semantics. + /// + public T this[int queueIndex, int simulatedIndex] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + int offset = GetRealOffset(queueIndex, simulatedIndex); + return _data[offset]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + int offset = GetRealOffset(queueIndex, simulatedIndex); + _data[offset] = value; + } + } + + /// + /// Constructs the striped ring with an initial queue count and per-queue capacity. + /// Allocates NativeList storage and zeroes head/count for all stripes. + /// + public StripedRingQueue(int initialQueueCount, int capacity, Allocator allocator) + { + if (initialQueueCount < 0) throw new ArgumentOutOfRangeException(nameof(initialQueueCount)); + if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); + + _meta = new NativeArray(2, allocator, NativeArrayOptions.UninitializedMemory); + _meta[0] = capacity; + _meta[1] = initialQueueCount; + + _data = new NativeList(math.max(1, initialQueueCount * capacity), allocator); + _head = new NativeList(math.max(1, initialQueueCount), allocator); + _count = new NativeList(math.max(1, initialQueueCount), allocator); + + _data.ResizeUninitialized(initialQueueCount * capacity); + _head.ResizeUninitialized(initialQueueCount); + _count.ResizeUninitialized(initialQueueCount); + + for (int i = 0; i < initialQueueCount; i++) + { + _head[i] = 0; + _count[i] = 0; + } + } + + /// + /// Disposes all internal lists synchronously. + /// Ensure that no jobs are accessing this storage when disposing. + /// + public void Dispose() + { + if (_data.IsCreated) _data.Dispose(); + if (_head.IsCreated) _head.Dispose(); + if (_count.IsCreated) _count.Dispose(); + if (_meta.IsCreated) _meta.Dispose(); + } + + /// + /// Schedules disposal of internal lists and returns a combined JobHandle. + /// Use this to free storage once dependent jobs have completed. + /// + public JobHandle Dispose(JobHandle inputDeps) + { + JobHandle h = inputDeps; + if (_data.IsCreated) h = _data.Dispose(h); + if (_head.IsCreated) h = _head.Dispose(h); + if (_count.IsCreated) h = _count.Dispose(h); + if (_meta.IsCreated) h = _meta.Dispose(h); + return h; + } + + /// + /// Adds a new empty queue (stripe) and returns its index. + /// Grows the data buffer by Capacity and zeroes the stripe's head/count. + /// + public int AddQueue() + { + int capacity = Capacity; + int queueCount = QueueCount; + + int newIndex = queueCount; + + int newDataLen = (newIndex + 1) * capacity; + if (_data.Capacity < newDataLen) _data.Capacity = newDataLen; + _data.ResizeUninitialized(newDataLen); + + _head.Add(0); + _count.Add(0); + + QueueCount = newIndex + 1; + return newIndex; + } + + /// + /// Removes the queue at the given index by swapping with the last stripe, + /// then shrinking storage by one stripe. Data swap is O(Capacity). + /// + public void RemoveQueueAtSwapBack(int index) + { + int queueCount = QueueCount; + int capacity = Capacity; + + int last = queueCount - 1; + if ((uint)index >= (uint)queueCount) + throw new ArgumentOutOfRangeException(nameof(index)); + if (last < 0) + return; + + if (index != last) + { + int a = index * capacity; + int b = last * capacity; + + for (int k = 0; k < capacity; k++) + { + (_data[a + k], _data[b + k]) = (_data[b + k], _data[a + k]); + } + } + + _data.ResizeUninitialized(_data.Length - capacity); + _head.RemoveAtSwapBack(index); + _count.RemoveAtSwapBack(index); + + QueueCount = last; + } + + /// + /// Returns the current number of items in queue i. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetCount(int i) => _count[i]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int BaseOffset(int i) => i * Capacity; + + /// + /// Returns the real index of the collection using a simulated index. + /// + /// + /// + /// True to allow an index be returned from an unused portion of the buffer so long as it is within bounds. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetRealOffset(int queueIndex, int simulatedIndex, bool allowUnusedBuffer = false) + { + int capacity = Capacity; + int queueCount = QueueCount; + + if ((uint)queueIndex >= (uint)queueCount) + throw new ArgumentOutOfRangeException(nameof(queueIndex)); + if ((uint)simulatedIndex >= (uint)capacity) + throw new ArgumentOutOfRangeException(nameof(simulatedIndex)); + + int count = _count[queueIndex]; + if (simulatedIndex >= count && !allowUnusedBuffer) + throw new ArgumentOutOfRangeException( + nameof(simulatedIndex), + $"Index {simulatedIndex} >= item count {count} in queue {queueIndex}"); + + int head = _head[queueIndex]; + int offset = (head + simulatedIndex) % capacity; + return BaseOffset(queueIndex) + offset; + } + + /// + /// Clears queue i by resetting head and count to zero. + /// Stored values remain but are considered invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(int i) + { + _head[i] = 0; + _count[i] = 0; + } + + /// + /// Enqueues 'value' into queue i; overwrites the oldest item when full. + /// Main-thread only unless no concurrent access to the same i is guaranteed. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Enqueue(int i, in T value) + { + int capacity = Capacity; + + int h = _head[i]; + int c = _count[i]; + int baseOff = BaseOffset(i); + int tail = (h + c) % capacity; + + _data[baseOff + tail] = value; + + if (c < capacity) + { + _count[i] = c + 1; + } + else + { + _head[i] = (h + 1) % capacity; // overwrite oldest + } + } + + /// + /// Tries to dequeue one item from queue i into 'value'. + /// Returns false when the queue is empty. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryDequeue(int i, out T value) + { + int c = _count[i]; + if (c == 0) + { + value = default; + return false; + } + + int capacity = Capacity; + + int h = _head[i]; + int baseOff = BaseOffset(i); + value = _data[baseOff + h]; + + _head[i] = (h + 1) % capacity; + _count[i] = c - 1; + return true; + } + + /// + /// Dequeues up to 'n' items from queue i and returns how many were removed. + /// The last removed item (if any) is written to 'last'. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int DequeueUpTo(int i, int n, out T last) + { + int c = _count[i]; + int drop = math.clamp(n, 0, c); + if (drop == 0) + { + last = default; + return 0; + } + + int capacity = Capacity; + + int h = _head[i]; + int baseOff = BaseOffset(i); + int lastIdx = (h + drop - 1) % capacity; + + last = _data[baseOff + lastIdx]; + + _head[i] = (h + drop) % capacity; + _count[i] = c - drop; + return drop; + } + + /// + /// Peeks the next entry from i queue. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Peek(int i) + { + int c = _count[i]; + if (c == 0) + throw new InvalidOperationException($"{nameof(StripedRingQueue)} of type {typeof(T).Name} is empty."); + + int h = _head[i]; + int baseOff = BaseOffset(i); + return _data[baseOff + h]; + } + + /// + /// Tries to peek the next entry from queue i. + /// + /// + /// Peeked entry. + /// True if an entry existed to peek. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPeek(int i, out T result) + { + int c = _count[i]; + if (c == 0) + { + result = default; + return false; + } + + int h = _head[i]; + int baseOff = BaseOffset(i); + result = _data[baseOff + h]; + return true; + } + } +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs index 0fa22645..6102fc00 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using Unity.Mathematics; using UnityEngine; namespace GameKit.Dependencies.Utilities @@ -23,15 +24,31 @@ public static class Vectors /// How long it should take to move to goal. /// A multiplier applied towards interval. Typically this is used for ticks passed. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetRate(this Vector3 a, Vector3 b, float duration, out float distance, uint interval = 1) { distance = Vector3.Distance(a, b); return distance / (duration * interval); } + + /// + /// Returns how fast an object must move over duration to reach goal. + /// + /// Vector3 to measure distance against. + /// How long it should take to move to goal. + /// A multiplier applied towards interval. Typically this is used for ticks passed. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetRate(this float3 a, float3 b, float duration, out float distance, uint interval = 1) + { + distance = math.distance(a, b); + return distance / (duration * interval); + } /// /// Adds a Vector2 X/Y onto a Vector3. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Add(this Vector3 v3, Vector2 v2) { return v3 + new Vector3(v2.x, v2.y, 0f); @@ -40,6 +57,7 @@ public static Vector3 Add(this Vector3 v3, Vector2 v2) /// /// Subtracts a Vector2 X/Y from a Vector3. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Subtract(this Vector3 v3, Vector2 v2) { return v3 - new Vector3(v2.x, v2.y, 0f); @@ -52,6 +70,7 @@ public static Vector3 Subtract(this Vector3 v3, Vector2 v2) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float InverseLerp(Vector3 a, Vector3 b, Vector3 value) { Vector3 ab = b - a; @@ -66,6 +85,7 @@ public static float InverseLerp(Vector3 a, Vector3 b, Vector3 value) /// Target vector. /// How close the target vector must be to be considered close. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Near(this Vector3 a, Vector3 b, float tolerance = 0.01f) { return Vector3.Distance(a, b) <= tolerance; @@ -76,6 +96,7 @@ public static bool Near(this Vector3 a, Vector3 b, float tolerance = 0.01f) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNan(this Vector3 source) { return float.IsNaN(source.x) || float.IsNaN(source.y) || float.IsNaN(source.z); @@ -85,6 +106,7 @@ public static bool IsNan(this Vector3 source) /// Lerp between three Vector3 values. /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Lerp3(Vector3 a, Vector3 b, Vector3 c, float percent) { Vector3 r0 = Vector3.Lerp(a, b, percent); @@ -98,6 +120,7 @@ public static Vector3 Lerp3(Vector3 a, Vector3 b, Vector3 c, float percent) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Lerp3(Vector3[] vectors, float percent) { if (vectors.Length < 3) @@ -115,6 +138,7 @@ public static Vector3 Lerp3(Vector3[] vectors, float percent) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Multiply(this Vector3 src, Vector3 multiplier) { return new(src.x * multiplier.x, src.y * multiplier.y, src.z * multiplier.z); @@ -201,6 +225,7 @@ public static Vector3 FastNormalize(Vector3 value) /// How long it should take to move to goal. /// A multiplier applied towards interval. Typically this is used for ticks passed. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetRate(this Vector2 a, Vector2 goal, float duration, out float distance, uint interval = 1) { distance = Vector2.Distance(a, goal); @@ -215,6 +240,7 @@ public static float GetRate(this Vector2 a, Vector2 goal, float duration, out fl /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Lerp3(Vector2 a, Vector2 b, Vector2 c, float percent) { Vector2 r0 = Vector2.Lerp(a, b, percent); @@ -228,6 +254,7 @@ public static Vector2 Lerp3(Vector2 a, Vector2 b, Vector2 c, float percent) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Lerp2(Vector2[] vectors, float percent) { if (vectors.Length < 3) @@ -245,6 +272,7 @@ public static Vector2 Lerp2(Vector2[] vectors, float percent) /// /// /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Multiply(this Vector2 src, Vector2 multiplier) { return new(src.x * multiplier.x, src.y * multiplier.y); 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; } } From 707c3eb4d475daf1aebd221b30bde8c4943c2dfc Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 20:46:37 +0300 Subject: [PATCH 09/23] feat: add jobified tick smoother --- .../TickSmoothing/MovementSettings.cs | 8 +- .../TickSmoothing/TickSmootherController.cs | 281 ++-- .../TickSmoothingManager.Types.cs | 1315 ++++++++++++++++ .../TickSmoothing/TickSmoothingManager.cs | 1336 +++++++++++++++++ .../TickSmoothing/UniversalTickSmoother.cs | 105 +- .../Runtime/Managing/NetworkManager.cs | 7 + .../Runtime/Managing/Timing/TimeManager.cs | 184 ++- .../Runtime/Object/Prediction/MoveRates.cs | 320 +++- .../Runtime/Object/TransformProperties.cs | 100 +- 9 files changed, 3268 insertions(+), 388 deletions(-) create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs index f543473f..135913c8 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs @@ -34,6 +34,11 @@ public struct MovementSettings [Tooltip("Properties to smooth. Any value not selected will become offset with every movement.")] public TransformPropertiesFlag SmoothedProperties; /// + /// True to apply smoothing in local space for position and rotation. False to use world space. + /// + [Tooltip("True to apply smoothing in local space for position and rotation. False to use world space.")] + public bool UseLocalSpace; + /// /// True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick. /// [Tooltip("True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.")] @@ -46,7 +51,8 @@ public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersO AdaptiveInterpolationValue = AdaptiveInterpolationType.Off; InterpolationValue = 2; SmoothedProperties = TransformPropertiesFlag.Everything; + UseLocalSpace = false; SnapNonSmoothedProperties = false; } } -} \ No newline at end of file +} diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs index 8c49766b..4bf63b15 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs @@ -1,9 +1,8 @@ -using FishNet.Managing.Predicting; -using FishNet.Managing.Timing; +using FishNet.Managing.Timing; using FishNet.Object; using GameKit.Dependencies.Utilities; -using Unity.Profiling; using UnityEngine; +using Unity.Profiling; namespace FishNet.Component.Transforming.Beta { @@ -14,13 +13,22 @@ namespace FishNet.Component.Transforming.Beta public class TickSmootherController : IResettable { #region Public. - /// - /// Logic for owner smoothing. - /// - public UniversalTickSmoother UniversalSmoother { get; private set; } + // /// + // /// Logic for owner smoothing. + // /// + // public UniversalTickSmoother UniversalSmoother { get; private set; } #endregion - + #region Private. + + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("TickSmootherController.TimeManager_OnUpdate()"); + private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPostTick()"); + + #endregion + /// /// private InitializationSettings _initializationSettings = new(); @@ -43,6 +51,10 @@ public class TickSmootherController : IResettable /// private NetworkBehaviour _initializingNetworkBehaviour; /// + /// TickSmoothingManager. + /// + private TickSmoothingManager _tickSmoothingManager; + /// /// Transform which initialized this object. /// private Transform _graphicalTransform; @@ -62,13 +74,13 @@ public class TickSmootherController : IResettable /// True if initialized. /// private bool _isInitialized; - private static readonly ProfilerMarker _pm_OnUpdate = new("TickSmootherController.TimeManager_OnUpdate()"); - private static readonly ProfilerMarker _pm_OnPreTick = new("TickSmootherController.TimeManager_OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostTick = new("TickSmootherController.TimeManager_OnPostTick()"); #endregion public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) { + _tickSmoothingManager = + initializationSettings.InitializingNetworkBehaviour?.NetworkManager.TickSmoothingManager ?? + InstanceFinder.NetworkManager.TickSmoothingManager; _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; _graphicalTransform = initializationSettings.GraphicalTransform; @@ -83,8 +95,12 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe public void OnDestroy() { - ChangeSubscriptions(false); - StoreSmoother(); + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Unregister(this); + + // ChangeSubscriptions(false); + // StoreSmoother(); _destroyed = true; _isInitialized = false; } @@ -99,11 +115,15 @@ public void StartSmoother() if (!canStart) return; - RetrieveSmoothers(); - - UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + // RetrieveSmoothers(); + // + // UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + // + // UniversalSmoother.StartSmoother(); - UniversalSmoother.StartSmoother(); + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Register(this, _initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); bool StartOnline() { @@ -125,13 +145,15 @@ bool StartOffline() public void StopSmoother() { - ChangeSubscriptions(subscribe: false); - + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Unregister(this); + if (!_initializedOffline) StopOnline(); - - if (UniversalSmoother != null) - UniversalSmoother.StopSmoother(); + + // if (UniversalSmoother != null) + // UniversalSmoother.StopSmoother(); void StopOnline() { @@ -142,64 +164,64 @@ void StopOnline() // void StopOffline() { } } - public void TimeManager_OnUpdate() - { - using (_pm_OnUpdate.Auto()) - { - UniversalSmoother.OnUpdate(Time.deltaTime); - } - } - - public void TimeManager_OnPreTick() - { - using (_pm_OnPreTick.Auto()) - { - UniversalSmoother.OnPreTick(); - } - } - - /// - /// Called after a tick completes. - /// - public void TimeManager_OnPostTick() - { - using (_pm_OnPostTick.Auto()) - { - if (_timeManager != null) - UniversalSmoother.OnPostTick(_timeManager.LocalTick); - } - } - - private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) - { - UniversalSmoother.OnPostReplicateReplay(clientTick); - } - - private void TimeManager_OnRoundTripTimeUpdated(long rttMs) - { - UniversalSmoother.UpdateRealtimeInterpolation(); - } - - /// - /// Stores smoothers if they have value. - /// - private void StoreSmoother() - { - if (UniversalSmoother == null) - return; - - ResettableObjectCaches.Store(UniversalSmoother); - UniversalSmoother = null; - } - - /// - /// Stores current smoothers and retrieves new ones. - /// - private void RetrieveSmoothers() - { - StoreSmoother(); - UniversalSmoother = ResettableObjectCaches.Retrieve(); - } + // public void TimeManager_OnUpdate() + // { + // using (_pm_OnUpdate.Auto()) + // { + // UniversalSmoother.OnUpdate(Time.deltaTime); + // } + // } + // + // public void TimeManager_OnPreTick() + // { + // using (_pm_OnPreTick.Auto()) + // { + // UniversalSmoother.OnPreTick(); + // } + // } + // + // /// + // /// Called after a tick completes. + // /// + // public void TimeManager_OnPostTick() + // { + // using (_pm_OnPostTick.Auto()) + // { + // if (_timeManager != null) + // UniversalSmoother.OnPostTick(_timeManager.LocalTick); + // } + // } + // + // private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) + // { + // UniversalSmoother.OnPostReplicateReplay(clientTick); + // } + // + // private void TimeManager_OnRoundTripTimeUpdated(long rttMs) + // { + // UniversalSmoother.UpdateRealtimeInterpolation(); + // } + // + // /// + // /// Stores smoothers if they have value. + // /// + // private void StoreSmoother() + // { + // if (UniversalSmoother == null) + // return; + // + // ResettableObjectCaches.Store(UniversalSmoother); + // UniversalSmoother = null; + // } + // + // /// + // /// Stores current smoothers and retrieves new ones. + // /// + // private void RetrieveSmoothers() + // { + // StoreSmoother(); + // UniversalSmoother = ResettableObjectCaches.Retrieve(); + // } // /// // /// Sets a target transform to follow. @@ -233,58 +255,59 @@ public void SetTimeManager(TimeManager tm) return; // Unsub from current. - ChangeSubscriptions(false); + // ChangeSubscriptions(false); //Sub to newest. _timeManager = tm; - ChangeSubscriptions(true); - } - - /// - /// Changes the subscription to the TimeManager. - /// - private void ChangeSubscriptions(bool subscribe) - { - if (_destroyed) - return; - TimeManager tm = _timeManager; - if (tm == null) - return; - - if (subscribe == _subscribed) - return; - _subscribed = subscribe; - - bool adaptiveIsOff = _ownerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off; - - if (subscribe) - { - tm.OnUpdate += TimeManager_OnUpdate; - tm.OnPreTick += TimeManager_OnPreTick; - tm.OnPostTick += TimeManager_OnPostTick; - - if (!adaptiveIsOff) - { - tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; - PredictionManager pm = tm.NetworkManager.PredictionManager; - pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; - _subscribedToAdaptiveEvents = true; - } - } - else - { - tm.OnUpdate -= TimeManager_OnUpdate; - tm.OnPreTick -= TimeManager_OnPreTick; - tm.OnPostTick -= TimeManager_OnPostTick; - - if (_subscribedToAdaptiveEvents) - { - tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; - PredictionManager pm = tm.NetworkManager.PredictionManager; - pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; - } - } + // ChangeSubscriptions(true); } - + + + // /// + // /// Changes the subscription to the TimeManager. + // /// + // private void ChangeSubscriptions(bool subscribe) + // { + // if (_destroyed) + // return; + // TimeManager tm = _timeManager; + // if (tm == null) + // return; + // + // if (subscribe == _subscribed) + // return; + // _subscribed = subscribe; + // + // bool adaptiveIsOff = _ownerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off; + // + // if (subscribe) + // { + // tm.OnUpdate += TimeManager_OnUpdate; + // tm.OnPreTick += TimeManager_OnPreTick; + // tm.OnPostTick += TimeManager_OnPostTick; + // + // if (!adaptiveIsOff) + // { + // tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; + // PredictionManager pm = tm.NetworkManager.PredictionManager; + // pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; + // _subscribedToAdaptiveEvents = true; + // } + // } + // else + // { + // tm.OnUpdate -= TimeManager_OnUpdate; + // tm.OnPreTick -= TimeManager_OnPreTick; + // tm.OnPostTick -= TimeManager_OnPostTick; + // + // if (_subscribedToAdaptiveEvents) + // { + // tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; + // PredictionManager pm = tm.NetworkManager.PredictionManager; + // pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; + // } + // } + // } + public void ResetState() { _initializationSettings = default; diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs new file mode 100644 index 00000000..c73f0890 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs @@ -0,0 +1,1315 @@ +using System; +using System.Runtime.CompilerServices; +using FishNet.Managing.Timing; +using FishNet.Object; +using FishNet.Object.Prediction; +using FishNet.Utility.Extension; +using GameKit.Dependencies.Utilities; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Jobs; +using UnityEngine.Scripting; + +namespace FishNet.Component.Transforming.Beta +{ + public partial class TickSmoothingManager + { + #region Types. + [Preserve] + public struct TickTransformProperties + { + public readonly uint Tick; + public readonly TransformProperties Properties; + + public TickTransformProperties(uint tick, TransformProperties properties) + { + Tick = tick; + Properties = properties; + } + } + [Preserve] + private struct NullableTransformProperties + { + public readonly byte IsExist; + public readonly TransformProperties Properties; + + public NullableTransformProperties(bool isExist, TransformProperties properties) + { + IsExist = (byte)(isExist ? 1 : 0); + Properties = properties; + } + } + [Preserve] + private struct MoveToTargetPayload + { + public byte executeMask; + public float delta; + + public MoveToTargetPayload(byte executeMask, float delta) + { + this.executeMask = executeMask; + this.delta = delta; + } + } + [Preserve] + private struct UpdateRealtimeInterpolationPayload + { + public byte executeMask; + + public UpdateRealtimeInterpolationPayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + [Preserve] + private struct DiscardExcessiveTransformPropertiesQueuePayload + { + public byte executeMask; + + public DiscardExcessiveTransformPropertiesQueuePayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + [Preserve] + private struct SetMoveRatesPayload + { + public byte executeMask; + public TransformProperties prevValues; + + public SetMoveRatesPayload(byte executeMask, TransformProperties prevValues) + { + this.executeMask = executeMask; + this.prevValues = prevValues; + } + } + [Preserve] + private struct SetMovementMultiplierPayload + { + public byte executeMask; + + public SetMovementMultiplierPayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + [Preserve] + private struct AddTransformPropertiesPayload + { + public byte executeMask; + public TickTransformProperties tickTransformProperties; + + public AddTransformPropertiesPayload(byte executeMask, TickTransformProperties tickTransformProperties) + { + this.executeMask = executeMask; + this.tickTransformProperties = tickTransformProperties; + } + } + [Preserve] + private struct ClearTransformPropertiesQueuePayload + { + public byte executeMask; + + public ClearTransformPropertiesQueuePayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + [Preserve] + private struct ModifyTransformPropertiesPayload + { + public byte executeMask; + public uint clientTick; + public uint firstTick; + + public ModifyTransformPropertiesPayload(byte executeMask, uint clientTick, uint firstTick) + { + this.executeMask = executeMask; + this.clientTick = clientTick; + this.firstTick = firstTick; + } + } + [Preserve] + private struct SnapNonSmoothedPropertiesPayload + { + public byte executeMask; + public TransformProperties goalValues; + + public SnapNonSmoothedPropertiesPayload(byte executeMask, TransformProperties goalValues) + { + this.executeMask = executeMask; + this.goalValues = goalValues; + } + } + [Preserve] + private struct TeleportPayload + { + public byte executeMask; + + public TeleportPayload(byte executeMask) + { + this.executeMask = executeMask; + } + } + #endregion + + #region PreTick. + [BurstCompile] + private struct PreTickMarkJob : IJobParallelFor + { + [ReadOnly] public NativeArray canSmoothMask; + [WriteOnly] public NativeArray preTickedMask; + + [WriteOnly] public NativeArray discardExcessivePayloads; + + public void Execute(int index) + { + discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(0); + + if (canSmoothMask[index] == 0) + return; + + preTickedMask[index] = 1; + discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(1); + } + } + + [BurstCompile] + private struct PreTickCaptureGraphicalJob : IJobParallelForTransform + { + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [WriteOnly] public NativeArray graphicSnapshot; + + public void Execute(int index, TransformAccess graphicalTransform) + { + if (canSmoothMask[index] == 0) + return; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + graphicSnapshot[index] = GetTransformProperties(graphicalTransform, useLocalSpace); + } + } + #endregion + + #region PostTick + + [BurstCompile] + private struct PostTickCaptureTrackerJob : IJobParallelForTransform + { + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public NativeArray detachOnStartMask; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [ReadOnly] public NativeArray targetSnapshot; + [WriteOnly] public NativeArray trackerSnapshot; + + public void Execute(int index, TransformAccess trackerTransform) + { + if (canSmoothMask[index] == 0) + return; + + bool isDetach = detachOnStartMask[index] != 0; + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + TransformProperties trackerProperties = GetTrackerTransformProperties(trackerTransform, isDetach, useLocalSpace); + if (useLocalSpace) trackerProperties += targetSnapshot[index]; + trackerSnapshot[index] = trackerProperties; + } + } + [BurstCompile] + private struct PostTickJob : IJobParallelForTransform + { + [ReadOnly] public uint clientTick; + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public NativeArray teleportedTick; + [ReadOnly] public NativeArray preTickedMask; + [ReadOnly] public NativeArray detachOnStartMask; + [ReadOnly] public NativeArray postTickTrackerSnapshot; + [ReadOnly] public NativeArray preTickGraphicSnapshot; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + [WriteOnly] public NativeArray discardExcessivePayloads; + [WriteOnly] public NativeArray snapNonSmoothedPropertiesPayloads; + [WriteOnly] public NativeArray addTransformPropertiesPayloads; + + public void Execute(int index, TransformAccess graphicalTransform) + { + discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(0); + addTransformPropertiesPayloads[index] = new AddTransformPropertiesPayload(0, default); + + if (canSmoothMask[index] == 0) + return; + + if (clientTick <= teleportedTick[index]) + return; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + TransformProperties trackerProps = postTickTrackerSnapshot[index]; + //If preticked then previous transform values are known. + if (preTickedMask[index] != 0) + { + //Only needs to be put to pretick position if not detached. + if (detachOnStartMask[index] == 0) + { + var graphicProps = preTickGraphicSnapshot[index]; + SetTransformProperties(graphicalTransform, graphicProps, useLocalSpace); + } + + TickTransformProperties tickTrackerProps = new TickTransformProperties(clientTick, trackerProps); + discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(1); + snapNonSmoothedPropertiesPayloads[index] = new SnapNonSmoothedPropertiesPayload(0, trackerProps); + addTransformPropertiesPayloads[index] = new AddTransformPropertiesPayload(1, tickTrackerProps); + } + //If did not pretick then the only thing we can do is snap to instantiated values. + else + { + //Only set to position if not to detach. + if (detachOnStartMask[index] == 0) + { + SetTransformProperties(graphicalTransform, trackerProps, useLocalSpace); + } + } + } + } + #endregion + + #region PostReplicateReplay + [BurstCompile] + private struct PostReplicateReplayJob : IJobParallelFor + { + [ReadOnly] public uint clientTick; + [ReadOnly] public NativeArray teleportedTick; + [ReadOnly] public NativeArray objectReconcilingMask; + + public StripedRingQueue transformProperties; + [WriteOnly] public NativeArray modifyTransformPropertiesPayloads; + + public void Execute(int index) + { + modifyTransformPropertiesPayloads[index] = new ModifyTransformPropertiesPayload(0, default, default); + if (objectReconcilingMask[index] == 0) + return; + + if (transformProperties.GetCount(index) == 0) + return; + if (clientTick <= teleportedTick[index]) + return; + uint firstTick = transformProperties.Peek(index).Tick; + //Already in motion to first entry, or first entry passed tick. + if (clientTick <= firstTick) + return; + + modifyTransformPropertiesPayloads[index] = new ModifyTransformPropertiesPayload(1, clientTick, firstTick); + } + } + #endregion + + #region RoundTripTimeUpdated + [BurstCompile] + private struct RoundTripTimeUpdatedJob : IJobParallelFor + { + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + [WriteOnly] public NativeArray updateRealtimeInterpolationPayloads; + + public void Execute(int index) + { + updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(0); + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + // Update RTT only if Adaptive Interpolation is not Off + if (GetUseAdaptiveInterpolation(settings)) + updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(1); + } + + private static bool GetUseAdaptiveInterpolation(in MovementSettings settings) + { + if (settings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off) + return false; + + return true; + } + } + #endregion + + #region Update + [BurstCompile] + private struct UpdateJob : IJobParallelFor + { + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public float deltaTime; + + [WriteOnly] public NativeArray moveToTargetPayloads; + + public void Execute(int index) + { + moveToTargetPayloads[index] = new MoveToTargetPayload(0, default); + + if (canSmoothMask[index] == 0) + return; + + moveToTargetPayloads[index] = new MoveToTargetPayload(1, deltaTime); + } + } + #endregion + + #region Methods. + [BurstCompile] + private struct CaptureLocalTargetJob : IJobParallelForTransform + { + [ReadOnly] public NativeArray canSmoothMask; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [WriteOnly] public NativeArray targetSnapshot; + + public void Execute(int index, TransformAccess targetTransform) + { + if (canSmoothMask[index] == 0) + return; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + if (!useLocalSpace) return; + + targetSnapshot[index] = GetTransformProperties(targetTransform, true); + } + } + + [BurstCompile] + private struct MoveToTargetJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + [ReadOnly] public NativeArray realTimeInterpolations; + [ReadOnly] public NativeArray moveImmediatelyMask; + [ReadOnly] public float tickDelta; + + public NativeArray isMoving; + public NativeArray movementMultipliers; + + public StripedRingQueue transformProperties; + public NativeArray moveRates; + + public NativeArray setMoveRatesPayloads; + public NativeArray setMovementMultiplierPayloads; + public NativeArray clearTransformPropertiesQueuePayloads; + + public void Execute(int index, TransformAccess graphicalTransform) + { + MoveToTarget( + index, + graphicalTransform, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref realTimeInterpolations, + ref moveImmediatelyMask, + tickDelta, + ref isMoving, + ref movementMultipliers, + ref transformProperties, + ref moveRates, + ref setMoveRatesPayloads, + ref setMovementMultiplierPayloads, + ref clearTransformPropertiesQueuePayloads); + } + + /// + /// Moves transform to target values. + /// + public static void MoveToTarget( + int index, + TransformAccess graphicalTransform, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref NativeArray realTimeInterpolations, + ref NativeArray moveImmediatelyMask, + float tickDelta, + ref NativeArray isMoving, + ref NativeArray movementMultipliers, + ref StripedRingQueue transformProperties, + ref NativeArray moveRates, + ref NativeArray setMoveRatesPayloads, + ref NativeArray setMovementMultiplierPayloads, + ref NativeArray clearTransformPropertiesQueuePayloads) + { + MoveToTargetPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new MoveToTargetPayload(0, default); + + // We only need the delta once, then clear payload for this frame. + float remainingDelta = jobPayload.delta; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + bool useLocalSpace = settings.UseLocalSpace; + + while (remainingDelta > 0f) + { + int tpCount = transformProperties.GetCount(index); + + // No data in queue. + if (tpCount == 0) + return; + + byte realtimeInterpolation = realTimeInterpolations[index]; + if (moveImmediatelyMask[index] != 0) + { + isMoving[index] = 1; + } + else + { + //Enough in buffer to move. + if (tpCount >= realtimeInterpolation) + { + isMoving[index] = 1; + } + else if (isMoving[index] == 0) + { + return; + } + /* If buffer is considerably under goal then halt + * movement. This will allow the buffer to grow. */ + else if (tpCount - realtimeInterpolation < -4) + { + isMoving[index] = 0; + return; + } + } + + TickTransformProperties ttp = transformProperties.Peek(index); + TransformPropertiesFlag smoothedProperties = settings.SmoothedProperties; + + MoveRates moveRatesValue = moveRates[index]; + float movementMultiplier = movementMultipliers[index]; + + moveRatesValue.Move( + graphicalTransform, + ttp.Properties, + smoothedProperties, + remainingDelta * movementMultiplier, + useWorldSpace: !useLocalSpace); + moveRates[index] = moveRatesValue; + + float tRemaining = moveRatesValue.TimeRemaining; + + //if TimeRemaining is <= 0f then transform is at goal. Grab a new goal if possible. + if (tRemaining <= 0f) + { + //Dequeue current entry and if there's another call a move on it. + transformProperties.TryDequeue(index, out _); + + //If there are entries left then setup for the next. + if (transformProperties.GetCount(index) > 0) + { + setMoveRatesPayloads[index] = new SetMoveRatesPayload(1, ttp.Properties); + SetMoveRatesJob.SetMoveRates( + index, + ref setMoveRatesPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref transformProperties, + tickDelta, + ref moveRates, + ref setMovementMultiplierPayloads); + + SetMovementMultiplierJob.SetMovementMultiplier( + index, + ref setMovementMultiplierPayloads, + ref transformProperties, + ref realTimeInterpolations, + ref moveImmediatelyMask, + ref movementMultipliers); + + // If there is leftover time, apply it to the next segment in this loop. + if (tRemaining < 0f) + { + remainingDelta = Mathf.Abs(tRemaining); + continue; + } + } + //No remaining, set to snap. + else + { + ClearTransformPropertiesQueueJob.ClearTransformPropertiesQueue( + index, + ref clearTransformPropertiesQueuePayloads, + ref transformProperties, + ref moveRates); + } + } + + // Either we did not finish, or there is no leftover time to consume. + break; + } + } + } + + [BurstCompile] + private struct UpdateRealtimeInterpolationJob : IJobParallelFor + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + [ReadOnly] public float tickDelta; + [ReadOnly] public ushort tickRate; + [ReadOnly] public uint localTick; + [ReadOnly] public long rtt; + [ReadOnly] public bool isServerOnlyStarted; + + public NativeArray realTimeInterpolations; + + public void Execute(int index) + { + UpdateRealtimeInterpolation( + index, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + tickDelta, + tickRate, + localTick, + rtt, + isServerOnlyStarted, + ref realTimeInterpolations); + } + + /// + /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. + /// + public static void UpdateRealtimeInterpolation( + int index, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + float tickDelta, + ushort tickRate, + uint localTick, + long rtt, + bool isServerOnlyStarted, + ref NativeArray realTimeInterpolations + ) + { + UpdateRealtimeInterpolationPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new UpdateRealtimeInterpolationPayload(0); + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + /* If not networked, server is started, or if not + * using adaptive interpolation then use + * flat interpolation.*/ + if (!GetUseAdaptiveInterpolation(settings, isServerOnlyStarted)) + { + realTimeInterpolations[index] = settings.InterpolationValue; + return; + } + + /* If here then adaptive interpolation is being calculated. */ + + //Calculate roughly what client state tick would be. + //This should never be the case; this is a precautionary against underflow. + if (localTick == TimeManager.UNSET_TICK) + return; + + //Ensure at least 1 tick. + uint rttTicks = TimeManager.TimeToTicks(rtt, tickDelta) + 1; + + uint clientStateTick = localTick - rttTicks; + float interpolation = localTick - clientStateTick; + + //Minimum interpolation is that of adaptive interpolation level. + interpolation += (byte)settings.AdaptiveInterpolationValue; + + //Ensure interpolation is not more than a second. + if (interpolation > tickRate) + interpolation = tickRate; + else if (interpolation > byte.MaxValue) + interpolation = byte.MaxValue; + + /* Only update realtime interpolation if it changed more than 1 + * tick. This is to prevent excessive changing of interpolation value, which + * could result in noticeable speed ups/slow downs given movement multiplier + * may change when buffer is too full or short. */ + float realtimeInterpolation = realTimeInterpolations[index]; + if (realtimeInterpolation == 0 || math.abs(realtimeInterpolation - interpolation) > 1) + realTimeInterpolations[index] = (byte)math.ceil(interpolation); + } + + private static bool GetUseAdaptiveInterpolation(in MovementSettings settings, in bool isServerOnlyStarted) + { + if (settings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off || isServerOnlyStarted) + return false; + + return true; + } + } + + [BurstCompile] + private struct DiscardExcessiveTransformPropertiesQueueJob : IJobParallelFor + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray realTimeInterpolations; + [ReadOnly] public int requiredQueuedOverInterpolation; + + public StripedRingQueue transformProperties; + [WriteOnly] public NativeArray setMoveRatesPayloads; + + public void Execute(int index) + { + DiscardExcessiveTransformPropertiesQueue( + index, + ref jobPayloads, + ref realTimeInterpolations, + requiredQueuedOverInterpolation, + ref transformProperties, + ref setMoveRatesPayloads); + } + + /// + /// Discards datas over interpolation limit from movement queue. + /// + public static void DiscardExcessiveTransformPropertiesQueue( + int index, + ref NativeArray jobPayloads, + ref NativeArray realTimeInterpolations, + int requiredQueuedOverInterpolation, + ref StripedRingQueue transformProperties, + ref NativeArray setMoveRatesPayloads) + { + setMoveRatesPayloads[index] = new SetMoveRatesPayload(0, default); + + DiscardExcessiveTransformPropertiesQueuePayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(0); + + int propertiesCount = transformProperties.GetCount(index); + int realtimeInterpolationValue = realTimeInterpolations[index]; + int dequeueCount = propertiesCount - (realtimeInterpolationValue + requiredQueuedOverInterpolation); + + // If there are entries to dequeue. + if (dequeueCount > 0) + { + TickTransformProperties ttp; + transformProperties.DequeueUpTo(index, dequeueCount, out ttp); + + var nextValues = ttp.Properties; + setMoveRatesPayloads[index] = new SetMoveRatesPayload(1, nextValues); + } + } + } + + [BurstCompile] + private struct SetMoveRatesJob : IJobParallelFor + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + public StripedRingQueue transformProperties; + [ReadOnly] public float tickDelta; + + [WriteOnly] public NativeArray moveRates; + [WriteOnly] public NativeArray setMovementMultiplierPayloads; + + public void Execute(int index) + { + SetMoveRates( + index, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref transformProperties, + tickDelta, + ref moveRates, + ref setMovementMultiplierPayloads); + } + + /// + /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. + /// + public static void SetMoveRates( + int index, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref StripedRingQueue transformProperties, + float tickDelta, + ref NativeArray moveRates, + ref NativeArray setMovementMultiplierPayloads) + { + moveRates[index] = new(MoveRates.UNSET_VALUE); + setMovementMultiplierPayloads[index] = new SetMovementMultiplierPayload(0); + + SetMoveRatesPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0 || transformProperties.GetCount(index) == 0) + return; + jobPayloads[index] = new SetMoveRatesPayload(0, default); + + TransformProperties prevValues = jobPayload.prevValues; + TransformProperties nextValues = transformProperties.Peek(index).Properties; + float duration = tickDelta; + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + float teleportThreshold = settings.EnableTeleport + ? settings.TeleportThreshold * settings.TeleportThreshold + : MoveRates.UNSET_VALUE; + + MoveRates moveRatesValue = MoveRates.GetMoveRates(prevValues, nextValues, duration, teleportThreshold); + moveRatesValue.TimeRemaining = duration; + moveRates[index] = moveRatesValue; + setMovementMultiplierPayloads[index] = new SetMovementMultiplierPayload(1); + } + } + + [BurstCompile] + private struct SetMovementMultiplierJob : IJobParallelFor + { + public NativeArray jobPayloads; + + public StripedRingQueue transformProperties; + [ReadOnly] public NativeArray realTimeInterpolations; + [ReadOnly] public NativeArray moveImmediatelyMask; + + public NativeArray movementMultipliers; + + public void Execute(int index) + { + SetMovementMultiplier( + index, + ref jobPayloads, + ref transformProperties, + ref realTimeInterpolations, + ref moveImmediatelyMask, + ref movementMultipliers); + } + + public static void SetMovementMultiplier( + int index, + ref NativeArray jobPayloads, + ref StripedRingQueue transformProperties, + ref NativeArray realTimeInterpolations, + ref NativeArray moveImmediatelyMask, + ref NativeArray movementMultipliers) + { + SetMovementMultiplierPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new SetMovementMultiplierPayload(0); + + byte moveImmediately = moveImmediatelyMask[index]; + byte realTimeInterpolation = realTimeInterpolations[index]; + float movementMultiplier = movementMultipliers[index]; + int propertiesCount = transformProperties.GetCount(index); + if (moveImmediately != 0) + { + float percent = math.unlerp(0, realTimeInterpolation, propertiesCount); + movementMultiplier = percent; + + movementMultiplier = math.clamp(movementMultiplier, 0.5f, 1.05f); + } + // For the time being, not moving immediately uses these multiplier calculations. + else + { + /* If there's more in queue than interpolation then begin to move faster based on overage. + * Move 5% faster for every overage. */ + int overInterpolation = propertiesCount - realTimeInterpolation; + // If needs to be adjusted. + if (overInterpolation != 0) + { + movementMultiplier += 0.015f * overInterpolation; + } + // If does not need to be adjusted. + else + { + // If interpolation is 1 then slow down just barely to accomodate for frame delta variance. + if (realTimeInterpolation == 1) + movementMultiplier = 1f; + } + + movementMultiplier = math.clamp(movementMultiplier, 0.95f, 1.05f); + } + + movementMultipliers[index] = movementMultiplier; + } + } + + [BurstCompile] + private struct AddTransformPropertiesJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + public StripedRingQueue transformProperties; + [WriteOnly] public NativeArray setMoveRatesPayloads; + + public void Execute(int index, TransformAccess graphicalTransform) + { + AddTransformProperties( + index, + graphicalTransform, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref transformProperties, + ref setMoveRatesPayloads); + } + + /// + /// Adds a new transform properties and sets move rates if needed. + /// + public static void AddTransformProperties( + int index, + TransformAccess graphicalTransform, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref StripedRingQueue transformProperties, + ref NativeArray setMoveRatesPayloads) + { + setMoveRatesPayloads[index] = new SetMoveRatesPayload(0, default); + + AddTransformPropertiesPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new AddTransformPropertiesPayload(0, default); + + transformProperties.Enqueue(index, jobPayload.tickTransformProperties); + + //If first entry then set move rates. + if (transformProperties.GetCount(index) == 1) + { + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + TransformProperties gfxProperties = GetTransformProperties(graphicalTransform, useLocalSpace); + setMoveRatesPayloads[index] = new SetMoveRatesPayload(1, gfxProperties); + } + } + } + + [BurstCompile] + private struct ClearTransformPropertiesQueueJob : IJobParallelFor + { + public NativeArray jobPayloads; + public StripedRingQueue transformProperties; + [WriteOnly] public NativeArray moveRates; + + public void Execute(int index) + { + ClearTransformPropertiesQueue( + index, + ref jobPayloads, + ref transformProperties, + ref moveRates); + } + + /// + /// Clears the pending movement queue. + /// + public static void ClearTransformPropertiesQueue( + int index, + ref NativeArray jobPayloads, + ref StripedRingQueue transformProperties, + ref NativeArray moveRates) + { + ClearTransformPropertiesQueuePayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new ClearTransformPropertiesQueuePayload(0); + + transformProperties.Clear(index); + //Also unset move rates since there is no more queue. + moveRates[index] = new MoveRates(MoveRates.UNSET_VALUE); + } + } + + [BurstCompile] + private struct ModifyTransformPropertiesJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + [ReadOnly] public NativeArray detachOnStartMask; + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [ReadOnly] public NativeArray targetSnapshot; + + public StripedRingQueue transformProperties; + + public void Execute(int index, TransformAccess trackerTransform) + { + ModifyTransformProperties( + index, + trackerTransform, + ref jobPayloads, + ref detachOnStartMask, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref targetSnapshot, + ref transformProperties); + } + + /// + /// Modifies a transform property for a tick. This does not error check for empty collections. + /// firstTick - First tick in the queue. If 0 this will be looked up. + /// + public static void ModifyTransformProperties( + int index, + TransformAccess trackerTransform, + ref NativeArray jobPayloads, + ref NativeArray detachOnStartMask, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref NativeArray targetSnapshot, + ref StripedRingQueue transformProperties) + { + ModifyTransformPropertiesPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new ModifyTransformPropertiesPayload(0, default, default); + + int queueCount = transformProperties.GetCount(index); + uint tick = jobPayload.clientTick; + /*Ticks will always be added incremental by 1 so it's safe to jump ahead the difference + * of tick and firstTick. */ + int tickIndex = (int)(tick - jobPayload.firstTick); + //Replace with new data. + if (tickIndex < queueCount) + { + TickTransformProperties tickTransformProperties = transformProperties[index, tickIndex]; + if (tick != tickTransformProperties.Tick) + { + //Should not be possible. + } + else + { + bool isDetach = detachOnStartMask[index] != 0; + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + + bool useLocalSpace = settings.UseLocalSpace; + TransformProperties newProperties = GetTrackerTransformProperties(trackerTransform, isDetach, useLocalSpace); + if (useLocalSpace) newProperties += targetSnapshot[index]; + /* Adjust transformProperties to ease into any corrections. + * The corrected value is used the more the index is to the end + * of the queue. */ + /* We want to be fully eased in by the last entry of the queue. */ + + int lastPossibleIndex = queueCount - 1; + int adjustedQueueCount = lastPossibleIndex - 1; + if (adjustedQueueCount < 1) + adjustedQueueCount = 1; + float easePercent = (float)tickIndex / adjustedQueueCount; + + //If easing. + if (easePercent < 1f) + { + if (easePercent < 1f) + easePercent = (float)Math.Pow(easePercent, adjustedQueueCount - tickIndex); + + TransformProperties oldProperties = tickTransformProperties.Properties; + newProperties.Position = Vector3.Lerp(oldProperties.Position, newProperties.Position, easePercent); + newProperties.Rotation = Quaternion.Lerp(oldProperties.Rotation, newProperties.Rotation, easePercent); + newProperties.Scale = Vector3.Lerp(oldProperties.Scale, newProperties.Scale, easePercent); + } + + transformProperties[index, tickIndex] = new TickTransformProperties(tick, newProperties); + } + } + else + { + //This should never happen. + } + } + } + + [BurstCompile] + private struct SnapNonSmoothedPropertiesJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + + public void Execute(int index, TransformAccess graphicalTransform) + { + SnapNonSmoothedProperties( + index, + graphicalTransform, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings); + } + + /// + /// Snaps non-smoothed properties to original positoin if setting is enabled. + /// + public static void SnapNonSmoothedProperties( + int index, + TransformAccess graphicalTransform, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings) + { + SnapNonSmoothedPropertiesPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new SnapNonSmoothedPropertiesPayload(0, default); + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + bool useLocalSpace = settings.UseLocalSpace; + + bool snapNonSmoothedProperties = settings.SnapNonSmoothedProperties; + //Feature is not enabled. + if (!snapNonSmoothedProperties) + return; + + TransformPropertiesFlag smoothedProperties = settings.SmoothedProperties; + + //Everything is smoothed. + if (smoothedProperties == TransformPropertiesFlag.Everything) + return; + + TransformProperties goalValues = jobPayload.goalValues; + + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Position)) + { + if (useLocalSpace) + graphicalTransform.localPosition = goalValues.Position; + else + graphicalTransform.position = goalValues.Position; + } + + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Rotation)) + { + if (useLocalSpace) + graphicalTransform.localRotation = goalValues.Rotation; + else + graphicalTransform.rotation = goalValues.Rotation; + } + + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Scale)) + graphicalTransform.localScale = goalValues.Scale; + } + } + + [BurstCompile] + private struct TeleportJob : IJobParallelForTransform + { + public NativeArray jobPayloads; + + [ReadOnly] public NativeArray useOwnerSettingsMask; + [ReadOnly] public NativeArray ownerSettings; + [ReadOnly] public NativeArray spectatorSettings; + [ReadOnly] public NativeArray preTickTrackerSnapshot; + [ReadOnly] public uint localTick; + + public StripedRingQueue transformProperties; + public NativeArray clearTransformPropertiesQueuePayloads; + [WriteOnly] public NativeArray moveRates; + [WriteOnly] public NativeArray teleportedTick; + + public void Execute(int index, TransformAccess graphicalTransform) + { + Teleport( + index, + graphicalTransform, + ref jobPayloads, + ref useOwnerSettingsMask, + ref ownerSettings, + ref spectatorSettings, + ref preTickTrackerSnapshot, + localTick, + ref transformProperties, + ref clearTransformPropertiesQueuePayloads, + ref moveRates, + ref teleportedTick); + } + + /// + /// Snaps non-smoothed properties to original positoin if setting is enabled. + /// + public static void Teleport( + int index, + TransformAccess graphicalTransform, + ref NativeArray jobPayloads, + ref NativeArray useOwnerSettingsMask, + ref NativeArray ownerSettings, + ref NativeArray spectatorSettings, + ref NativeArray preTickTrackerSnapshot, + uint localTick, + ref StripedRingQueue transformProperties, + ref NativeArray clearTransformPropertiesQueuePayloads, + ref NativeArray moveRates, + ref NativeArray teleportedTick) + { + TeleportPayload jobPayload = jobPayloads[index]; + if (jobPayload.executeMask == 0) + return; + jobPayloads[index] = new TeleportPayload(0); + + byte isOwner = useOwnerSettingsMask[index]; + MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; + bool useLocalSpace = settings.UseLocalSpace; + + AdaptiveInterpolationType adaptiveInterpolationValue = settings.AdaptiveInterpolationValue; + + //If using adaptive interpolation then set the tick which was teleported. + if (adaptiveInterpolationValue != AdaptiveInterpolationType.Off) + teleportedTick[index] = localTick; + + clearTransformPropertiesQueuePayloads[index] = new ClearTransformPropertiesQueuePayload(1); + ClearTransformPropertiesQueueJob.ClearTransformPropertiesQueue( + index, + ref clearTransformPropertiesQueuePayloads, + ref transformProperties, + ref moveRates); + + TransformProperties trackerProps = preTickTrackerSnapshot[index]; + SetTransformProperties(graphicalTransform, trackerProps, useLocalSpace); + } + } + + /// + /// Gets properties for the graphical transform in the desired space. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TransformProperties GetTransformProperties(TransformAccess transform, bool useLocalSpace) + { + if (useLocalSpace) + return transform.GetLocalProperties(); + else + return transform.GetWorldProperties(); + } + + /// + /// Gets properties for the tracker transform in the desired space, accounting for detach. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TransformProperties GetTrackerTransformProperties(TransformAccess trackerTransform, bool isDetach, bool useLocalSpace) + { + /* Return lossyScale if graphical is not attached. Otherwise, + * graphical should retain the tracker localScale so it changes + * with root. */ + + Vector3 scale = isDetach ? ExtractLossyScale(trackerTransform) : trackerTransform.localScale; + Vector3 pos; + Quaternion rot; + + if (useLocalSpace) + trackerTransform.GetCorrectLocalPositionAndRotation(out pos, out rot); + else + trackerTransform.GetPositionAndRotation(out pos, out rot); + + return new TransformProperties(pos, rot, scale); + } + + /// + /// Applies properties to a transform using the desired space for position/rotation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetTransformProperties(TransformAccess transform, in TransformProperties properties, bool useLocalSpace) + { + if (useLocalSpace) + transform.SetLocalProperties(properties); + else + transform.SetWorldProperties(properties); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector3 ExtractLossyScale(TransformAccess transform) + { + var m = transform.localToWorldMatrix; + var c0 = new float3(m.m00, m.m10, m.m20); + var c1 = new float3(m.m01, m.m11, m.m21); + var c2 = new float3(m.m02, m.m12, m.m22); + return new Vector3(math.length(c0), math.length(c1), math.length(c2)); + } + + #endregion + + + public static TransformProperties GetGraphicalWorldProperties(TransformAccess graphicalTransform) + { + return graphicalTransform.GetWorldProperties(); + } + + public static TransformProperties GetTrackerWorldProperties(TransformAccess trackerTransform, bool isDetach) + { + /* Return lossyScale if graphical is not attached. Otherwise, + * graphical should retain the tracker localScale so it changes + * with root. */ + + trackerTransform.GetPositionAndRotation(out var pos, out var rot); + Vector3 scl; + if (isDetach) + { + var m = trackerTransform.localToWorldMatrix; + var c0 = new float3(m.m00, m.m10, m.m20); + var c1 = new float3(m.m01, m.m11, m.m21); + var c2 = new float3(m.m02, m.m12, m.m22); + scl = new Vector3(math.length(c0), math.length(c1), math.length(c2)); + } + else scl = trackerTransform.localScale; + return new TransformProperties(pos, rot, scl); + } + } +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs new file mode 100644 index 00000000..92603ce9 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -0,0 +1,1336 @@ +using System.Collections.Generic; +using FishNet.Managing; +using FishNet.Managing.Predicting; +using FishNet.Managing.Timing; +using FishNet.Object; +using FishNet.Object.Prediction; +using FishNet.Transporting; +using FishNet.Utility.Extension; +using GameKit.Dependencies.Utilities; +using Unity.Collections; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using Unity.Profiling; +using UnityEngine; +using UnityEngine.Jobs; + +namespace FishNet.Component.Transforming.Beta +{ + public partial class TickSmoothingManager : MonoBehaviour + { + #region Private. + + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_ClientManager_OnClientConnectionState = new("TickSmoothingManager.Client_OnClientConnectionState"); + private static readonly ProfilerMarker _pm_OnUpdate = new("TickSmoothingManager.TimeManager_OnUpdate()"); + private static readonly ProfilerMarker _pm_OnPreTick = new("TickSmoothingManager.TimeManager_OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostTick = new("TickSmoothingManager.TimeManager_OnPostTick()"); + private static readonly ProfilerMarker _pm_Prediction_OnPostReplicateReplay = new("TickSmoothingManager.Prediction_OnPostReplicateReplay()"); + private static readonly ProfilerMarker _pm_TimeManager_OnRoundTripTimeUpdated = new("TickSmoothingManager.TimeManager_OnRoundTripTimeUpdated()"); + private static readonly ProfilerMarker _pm_MoveToTarget = new("TickSmoothingManager.MoveToTarget()"); + private static readonly ProfilerMarker _pm_ScheduleUpdateRealtimeInterpolation = new("TickSmoothingManager.UpdateRealtimeInterpolation()"); + private static readonly ProfilerMarker _pm_ScheduleDiscardExcessiveTransformPropertiesQueue = new("TickSmoothingManager.DiscardExcessiveTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_ScheduleSetMoveRates = new("TickSmoothingManager.SetMoveRates()"); + private static readonly ProfilerMarker _pm_ScheduleSetMovementMultiplier = new("TickSmoothingManager.SetMovementMultiplier()"); + private static readonly ProfilerMarker _pm_ScheduleAddTransformProperties = new("TickSmoothingManager.ScheduleAddTransformProperties()"); + private static readonly ProfilerMarker _pm_ScheduleClearTransformPropertiesQueue = new("TickSmoothingManager.ScheduleClearTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_ScheduleModifyTransformProperties = new("TickSmoothingManager.ScheduleModifyTransformProperties()"); + private static readonly ProfilerMarker _pm_ScheduleSnapNonSmoothedProperties = new("TickSmoothingManager.ScheduleSnapNonSmoothedProperties()"); + private static readonly ProfilerMarker _pm_ScheduleTeleport = new("TickSmoothingManager.ScheduleTeleport()"); + private static readonly ProfilerMarker _pm_Register = new("TickSmoothingManager.Register()"); + private static readonly ProfilerMarker _pm_Unregister = new("TickSmoothingManager.Unregister()"); + #endregion + + #region Const. + /// + /// Maximum allowed entries. + /// + private const int MAXIMUM_QUEUED = 256; + /// + /// Maximum allowed entries to be queued over the interpolation amount. + /// + private const int REQUIRED_QUEUED_OVER_INTERPOLATION = 3; + #endregion + + /// + /// NetworkManager on the same object as this script. + /// + private NetworkManager _networkManager; + /// + /// TimeManager on the same object as this script. + /// + private TimeManager _timeManager; + /// + /// PredictionManager on the same object as this script. + /// + private PredictionManager _predictionManager; + + /// + /// TrackerTransformsPool. + /// + private readonly Stack _trackerTransformsPool = new(); + /// + /// TrackerTransformsPoolHolder. + /// + private Transform _trackerTransformsPoolHolder; + + /// + /// TickSmootherController to index lookup. + /// + private readonly Dictionary _lookup = new(); + /// + /// Index to TickSmootherController and InitializationSettings lookup. + /// + private readonly List _indexToSmoother = new(); + /// + /// Index to TickSmootherController and NetworkBehaviours lookup. + /// + private readonly List _indexToNetworkBehaviour = new(); + + /// + /// Index to MoveRate lookup. + /// How quickly to move towards goal values. + /// + private NativeList _moveRates; + /// + /// Index to Owner MovementSettings lookup. + /// Settings to use for owners. + /// + private NativeList _ownerSettings; + /// + /// Index to Spectator MovementSettings lookup. + /// Settings to use for spectators. + /// + private NativeList _spectatorSettings; + + /// + /// Index to PreTickedMask lookup. + /// True if a pretick occurred since last postTick. + /// + private NativeList _preTickedMask; + /// + /// Index to MoveImmediatelyMask lookup. + /// True to begin moving soon as movement data becomes available. Movement will ease in until at interpolation value. False to prevent movement until movement data count meet interpolation. + /// + private NativeList _moveImmediatelyMask; + /// + /// Index to CanSmoothMask lookup. + /// Returns if prediction can be used on this rigidbody. + /// + private NativeList _canSmoothMask; + /// + /// Index to UseOwnerSettingsMask lookup. + /// True if to smooth using owner settings, false for spectator settings. + /// This is only used for performance gains. + /// + private NativeList _useOwnerSettingsMask; + /// + /// Index to ObjectReconcilingMask lookup. + /// + private NativeList _objectReconcilingMask; + /// + /// Index to DetachOnStartMask lookup. + /// True if to detach on smoothing start. + /// + private NativeList _detachOnStartMask; + /// + /// Index to AttachOnStopMask lookup. + /// True if to attach on smoothing stop. + /// + private NativeList _attachOnStopMask; + /// + /// Index to IsMoving lookup. + /// True if moving has started and has not been stopped. + /// + private NativeList _isMoving; + /// + /// Index to TeleportedTick lookup. + /// Last tick this was teleported on. + /// + private NativeList _teleportedTick; + /// + /// Index to RealTimeInterpolation lookup. + /// Current interpolation value, be it a flat value or adaptive. + /// + private NativeList _realTimeInterpolations; + /// + /// Index to MovementMultiplier lookup. + /// Value to multiply movement by. This is used to reduce or increase the rate the movement buffer is consumed. + /// + private NativeList _movementMultipliers; + + /// + /// Index to TransformProperties lookup. + /// TransformProperties to move towards + /// + private StripedRingQueue _transformProperties; + /// + /// Index to PreTick Graphic TransformProperties Snapshot lookup. + /// World values of the graphical after it's been aligned to initialized values in PreTick. + /// + private NativeList _preTickGraphicSnapshot; + /// + /// Index to PreTick Tracker TransformProperties Snapshot lookup. + /// World values of the graphical after it's been aligned to initialized values in PreTick. + /// + private NativeList _postTickTrackerSnapshot; + /// + /// Index to Temp Target TransformProperties Snapshot lookup. + /// + private NativeList _tempTargetSnapshot; + /// + /// Index to OutSnapGraphicWorld lookup. + /// + private NativeList _outSnapGraphicWorld; + /// + /// Index to OutEnqueueTrackerWorld lookup. + /// + private NativeList _outEnqueueTrackerWorld; + /// + /// Index to QueuedTrackerProperties lookup. + /// Properties for the tracker which are queued to be set when the tracker is setup. + /// + private NativeList _queuedTrackerProperties; + + /// + /// Index to MoveToTargetPayloads lookup. + /// + private NativeList _moveToTargetPayloads; + /// + /// Index to UpdateRealtimeInterpolationPayloads lookup. + /// + private NativeList _updateRealtimeInterpolationPayloads; + /// + /// Index to DiscardExcessiveTransformPropertiesQueuePayloads lookup. + /// + private NativeList _discardExcessivePayloads; + /// + /// Index to SetMoveRatesPayloads lookup. + /// + private NativeList _setMoveRatesPayloads; + /// + /// Index to SetMovementMultiplierPayloads lookup. + /// + private NativeList _setMovementMultiplierPayloads; + /// + /// Index to AddTransformPropertiesPayloads lookup. + /// + private NativeList _addTransformPropertiesPayloads; + /// + /// Index to ClearTransformPropertiesQueuePayloads lookup. + /// + private NativeList _clearTransformPropertiesQueuePayloads; + /// + /// Index to ModifyTransformPropertiesPayloads lookup. + /// + private NativeList _modifyTransformPropertiesPayloads; + /// + /// Index to SnapNonSmoothedPropertiesPayloads lookup. + /// + private NativeList _snapNonSmoothedPropertiesPayloads; + /// + /// Index to TeleportPayloads lookup. + /// + private NativeList _teleportPayloads; + + /// + /// Target objects TransformAccessArray. + /// Transform the graphics should follow. + /// + private TransformAccessArray _targetTaa; + /// + /// Graphical objects TransformAccessArray. + /// Cached value of the object to smooth. + /// + private TransformAccessArray _graphicalTaa; + /// + /// Tracker objects TransformAccessArray. + /// Empty gameObject containing a transform which has properties checked after each simulation. + /// If the graphical starts off as nested of targetTransform then this object is created where the graphical object is. + /// Otherwise, this object is placed directly beneath targetTransform. + /// + private TransformAccessArray _trackerTaa; + + /// + /// Subscription to callbacks state. + /// + private bool _subscribed; + #endregion + + /// + /// Initialize once from NetworkManager (pattern mirrors RollbackManager). + /// + internal void InitializeOnce_Internal(NetworkManager manager) + { + _networkManager = manager; + _timeManager = manager.TimeManager; + _predictionManager = manager.PredictionManager; + + if (!_trackerTransformsPoolHolder) + { + _trackerTransformsPoolHolder = new GameObject("Tracker Transforms Pool Holder").transform; + DontDestroyOnLoad(_trackerTransformsPoolHolder.gameObject); + } + + if (!_moveRates.IsCreated) _moveRates = new NativeList(64, Allocator.Persistent); + if (!_ownerSettings.IsCreated) _ownerSettings = new NativeList(64, Allocator.Persistent); + if (!_spectatorSettings.IsCreated) _spectatorSettings = new NativeList(64, Allocator.Persistent); + + if (!_preTickedMask.IsCreated) _preTickedMask = new NativeList(64, Allocator.Persistent); + if (!_canSmoothMask.IsCreated) _canSmoothMask = new NativeList(64, Allocator.Persistent); + if (!_useOwnerSettingsMask.IsCreated) _useOwnerSettingsMask = new NativeList(64, Allocator.Persistent); + if (!_objectReconcilingMask.IsCreated) _objectReconcilingMask = new NativeList(64, Allocator.Persistent); + if (!_detachOnStartMask.IsCreated) _detachOnStartMask = new NativeList(64, Allocator.Persistent); + if (!_attachOnStopMask.IsCreated) _attachOnStopMask = new NativeList(64, Allocator.Persistent); + if (!_moveImmediatelyMask.IsCreated) _moveImmediatelyMask = new NativeList(64, Allocator.Persistent); + if (!_isMoving.IsCreated) _isMoving = new NativeList(64, Allocator.Persistent); + if (!_teleportedTick.IsCreated) _teleportedTick = new NativeList(64, Allocator.Persistent); + if (!_realTimeInterpolations.IsCreated) _realTimeInterpolations = new NativeList(64, Allocator.Persistent); + if (!_movementMultipliers.IsCreated) _movementMultipliers = new NativeList(64, Allocator.Persistent); + + if (!_transformProperties.IsCreated) _transformProperties = new StripedRingQueue(64, MAXIMUM_QUEUED, Allocator.Persistent); + if (!_preTickGraphicSnapshot.IsCreated) _preTickGraphicSnapshot = new NativeList(64, Allocator.Persistent); + if (!_postTickTrackerSnapshot.IsCreated) _postTickTrackerSnapshot = new NativeList(64, Allocator.Persistent); + if (!_tempTargetSnapshot.IsCreated) _tempTargetSnapshot = new NativeList(64, Allocator.Persistent); + if (!_outSnapGraphicWorld.IsCreated) _outSnapGraphicWorld = new NativeList(64, Allocator.Persistent); + if (!_outEnqueueTrackerWorld.IsCreated) _outEnqueueTrackerWorld = new NativeList(64, Allocator.Persistent); + if (!_queuedTrackerProperties.IsCreated) _queuedTrackerProperties = new NativeList(64, Allocator.Persistent); + + if (!_moveToTargetPayloads.IsCreated) _moveToTargetPayloads = new NativeList(64, Allocator.Persistent); + if (!_updateRealtimeInterpolationPayloads.IsCreated) _updateRealtimeInterpolationPayloads = new NativeList(64, Allocator.Persistent); + if (!_discardExcessivePayloads.IsCreated) _discardExcessivePayloads = new NativeList(64, Allocator.Persistent); + if (!_setMoveRatesPayloads.IsCreated) _setMoveRatesPayloads = new NativeList(64, Allocator.Persistent); + if (!_setMovementMultiplierPayloads.IsCreated) _setMovementMultiplierPayloads = new NativeList(64, Allocator.Persistent); + if (!_addTransformPropertiesPayloads.IsCreated) _addTransformPropertiesPayloads = new NativeList(64, Allocator.Persistent); + if (!_clearTransformPropertiesQueuePayloads.IsCreated) _clearTransformPropertiesQueuePayloads = new NativeList(64, Allocator.Persistent); + if (!_modifyTransformPropertiesPayloads.IsCreated) _modifyTransformPropertiesPayloads = new NativeList(64, Allocator.Persistent); + if (!_snapNonSmoothedPropertiesPayloads.IsCreated) _snapNonSmoothedPropertiesPayloads = new NativeList(64, Allocator.Persistent); + if (!_teleportPayloads.IsCreated) _teleportPayloads = new NativeList(64, Allocator.Persistent); + + if (!_targetTaa.isCreated) _targetTaa = new TransformAccessArray(64); + if (!_graphicalTaa.isCreated) _graphicalTaa = new TransformAccessArray(64); + if (!_trackerTaa.isCreated) _trackerTaa = new TransformAccessArray(64); + + // Subscribe to client connection state to (un)hook timing/prediction. + _networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState; + if (_networkManager.ClientManager.Started) ChangeSubscriptions(true); + } + + private void OnDestroy() + { + ChangeSubscriptions(false); + + if (_networkManager != null) + { + _networkManager.ClientManager.OnClientConnectionState -= ClientManager_OnClientConnectionState; + } + + while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) + if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + + for (int i = 0; i < _indexToSmoother.Count; i++) + { + Transform trackerTransform = _trackerTaa[i]; + if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + } + + if (_moveRates.IsCreated) _moveRates.Dispose(); + if (_ownerSettings.IsCreated) _ownerSettings.Dispose(); + if (_spectatorSettings.IsCreated) _spectatorSettings.Dispose(); + + if (_preTickedMask.IsCreated) _preTickedMask.Dispose(); + if (_canSmoothMask.IsCreated) _canSmoothMask.Dispose(); + if (_useOwnerSettingsMask.IsCreated) _useOwnerSettingsMask.Dispose(); + if (_objectReconcilingMask.IsCreated) _objectReconcilingMask.Dispose(); + if (_detachOnStartMask.IsCreated) _detachOnStartMask.Dispose(); + if (_attachOnStopMask.IsCreated) _attachOnStopMask.Dispose(); + if (_moveImmediatelyMask.IsCreated) _moveImmediatelyMask.Dispose(); + if (_isMoving.IsCreated) _isMoving.Dispose(); + if (_teleportedTick.IsCreated) _teleportedTick.Dispose(); + if (_realTimeInterpolations.IsCreated) _realTimeInterpolations.Dispose(); + if (_movementMultipliers.IsCreated) _movementMultipliers.Dispose(); + + if (_transformProperties.IsCreated) _transformProperties.Dispose(); + if (_preTickGraphicSnapshot.IsCreated) _preTickGraphicSnapshot.Dispose(); + if (_postTickTrackerSnapshot.IsCreated) _postTickTrackerSnapshot.Dispose(); + if (_tempTargetSnapshot.IsCreated) _tempTargetSnapshot.Dispose(); + if (_outSnapGraphicWorld.IsCreated) _outSnapGraphicWorld.Dispose(); + if (_outEnqueueTrackerWorld.IsCreated) _outEnqueueTrackerWorld.Dispose(); + if (_queuedTrackerProperties.IsCreated) _queuedTrackerProperties.Dispose(); + + if (_moveToTargetPayloads.IsCreated) _moveToTargetPayloads.Dispose(); + if (_updateRealtimeInterpolationPayloads.IsCreated) _updateRealtimeInterpolationPayloads.Dispose(); + if (_discardExcessivePayloads.IsCreated) _discardExcessivePayloads.Dispose(); + if (_setMoveRatesPayloads.IsCreated) _setMoveRatesPayloads.Dispose(); + if (_setMovementMultiplierPayloads.IsCreated) _setMovementMultiplierPayloads.Dispose(); + if (_addTransformPropertiesPayloads.IsCreated) _addTransformPropertiesPayloads.Dispose(); + if (_clearTransformPropertiesQueuePayloads.IsCreated) _clearTransformPropertiesQueuePayloads.Dispose(); + if (_modifyTransformPropertiesPayloads.IsCreated) _modifyTransformPropertiesPayloads.Dispose(); + if (_snapNonSmoothedPropertiesPayloads.IsCreated) _snapNonSmoothedPropertiesPayloads.Dispose(); + if (_teleportPayloads.IsCreated) _teleportPayloads.Dispose(); + + if (_targetTaa.isCreated) _targetTaa.Dispose(); + if (_graphicalTaa.isCreated) _graphicalTaa.Dispose(); + if (_trackerTaa.isCreated) _trackerTaa.Dispose(); + + _indexToNetworkBehaviour.Clear(); + _indexToSmoother.Clear(); + _lookup.Clear(); + + _networkManager = null; + _timeManager = null; + _predictionManager = null; + } + + /// + /// Register a TickSmootherController with associated settings. + /// + public void Register(TickSmootherController smoother, InitializationSettings initializationSettings, + MovementSettings ownerSettings, MovementSettings spectatorSettings) + { + using (_pm_Register.Auto()) + { + if (smoother == null) + return; + + if (!TransformsAreValid(initializationSettings.GraphicalTransform, initializationSettings.TargetTransform)) + return; + + /* Unset scale smoothing if not detaching. This is to prevent + * the scale from changing with the parent if nested, as that + * would result in the scale being modified twice, once on the parent + * and once on the graphical. Thanks deo_wh for find! */ + if (!initializationSettings.DetachOnStart) + { + ownerSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; + spectatorSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; + } + + if (_lookup.TryGetValue(smoother, out int index)) + { + _ownerSettings[index] = ownerSettings; + _spectatorSettings[index] = spectatorSettings; + return; + } + index = _indexToSmoother.Count; + + _lookup[smoother] = index; + _indexToSmoother.Add(smoother); + _indexToNetworkBehaviour.Add(initializationSettings.InitializingNetworkBehaviour); + + _moveRates.Add(new MoveRates(MoveRates.UNSET_VALUE)); + _ownerSettings.Add(ownerSettings); + _spectatorSettings.Add(spectatorSettings); + + _preTickedMask.Add(0); + _canSmoothMask.Add( + (byte)(initializationSettings.GraphicalTransform != null && + _networkManager.IsClientStarted ? 1 : 0)); + _useOwnerSettingsMask.Add( + (byte)(initializationSettings.InitializingNetworkBehaviour == null || + initializationSettings.InitializingNetworkBehaviour.IsOwner || + !initializationSettings.InitializingNetworkBehaviour.Owner.IsValid ? 1 : 0)); + _objectReconcilingMask.Add( + (byte)(initializationSettings.InitializingNetworkBehaviour == null || + initializationSettings.InitializingNetworkBehaviour.NetworkObject == null || + initializationSettings.InitializingNetworkBehaviour.NetworkObject.IsObjectReconciling ? 1 : 0)); + _detachOnStartMask.Add( + (byte)(initializationSettings.DetachOnStart ? 1 : 0)); + _attachOnStopMask.Add( + (byte)(initializationSettings.AttachOnStop ? 1 : 0)); + _moveImmediatelyMask.Add( + (byte)(initializationSettings.MoveImmediately ? 1 : 0)); + + _isMoving.Add(default); + _teleportedTick.Add(TimeManager.UNSET_TICK); + _realTimeInterpolations.Add(default); + _movementMultipliers.Add(default); + + _transformProperties.AddQueue(); + _preTickGraphicSnapshot.Add(default); + _postTickTrackerSnapshot.Add(default); + _tempTargetSnapshot.Add(default); + _outSnapGraphicWorld.Add(default); + _outEnqueueTrackerWorld.Add(default); + _queuedTrackerProperties.Add(new NullableTransformProperties(false, default)); + + _moveToTargetPayloads.Add(new MoveToTargetPayload(0, default)); + _updateRealtimeInterpolationPayloads.Add(new UpdateRealtimeInterpolationPayload(0)); + _discardExcessivePayloads.Add(new DiscardExcessiveTransformPropertiesQueuePayload(0)); + _setMoveRatesPayloads.Add(new SetMoveRatesPayload(0, default)); + _setMovementMultiplierPayloads.Add(new SetMovementMultiplierPayload(0)); + _addTransformPropertiesPayloads.Add(new AddTransformPropertiesPayload(0, default)); + _clearTransformPropertiesQueuePayloads.Add(new ClearTransformPropertiesQueuePayload(0)); + _modifyTransformPropertiesPayloads.Add(new ModifyTransformPropertiesPayload(0, default, default)); + _snapNonSmoothedPropertiesPayloads.Add(new SnapNonSmoothedPropertiesPayload(0, default)); + _teleportPayloads.Add(new TeleportPayload(0)); + + Transform targetTransform = initializationSettings.TargetTransform; + Transform graphicalTransform = initializationSettings.GraphicalTransform; + if (!_trackerTransformsPool.TryPop(out Transform trackerTransform)) + trackerTransform = new GameObject().transform; + ProcessTransformsOnStart(trackerTransform, targetTransform, graphicalTransform, initializationSettings.DetachOnStart); + + _targetTaa.Add(targetTransform); + _graphicalTaa.Add(graphicalTransform); + _trackerTaa.Add(trackerTransform); + + //Use set method as it has sanity checks. + SetInterpolationValue(smoother, ownerSettings.InterpolationValue, forOwnerOrOfflineSmoother: true, unsetAdaptiveInterpolation: false); + SetInterpolationValue(smoother, spectatorSettings.InterpolationValue, forOwnerOrOfflineSmoother: false, unsetAdaptiveInterpolation: false); + + SetAdaptiveInterpolation(smoother, ownerSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: true); + SetAdaptiveInterpolation(smoother, spectatorSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: false); + } + } + + /// + /// Unregister a TickSmootherController. + /// + public void Unregister(TickSmootherController smoother) + { + using (_pm_Unregister.Auto()) + { + if (smoother == null || !_lookup.TryGetValue(smoother, out int index)) + return; + + bool isDetachOnStart = _detachOnStartMask[index] != 0; + bool isAttachOnStop = _attachOnStopMask[index] != 0; + Transform targetTransform = _targetTaa[index]; + Transform graphicalTransform = _graphicalTaa[index]; + Transform trackerTransform = _trackerTaa[index]; + ProcessTransformsOnStop(trackerTransform, targetTransform, graphicalTransform, isDetachOnStart, isAttachOnStop); + if (trackerTransform) + { + _trackerTransformsPool.Push(trackerTransform); + trackerTransform.SetParent(_trackerTransformsPoolHolder); + } + + int last = _indexToSmoother.Count - 1; + if (index != last) + { + var movedSmoother = _indexToSmoother[last]; + _indexToSmoother[index] = movedSmoother; + _lookup[movedSmoother] = index; + var movedNetworkBehaviour = _indexToNetworkBehaviour[last]; + _indexToNetworkBehaviour[index] = movedNetworkBehaviour; + } + + _indexToNetworkBehaviour.RemoveAt(last); + _indexToSmoother.RemoveAt(last); + _lookup.Remove(smoother); + + _moveRates.RemoveAtSwapBack(index); + _ownerSettings.RemoveAtSwapBack(index); + _spectatorSettings.RemoveAtSwapBack(index); + + _preTickedMask.RemoveAtSwapBack(index); + _canSmoothMask.RemoveAtSwapBack(index); + _useOwnerSettingsMask.RemoveAtSwapBack(index); + _objectReconcilingMask.RemoveAtSwapBack(index); + _detachOnStartMask.RemoveAtSwapBack(index); + _attachOnStopMask.RemoveAtSwapBack(index); + _moveImmediatelyMask.RemoveAtSwapBack(index); + + _isMoving.RemoveAtSwapBack(index); + _teleportedTick.RemoveAtSwapBack(index); + _realTimeInterpolations.RemoveAtSwapBack(index); + _movementMultipliers.RemoveAtSwapBack(index); + + _transformProperties.RemoveQueueAtSwapBack(index); + _preTickGraphicSnapshot.RemoveAtSwapBack(index); + _postTickTrackerSnapshot.RemoveAtSwapBack(index); + _tempTargetSnapshot.RemoveAtSwapBack(index); + _outSnapGraphicWorld.RemoveAtSwapBack(index); + _outEnqueueTrackerWorld.RemoveAtSwapBack(index); + _queuedTrackerProperties.RemoveAtSwapBack(index); + + _targetTaa.RemoveAtSwapBack(index); + _graphicalTaa.RemoveAtSwapBack(index); + _trackerTaa.RemoveAtSwapBack(index); + + _moveToTargetPayloads.RemoveAtSwapBack(index); + _updateRealtimeInterpolationPayloads.RemoveAtSwapBack(index); + _discardExcessivePayloads.RemoveAtSwapBack(index); + _setMoveRatesPayloads.RemoveAtSwapBack(index); + _setMovementMultiplierPayloads.RemoveAtSwapBack(index); + _addTransformPropertiesPayloads.RemoveAtSwapBack(index); + _clearTransformPropertiesQueuePayloads.RemoveAtSwapBack(index); + _modifyTransformPropertiesPayloads.RemoveAtSwapBack(index); + _snapNonSmoothedPropertiesPayloads.RemoveAtSwapBack(index); + _teleportPayloads.RemoveAtSwapBack(index); + } + } + + /// + /// Returns if configured transforms are valid. + /// + /// + private static bool TransformsAreValid(Transform graphicalTransform, Transform targetTransform) + { + if (graphicalTransform == null) + { + NetworkManagerExtensions.LogError($"Graphical transform cannot be null."); + return false; + } + if (targetTransform == null) + { + NetworkManagerExtensions.LogError($"Target transform on {graphicalTransform} cannot be null."); + return false; + } + if (targetTransform == graphicalTransform) + { + NetworkManagerExtensions.LogError($"Target transform cannot be the same as graphical transform on {graphicalTransform}."); + return false; + } + + return true; + } + + private static void ProcessTransformsOnStart(Transform trackerTransform, Transform targetTransform, Transform graphicalTransform, bool isDetachOnStart) + { + if (isDetachOnStart) + { + trackerTransform.SetParent(targetTransform); + + TransformProperties gfxWorldProperties = graphicalTransform.GetWorldProperties(); + graphicalTransform.SetParent(null); + graphicalTransform.SetWorldProperties(gfxWorldProperties); + } + else + { + Transform trackerParent = graphicalTransform.IsChildOf(targetTransform) ? graphicalTransform.parent : targetTransform; + trackerTransform.SetParent(trackerParent); + } + + targetTransform.GetPositionAndRotation(out var pos, out var rot); + trackerTransform.SetWorldPositionRotationAndScale(pos, rot, graphicalTransform.localScale); + trackerTransform.gameObject.name = $"{graphicalTransform.name}_Tracker"; + } + + private static void ProcessTransformsOnStop(Transform trackerTransform, Transform targetTransform, Transform graphicalTransform, bool isDetachOnStart, bool isAttachOnStop) + { + if (trackerTransform == null || targetTransform == null || graphicalTransform == null) + return; + if (ApplicationState.IsQuitting()) + return; + + trackerTransform.SetParent(null); + if (isDetachOnStart && isAttachOnStop) + { + graphicalTransform.SetParent(targetTransform.parent); + graphicalTransform.SetLocalProperties(trackerTransform.GetLocalProperties()); + } + } + + /// + /// Updates movement settings for a registered smoother. + /// Both owner and spectator settings are applied atomically. + /// + public void SetSettings(TickSmootherController smoother, in MovementSettings owner, in MovementSettings spectator) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + _ownerSettings[index] = owner; + _spectatorSettings[index] = spectator; + } + + /// + /// Sets transforms for a registered smoother (target, graphical, tracker). + /// + public void SetTransforms(TickSmootherController smoother, Transform target, Transform graphical) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + bool isDetachOnStart = _detachOnStartMask[index] != 0; + bool isAttachOnStop = _attachOnStopMask[index] != 0; + + Transform tracker = _trackerTaa[index]; + Transform prevTarget = _targetTaa[index]; + Transform prevGraphical = _graphicalTaa[index]; + ProcessTransformsOnStop(tracker, prevTarget, prevGraphical, isDetachOnStart, isAttachOnStop); + + _targetTaa[index] = target; + _graphicalTaa[index] = graphical; + ProcessTransformsOnStart(tracker, target, graphical, isDetachOnStart); + } + + /// + /// Updates the smoothedProperties value. + /// + /// TickSmootherController. + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetSmoothedProperties(TickSmootherController smoother, TransformPropertiesFlag value, bool forOwnerOrOfflineSmoother) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; + settings.SmoothedProperties = value; + + if (forOwnerOrOfflineSmoother) + _ownerSettings[index] = settings; + else + _spectatorSettings[index] = settings; + } + + /// + /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. + /// + /// TickSmootherController. + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetInterpolationValue(TickSmootherController smoother, byte value, bool forOwnerOrOfflineSmoother) => SetInterpolationValue(smoother, value, forOwnerOrOfflineSmoother, unsetAdaptiveInterpolation: true); + + /// + /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. + /// + /// TickSmootherController. + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + /// + private void SetInterpolationValue(TickSmootherController smoother, byte value, bool forOwnerOrOfflineSmoother, bool unsetAdaptiveInterpolation) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + if (value < 1) + value = 1; + + MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; + settings.InterpolationValue = value; + + if (forOwnerOrOfflineSmoother) + _ownerSettings[index] = settings; + else + _spectatorSettings[index] = settings; + + if (unsetAdaptiveInterpolation) + SetAdaptiveInterpolation(smoother, AdaptiveInterpolationType.Off, forOwnerOrOfflineSmoother); + } + + /// + /// Updates the adaptiveInterpolation value. + /// + /// TickSmootherController. + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetAdaptiveInterpolation(TickSmootherController smoother, AdaptiveInterpolationType value, bool forOwnerOrOfflineSmoother) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; + settings.AdaptiveInterpolationValue = value; + + if (forOwnerOrOfflineSmoother) + _ownerSettings[index] = settings; + else + _spectatorSettings[index] = settings; + + _updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(1); + } + + /// + /// Tries to set local properties for the graphical tracker transform. + /// + /// New values. + /// Returns true if the tracker has been setup and values have been applied to teh tracker transform. + /// When false is returned the values are cached and will be set when tracker is created. A cached value will be used every time the tracker is setup; to disable this behavior call this method with null value. + public bool TrySetGraphicalTrackerLocalProperties(TickSmootherController smoother, TransformProperties? localValues) + { + if (smoother == null) return false; + if (!_lookup.TryGetValue(smoother, out int index)) return false; + + if (_trackerTaa[index] == null || localValues == null) + { + _queuedTrackerProperties[index] = new NullableTransformProperties(localValues != null, localValues ?? default); + return false; + } + + _trackerTaa[index].SetLocalProperties(localValues.Value); + return true; + } + + public bool TryGetGraphicalTrackerLocalProperties(TickSmootherController smoother, out TransformProperties localValues) + { + localValues = default; + if (smoother == null) return false; + if (!_lookup.TryGetValue(smoother, out int index)) return false; + + Transform trackerTransform = _trackerTaa[index]; + if (trackerTransform != null) + { + localValues = new(trackerTransform.localPosition, trackerTransform.localRotation, trackerTransform.localScale); + return true; + } + + NullableTransformProperties queuedTrackerProperties = _queuedTrackerProperties[index]; + if (queuedTrackerProperties.IsExist != 0) + { + localValues = queuedTrackerProperties.Properties; + return true; + } + + // Fall through. + return false; + } + + /// + /// Marks to teleports the graphical to it's starting position and clears the internal movement queue at the PreTick. + /// + public void Teleport(TickSmootherController smoother) + { + if (smoother == null) return; + if (!_lookup.TryGetValue(smoother, out int index)) return; + + _teleportPayloads[index] = new TeleportPayload(1); + } + + private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs args) + { + using (_pm_ClientManager_OnClientConnectionState.Auto()) + { + while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) + if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + + if (args.ConnectionState == LocalConnectionState.Started) + ChangeSubscriptions(true); + else + ChangeSubscriptions(false); + } + } + + private void ChangeSubscriptions(bool subscribe) + { + if (_timeManager == null) + return; + + if (_subscribed == subscribe) + return; + + _subscribed = subscribe; + + if (subscribe) + { + _timeManager.OnUpdate += TimeManager_OnUpdate; + _timeManager.OnPreTick += TimeManager_OnPreTick; + _timeManager.OnPostTick += TimeManager_OnPostTick; + _timeManager.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; + + if (_predictionManager != null) + _predictionManager.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; + } + else + { + _timeManager.OnUpdate -= TimeManager_OnUpdate; + _timeManager.OnPreTick -= TimeManager_OnPreTick; + _timeManager.OnPostTick -= TimeManager_OnPostTick; + _timeManager.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; + + if (_predictionManager != null) + _predictionManager.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; + } + } + + /// + /// Called every frame. + /// + private void TimeManager_OnUpdate() + { + using (_pm_OnUpdate.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + int batchSize = ComputeBatchSize(count); + + var job = new UpdateJob + { + canSmoothMask = _canSmoothMask.AsArray(), + deltaTime = Time.deltaTime, + + moveToTargetPayloads = _moveToTargetPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize); + JobHandle moveToTargetHandle = ScheduleMoveToTarget(innerHandle); + moveToTargetHandle.Complete(); + } + } + + /// + /// Called when the TimeManager invokes OnPreTick. + /// + private void TimeManager_OnPreTick() + { + using (_pm_OnPreTick.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + int batchSize = ComputeBatchSize(count); + + for (int i = 0; i < count; i++) + { + Transform graphicalTransform = _graphicalTaa[i]; + NetworkBehaviour networkBehaviour = _indexToNetworkBehaviour[i]; + _canSmoothMask[i] = + (byte)(graphicalTransform != null && + _networkManager.IsClientStarted ? 1 : 0); + + _useOwnerSettingsMask[i] = + (byte)(networkBehaviour == null || + networkBehaviour.IsOwner || + !networkBehaviour.Owner.IsValid ? 1 : 0); + + _objectReconcilingMask[i] = + (byte)(networkBehaviour == null || + networkBehaviour.NetworkObject == null || + networkBehaviour.NetworkObject.IsObjectReconciling ? 1 : 0); + } + + JobHandle preTickMarkHandle = new PreTickMarkJob + { + canSmoothMask = _canSmoothMask.AsArray(), + preTickedMask = _preTickedMask.AsArray(), + discardExcessivePayloads = _discardExcessivePayloads.AsArray() + }.Schedule(count, batchSize); + + JobHandle discardExcessiveHandle = ScheduleDiscardExcessiveTransformPropertiesQueue(preTickMarkHandle); + + JobHandle teleportHandle = ScheduleTeleport(discardExcessiveHandle); + + JobHandle preTickCaptureGraphicalHandle = new PreTickCaptureGraphicalJob + { + canSmoothMask = _canSmoothMask.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + graphicSnapshot = _preTickGraphicSnapshot.AsArray() + }.Schedule(_graphicalTaa, teleportHandle); + + preTickCaptureGraphicalHandle.Complete(); + } + } + + /// + /// Called when the TimeManager invokes OnPostReplay. + /// + /// Replay tick for the local client. + /// + /// This is dependent on the initializing NetworkBehaviour being set. + private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) + { + using (_pm_Prediction_OnPostReplicateReplay.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + int batchSize = ComputeBatchSize(count); + + var job = new PostReplicateReplayJob + { + clientTick = clientTick, + teleportedTick = _teleportedTick.AsArray(), + objectReconcilingMask = _objectReconcilingMask.AsArray(), + + modifyTransformPropertiesPayloads = _modifyTransformPropertiesPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize); + JobHandle modifyTransformPropertiesHandle = ScheduleModifyTransformProperties(innerHandle); + modifyTransformPropertiesHandle.Complete(); + } + } + + /// + /// Called when TimeManager invokes OnPostTick. + /// + private void TimeManager_OnPostTick() + { + using (_pm_OnPostTick.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + + JobHandle captureLocalTargetHandle = new CaptureLocalTargetJob + { + canSmoothMask = _canSmoothMask.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + targetSnapshot = _tempTargetSnapshot.AsArray() + }.Schedule(_targetTaa); + + JobHandle postTickCaptureTrackerHandle = new PostTickCaptureTrackerJob + { + canSmoothMask = _canSmoothMask.AsArray(), + detachOnStartMask = _detachOnStartMask.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + targetSnapshot = _tempTargetSnapshot.AsArray(), + trackerSnapshot = _postTickTrackerSnapshot.AsArray() + }.Schedule(_trackerTaa, captureLocalTargetHandle); + + JobHandle postTickHandle = new PostTickJob + { + clientTick = _timeManager.LocalTick, + canSmoothMask = _canSmoothMask.AsArray(), + teleportedTick = _teleportedTick.AsArray(), + preTickedMask = _preTickedMask.AsArray(), + detachOnStartMask = _detachOnStartMask.AsArray(), + postTickTrackerSnapshot = _postTickTrackerSnapshot.AsArray(), + preTickGraphicSnapshot = _preTickGraphicSnapshot.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + discardExcessivePayloads = _discardExcessivePayloads.AsArray(), + snapNonSmoothedPropertiesPayloads = _snapNonSmoothedPropertiesPayloads.AsArray(), + addTransformPropertiesPayloads = _addTransformPropertiesPayloads.AsArray() + }.Schedule(_graphicalTaa, postTickCaptureTrackerHandle); + + JobHandle discardExcessiveHandle = ScheduleDiscardExcessiveTransformPropertiesQueue(postTickHandle); + JobHandle snapNonSmoothedPropertiesHandle = ScheduleSnapNonSmoothedProperties(discardExcessiveHandle); + JobHandle addTransformPropertiesHandle = ScheduleAddTransformProperties(snapNonSmoothedPropertiesHandle); + addTransformPropertiesHandle.Complete(); + } + } + + private void TimeManager_OnRoundTripTimeUpdated(long rttMs) + { + using (_pm_TimeManager_OnRoundTripTimeUpdated.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return; + int batchSize = ComputeBatchSize(count); + + var job = new RoundTripTimeUpdatedJob + { + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + updateRealtimeInterpolationPayloads = _updateRealtimeInterpolationPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize); + JobHandle updateRealtimeInterpolationHandle = ScheduleUpdateRealtimeInterpolation(innerHandle); + updateRealtimeInterpolationHandle.Complete(); + } + } + + /// + /// Moves transform to target values. + /// + public JobHandle ScheduleMoveToTarget(in JobHandle outerHandle = default) + { + using (_pm_MoveToTarget.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + var job = new MoveToTargetJob + { + jobPayloads = _moveToTargetPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + realTimeInterpolations = _realTimeInterpolations.AsArray(), + moveImmediatelyMask = _moveImmediatelyMask.AsArray(), + tickDelta = (float)_timeManager.TickDelta, + + isMoving = _isMoving.AsArray(), + movementMultipliers = _movementMultipliers.AsArray(), + + transformProperties = _transformProperties, + moveRates = _moveRates.AsArray(), + + setMoveRatesPayloads = _setMoveRatesPayloads.AsArray(), + setMovementMultiplierPayloads = _setMovementMultiplierPayloads.AsArray(), + clearTransformPropertiesQueuePayloads = _clearTransformPropertiesQueuePayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); + return innerHandle; + } + } + + /// + /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. + /// + public JobHandle ScheduleUpdateRealtimeInterpolation(in JobHandle outerHandle = default) + { + using (_pm_ScheduleUpdateRealtimeInterpolation.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new UpdateRealtimeInterpolationJob + { + jobPayloads = _updateRealtimeInterpolationPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + tickDelta = (float)_timeManager.TickDelta, + tickRate = _timeManager.TickRate, + rtt = _timeManager.RoundTripTime, + localTick = _timeManager.LocalTick, + isServerOnlyStarted = _networkManager.IsServerOnlyStarted, + + realTimeInterpolations = _realTimeInterpolations.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + return innerHandle; + } + } + + /// + /// Discards datas over interpolation limit from movement queue. + /// + private JobHandle ScheduleDiscardExcessiveTransformPropertiesQueue(in JobHandle outerHandle = default) + { + using (_pm_ScheduleDiscardExcessiveTransformPropertiesQueue.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new DiscardExcessiveTransformPropertiesQueueJob + { + jobPayloads = _discardExcessivePayloads.AsArray(), + + realTimeInterpolations = _realTimeInterpolations.AsArray(), + requiredQueuedOverInterpolation = REQUIRED_QUEUED_OVER_INTERPOLATION, + + transformProperties = _transformProperties, + setMoveRatesPayloads = _setMoveRatesPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + JobHandle setMoveRatesHandle = ScheduleSetMoveRates(innerHandle); + return setMoveRatesHandle; + } + } + + /// + /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. + /// + private JobHandle ScheduleSetMoveRates(in JobHandle outerHandle = default) + { + using (_pm_ScheduleSetMoveRates.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new SetMoveRatesJob + { + jobPayloads = _setMoveRatesPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + transformProperties = _transformProperties, + tickDelta = (float)_timeManager.TickDelta, + + moveRates = _moveRates.AsArray(), + setMovementMultiplierPayloads = _setMovementMultiplierPayloads.AsArray(), + }; + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + JobHandle setMovementMultiplierHandle = ScheduleSetMovementMultiplier(innerHandle); + return setMovementMultiplierHandle; + } + } + + private JobHandle ScheduleSetMovementMultiplier(in JobHandle outerHandle = default) + { + using (_pm_ScheduleSetMovementMultiplier.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new SetMovementMultiplierJob + { + jobPayloads = _setMovementMultiplierPayloads.AsArray(), + + transformProperties = _transformProperties, + realTimeInterpolations = _realTimeInterpolations.AsArray(), + moveImmediatelyMask = _moveImmediatelyMask.AsArray(), + + movementMultipliers = _movementMultipliers.AsArray() + }; + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + return innerHandle; + } + } + + /// + /// Adds a new transform properties and sets move rates if needed. + /// + private JobHandle ScheduleAddTransformProperties(JobHandle outerHandle) + { + using (_pm_ScheduleAddTransformProperties.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + var job = new AddTransformPropertiesJob + { + jobPayloads = _addTransformPropertiesPayloads.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + + transformProperties = _transformProperties, + setMoveRatesPayloads = _setMoveRatesPayloads.AsArray() + }; + + JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); + JobHandle setMoveRatesHandle = ScheduleSetMoveRates(innerHandle); + return setMoveRatesHandle; + } + } + + /// + /// Clears the pending movement queue. + /// + private JobHandle ScheduleClearTransformPropertiesQueue(JobHandle outerHandle) + { + using (_pm_ScheduleClearTransformPropertiesQueue.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + int batchSize = ComputeBatchSize(count); + + var job = new ClearTransformPropertiesQueueJob + { + jobPayloads = _clearTransformPropertiesQueuePayloads.AsArray(), + + transformProperties = _transformProperties, + moveRates = _moveRates.AsArray() + }; + + JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); + return innerHandle; + } + } + + /// + /// Modifies a transform property for a tick. This does not error check for empty collections. + /// firstTick - First tick in the queue. If 0 this will be looked up. + /// + private JobHandle ScheduleModifyTransformProperties(JobHandle outerHandle) + { + using (_pm_ScheduleModifyTransformProperties.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + JobHandle captureLocalTargetHandle = new CaptureLocalTargetJob + { + canSmoothMask = _canSmoothMask.AsArray(), + targetSnapshot = _tempTargetSnapshot.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray() + }.Schedule(_targetTaa, outerHandle); + + JobHandle modifyTransformPropertiesHandle = new ModifyTransformPropertiesJob + { + jobPayloads = _modifyTransformPropertiesPayloads.AsArray(), + detachOnStartMask = _detachOnStartMask.AsArray(), + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + targetSnapshot = _tempTargetSnapshot.AsArray(), + transformProperties = _transformProperties, + }.Schedule(_trackerTaa, captureLocalTargetHandle); + + return modifyTransformPropertiesHandle; + } + } + + /// + /// Snaps non-smoothed properties to original positoin if setting is enabled. + /// + private JobHandle ScheduleSnapNonSmoothedProperties(JobHandle outerHandle) + { + using (_pm_ScheduleSnapNonSmoothedProperties.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + var job = new SnapNonSmoothedPropertiesJob + { + jobPayloads = _snapNonSmoothedPropertiesPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + }; + + JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); + return innerHandle; + } + } + + /// + /// Teleports the graphical to it's starting position and clears the internal movement queue. + /// + private JobHandle ScheduleTeleport(JobHandle outerHandle) + { + using (_pm_ScheduleTeleport.Auto()) + { + int count = _indexToSmoother.Count; + if (count == 0) return outerHandle; + + var job = new TeleportJob + { + jobPayloads = _teleportPayloads.AsArray(), + + useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), + ownerSettings = _ownerSettings.AsArray(), + spectatorSettings = _spectatorSettings.AsArray(), + preTickTrackerSnapshot = _postTickTrackerSnapshot.AsArray(), + localTick = _timeManager.LocalTick, + + transformProperties = _transformProperties, + clearTransformPropertiesQueuePayloads = _clearTransformPropertiesQueuePayloads.AsArray(), + moveRates = _moveRates.AsArray(), + teleportedTick = _teleportedTick.AsArray() + }; + + JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); + JobHandle clearTransformPropertiesQueueHandle = ScheduleClearTransformPropertiesQueue(innerHandle); + return clearTransformPropertiesQueueHandle; + } + } + + private static int ComputeBatchSize(int length, int minBatch = 1, int maxBatch = 128) + { + if (length <= 0) return 1; + + // +1: main thread + worker threads + int workers = JobsUtility.JobWorkerCount + 1; + + // Aim for ~4 waves of batches across all workers. + int targetBatches = Mathf.Max(1, workers * 4); + + // CeilDiv to get iterations per batch + int batch = (length + targetBatches - 1) / targetBatches; + + return Mathf.Clamp(batch, minBatch, maxBatch); + } + } +} diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs index 5f621a34..76c83f7c 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs @@ -5,8 +5,9 @@ using FishNet.Object.Prediction; using FishNet.Utility.Extension; using GameKit.Dependencies.Utilities; -using Unity.Profiling; using UnityEngine; +using UnityEngine.Profiling; +using Unity.Profiling; using UnityEngine.Scripting; namespace FishNet.Component.Transforming.Beta @@ -16,41 +17,7 @@ namespace FishNet.Component.Transforming.Beta /// public sealed class UniversalTickSmoother : IResettable { - #region Types. - [Preserve] - private struct TickTransformProperties - { - public readonly uint Tick; - public readonly TransformProperties Properties; - - public TickTransformProperties(uint tick, Transform t) - { - Tick = tick; - Properties = new(t.localPosition, t.localRotation, t.localScale); - } - - public TickTransformProperties(uint tick, Transform t, Vector3 localScale) - { - Tick = tick; - Properties = new(t.localPosition, t.localRotation, localScale); - } - - public TickTransformProperties(uint tick, TransformProperties tp) - { - Tick = tick; - Properties = tp; - } - - public TickTransformProperties(uint tick, TransformProperties tp, Vector3 localScale) - { - Tick = tick; - tp.Scale = localScale; - Properties = tp; - } - } - #endregion - - #region public. + #region Public. /// /// True if currently initialized. /// @@ -58,6 +25,23 @@ public TickTransformProperties(uint tick, TransformProperties tp, Vector3 localS #endregion #region Private. + + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new ProfilerMarker("UniversalTickSmoother.UpdateRealtimeInterpolation()"); + private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("UniversalTickSmoother.OnUpdate(float)"); + private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("UniversalTickSmoother.OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new ProfilerMarker("UniversalTickSmoother.OnPostReplicateReplay(uint)"); + private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("UniversalTickSmoother.OnPostTick(uint)"); + private static readonly ProfilerMarker _pm_ClearTPQ = new ProfilerMarker("UniversalTickSmoother.ClearTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_DiscardTPQ = new ProfilerMarker("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_AddTP = new ProfilerMarker("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)"); + private static readonly ProfilerMarker _pm_ModifyTP = new ProfilerMarker("UniversalTickSmoother.ModifyTransformProperties(uint, uint)"); + private static readonly ProfilerMarker _pm_SetMoveRates = new ProfilerMarker("UniversalTickSmoother.SetMoveRates(in TransformProperties)"); + private static readonly ProfilerMarker _pm_MoveToTarget = new ProfilerMarker("UniversalTickSmoother.MoveToTarget(float)"); + + #endregion + /// /// How quickly to move towards goal values. /// @@ -139,7 +123,7 @@ public TickTransformProperties(uint tick, TransformProperties tp, Vector3 localS /// /// TransformProperties to move towards. /// - private BasicQueue _transformProperties; + private BasicQueue _transformProperties; /// /// True if to smooth using owner settings, false for spectator settings. /// This is only used for performance gains. @@ -167,22 +151,6 @@ public TickTransformProperties(uint tick, TransformProperties tp, Vector3 localS private bool _isMoving; #endregion - #region Private Profiler Markers - // private static readonly ProfilerMarker _pm_ConsumeFixedOffset = new("UniversalTickSmoother.ConsumeFixedOffset(uint)"); - // private static readonly ProfilerMarker _pm_AxiswiseClamp = new("UniversalTickSmoother.AxiswiseClamp(TransformProperties, TransformProperties)"); - private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new("UniversalTickSmoother.UpdateRealtimeInterpolation()"); - private static readonly ProfilerMarker _pm_OnUpdate = new("UniversalTickSmoother.OnUpdate(float)"); - private static readonly ProfilerMarker _pm_OnPreTick = new("UniversalTickSmoother.OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new("UniversalTickSmoother.OnPostReplicateReplay(uint)"); - private static readonly ProfilerMarker _pm_OnPostTick = new("UniversalTickSmoother.OnPostTick(uint)"); - private static readonly ProfilerMarker _pm_ClearTPQ = new("UniversalTickSmoother.ClearTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_DiscardTPQ = new("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_AddTP = new("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)"); - private static readonly ProfilerMarker _pm_ModifyTP = new("UniversalTickSmoother.ModifyTransformProperties(uint, uint)"); - private static readonly ProfilerMarker _pm_SetMoveRates = new("UniversalTickSmoother.SetMoveRates(in TransformProperties)"); - private static readonly ProfilerMarker _pm_MoveToTarget = new("UniversalTickSmoother.MoveToTarget(float)"); - #endregion - #region Const. /// /// Maximum allowed entries to be queued over the interpolation amount. @@ -305,8 +273,8 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe if (!TransformsAreValid(graphicalTransform, targetTransform)) return; - - _transformProperties = CollectionCaches.RetrieveBasicQueue(); + + _transformProperties = CollectionCaches.RetrieveBasicQueue(); _controllerMovementSettings = ownerSettings; _spectatorMovementSettings = spectatorSettings; @@ -358,7 +326,7 @@ void SetupTrackerTransform() _trackerTransform.SetParent(trackerParent); } - _trackerTransform.SetLocalPositionRotationAndScale(_graphicalTransform.localPosition, graphicalTransform.localRotation, graphicalTransform.localScale); + _trackerTransform.SetWorldPositionRotationAndScale(_targetTransform.position, _targetTransform.rotation, graphicalTransform.localScale); } IsInitialized = true; @@ -588,14 +556,14 @@ public void OnPostTick(uint clientTick) //If preticked then previous transform values are known. if (_preTicked) { - DiscardExcessiveTransformPropertiesQueue(); - + var trackerProps = GetTrackerWorldProperties(); //Only needs to be put to pretick position if not detached. if (!_detachOnStart) _graphicalTransform.SetWorldProperties(_graphicsPreTickWorldValues); + DiscardExcessiveTransformPropertiesQueue(); //SnapNonSmoothedProperties(); - AddTransformProperties(clientTick); + AddTransformProperties(clientTick, trackerProps); } //If did not pretick then the only thing we can do is snap to instantiated values. else @@ -684,11 +652,14 @@ private void DiscardExcessiveTransformPropertiesQueue() //If there are entries to dequeue. if (dequeueCount > 0) { - TickTransformProperties tpp = default; + TickSmoothingManager.TickTransformProperties ttp = default; for (int i = 0; i < dequeueCount; i++) - tpp = _transformProperties.Dequeue(); + { + ttp = _transformProperties.Dequeue(); + } - SetMoveRates(tpp.Properties); + var nextValues = ttp.Properties; + SetMoveRates(nextValues); } } } @@ -696,12 +667,12 @@ private void DiscardExcessiveTransformPropertiesQueue() /// /// Adds a new transform properties and sets move rates if needed. /// - private void AddTransformProperties(uint tick) + private void AddTransformProperties(uint tick, TransformProperties properties) { using (_pm_AddTP.Auto()) { - TickTransformProperties tpp = new(tick, GetTrackerWorldProperties()); - _transformProperties.Enqueue(tpp); + TickSmoothingManager.TickTransformProperties ttp = new(tick, properties); + _transformProperties.Enqueue(ttp); //If first entry then set move rates. if (_transformProperties.Count == 1) @@ -887,7 +858,7 @@ private void MoveToTarget(float delta) } } - TickTransformProperties ttp = _transformProperties.Peek(); + TickSmoothingManager.TickTransformProperties ttp = _transformProperties.Peek(); TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; @@ -969,7 +940,7 @@ public void ResetState() _teleportedTick = TimeManager.UNSET_TICK; _movementMultiplier = 1f; - CollectionCaches.StoreAndDefault(ref _transformProperties); + CollectionCaches.StoreAndDefault(ref _transformProperties); _moveRates = default; _preTicked = default; _queuedTrackerProperties = null; diff --git a/Assets/FishNet/Runtime/Managing/NetworkManager.cs b/Assets/FishNet/Runtime/Managing/NetworkManager.cs index 6f0736d5..216aed6a 100644 --- a/Assets/FishNet/Runtime/Managing/NetworkManager.cs +++ b/Assets/FishNet/Runtime/Managing/NetworkManager.cs @@ -25,6 +25,7 @@ using FishNet.Managing.Statistic; using FishNet.Utility.Performance; using FishNet.Component.ColliderRollback; +using FishNet.Component.Transforming.Beta; using FishNet.Configuring; using FishNet.Configuring.EditorCloning; using FishNet.Managing.Predicting; @@ -121,6 +122,10 @@ public static IReadOnlyList Instances /// public ObserverManager ObserverManager { get; private set; } /// + /// TickSmoothingManager for this NetworkManager. + /// + public TickSmoothingManager TickSmoothingManager { get; private set; } + /// /// DebugManager for this NetworkManager. /// public DebugManager DebugManager { get; private set; } @@ -318,6 +323,7 @@ private void Awake() TimeManager = GetOrCreateComponent(); SceneManager = GetOrCreateComponent(); ObserverManager = GetOrCreateComponent(); + TickSmoothingManager = GetOrCreateComponent(); RollbackManager = GetOrCreateComponent(); PredictionManager = GetOrCreateComponent(); StatisticsManager = GetOrCreateComponent(); @@ -362,6 +368,7 @@ private void InitializeComponents() SceneManager.InitializeOnce_Internal(this); ObserverManager.InitializeOnce_Internal(this); + TickSmoothingManager.InitializeOnce_Internal(this); RollbackManager.InitializeOnce_Internal(this); PredictionManager.InitializeOnce(this); StatisticsManager.InitializeOnce_Internal(this); diff --git a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs index c81ec7d1..58c37480 100644 --- a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs +++ b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs @@ -8,6 +8,7 @@ using System; using System.Runtime.CompilerServices; using FishNet.Managing.Statistic; +using Unity.Mathematics; using Unity.Profiling; using UnityEngine; using SystemStopwatch = System.Diagnostics.Stopwatch; @@ -284,10 +285,11 @@ 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_OnFixedUpdate = new("TimeManager.OnFixedUpdate()"); private static readonly ProfilerMarker _pm_OnPostPhysicsSimulation = new("TimeManager.OnPostPhysicsSimulation(float)"); private static readonly ProfilerMarker _pm_OnPrePhysicsSimulation = new("TimeManager.OnPrePhysicsSimulation(float)"); @@ -683,97 +685,100 @@ 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; - } - - double time = Time.unscaledDeltaTime; + bool isClient = NetworkManager.IsClientStarted; + bool isServer = NetworkManager.IsServerStarted; - _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) { - using (_pm_OnPrePhysicsSimulation.Auto()) - OnPrePhysicsSimulation?.Invoke(tickDelta); - using (_pm_PhysicsSimulate.Auto()) - Physics.Simulate(tickDelta); - using (_pm_Physics2DSimulate.Auto()) - Physics2D.Simulate(tickDelta); - using (_pm_OnPostPhysicsSimulation.Auto()) - OnPostPhysicsSimulation?.Invoke(tickDelta); + // Tell predicted objecs to reconcile before OnTick. + NetworkManager.PredictionManager.ReconcileToStates(); + + using (_pm_OnTick.Auto()) + OnTick?.Invoke(); + + if (PhysicsMode == PhysicsMode.TimeManager && tickDelta > 0f) + { + using (_pm_OnPrePhysicsSimulation.Auto()) + OnPrePhysicsSimulation?.Invoke(tickDelta); + using (_pm_PhysicsSimulate.Auto()) + Physics.Simulate(tickDelta); + using (_pm_Physics2DSimulate.Auto()) + Physics2D.Simulate(tickDelta); + using (_pm_OnPostPhysicsSimulation.Auto()) + OnPostPhysicsSimulation?.Invoke(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. @@ -795,12 +800,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(); @@ -811,6 +818,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; @@ -892,6 +900,7 @@ public double TicksToTime(TickType tickType = TickType.LocalTick) /// /// PreciseTick to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(PreciseTick pt) { double tickTime = TicksToTime(pt.Tick); @@ -904,6 +913,7 @@ public double TicksToTime(PreciseTick pt) /// /// Ticks to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(uint ticks) { return TickDelta * (double)ticks; @@ -993,16 +1003,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); } /// @@ -1010,6 +1032,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; @@ -1021,6 +1044,7 @@ public uint TimeToTicks(long time, TickRounding rounding = TickRounding.RoundNea /// /// Time to convert. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public PreciseTick TimeToPreciseTick(double time) => time.AsPreciseTick(TickDelta); /// diff --git a/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs index e647b7ef..befe7acb 100644 --- a/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs +++ b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs @@ -1,6 +1,10 @@ -using GameKit.Dependencies.Utilities; -using Unity.Profiling; +using System.Runtime.CompilerServices; +using FishNet.Utility.Extension; +using GameKit.Dependencies.Utilities; +using Unity.Mathematics; using UnityEngine; +using Unity.Profiling; +using UnityEngine.Jobs; using UnityEngine.Scripting; namespace FishNet.Object.Prediction @@ -11,6 +15,14 @@ namespace FishNet.Object.Prediction [Preserve] public struct MoveRates { + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_GetMoveRatesFull = new ProfilerMarker("MoveRates.GetMoveRates(float3, float3, quaternion, quaternion, float3, float3, float, float)"); + private static readonly ProfilerMarker _pm_GetMoveRatesVec = new ProfilerMarker("MoveRates.GetMoveRates(float3, float3, float, float)"); + private static readonly ProfilerMarker _pm_Move = new ProfilerMarker("MoveRates.Move(TransformAccess, TransformPropertiesFlag, float3, float, quaternion, float, float3, float, float, bool)"); + + #endregion + /// /// Rate at which to move Position. /// @@ -102,25 +114,22 @@ public MoveRates(float position, float rotation, float scale, float timeRemainin /// public bool IsScaleInstantValue => Scale == INSTANT_VALUE; - #region Private Profiler Markers - private static readonly ProfilerMarker _pm_GetMoveRatesFull = new("MoveRates.GetMoveRates(Vector3, Vector3, Quaternion, Quaternion, Vector3, Vector3, float, float)"); - private static readonly ProfilerMarker _pm_GetMoveRatesVec = new("MoveRates.GetMoveRates(Vector3, Vector3, float, float)"); - private static readonly ProfilerMarker _pm_Move = new("MoveRates.Move(Transform, TransformPropertiesFlag, Vector3, float, Quaternion, float, Vector3, float, float, bool)"); - #endregion - /// /// Sets all rates to instant. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetInstantRates() => Update(INSTANT_VALUE); /// /// Sets all rates to the same value. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float value) => Update(value, value, value); /// /// Sets rates for each property. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale) { Position = position; @@ -133,6 +142,7 @@ public void Update(float position, float rotation, float scale) /// /// Sets rates for each property. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale, float timeRemaining) { Position = position; @@ -146,11 +156,13 @@ public void Update(float position, float rotation, float scale, float timeRemain /// /// Updates to new values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(MoveRates moveRates) => Update(moveRates.Position, moveRates.Rotation, moveRates.Scale, moveRates.TimeRemaining); /// /// Updates to new values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(MoveRatesCls moveRates) => Update(moveRates.Position, moveRates.Rotation, moveRates.Scale, moveRates.TimeRemaining); /// @@ -166,38 +178,91 @@ public void ResetState() /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetWorldMoveRates(Transform from, Transform to, float duration, float teleportThreshold) { - return GetMoveRates(from.position, to.position, from.rotation, to.rotation, from.localScale, to.localScale, duration, teleportThreshold); + from.GetPositionAndRotation(out var fromPos, out var fromRot); + to.GetPositionAndRotation(out var toPos, out var toRot); + return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); + } + + /// + /// Returns a new MoveRates based on previous values, and a transforms current position. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetWorldMoveRates(TransformAccess from, TransformAccess to, float duration, float teleportThreshold) + { + from.GetPositionAndRotation(out var fromPos, out var fromRot); + to.GetPositionAndRotation(out var toPos, out var toRot); + return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); } /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetLocalMoveRates(Transform from, Transform to, float duration, float teleportThreshold) { - return GetMoveRates(from.localPosition, to.localPosition, from.localRotation, to.localRotation, from.localScale, to.localScale, duration, teleportThreshold); + from.GetPositionAndRotation(out var fromPos, out var fromRot); + to.GetPositionAndRotation(out var toPos, out var toRot); + return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); } - + + /// + /// Returns a new MoveRates based on previous values, and a transforms current position. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetLocalMoveRates(TransformAccess from, TransformAccess to, float duration, float teleportThreshold) + { + from.GetPositionAndRotation(out var fromPos, out var fromRot); + to.GetPositionAndRotation(out var toPos, out var toRot); + return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); + } + /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetWorldMoveRates(TransformProperties prevValues, Transform t, float duration, float teleportThreshold) { - return GetMoveRates(prevValues.Position, t.position, prevValues.Rotation, t.rotation, prevValues.Scale, t.localScale, duration, teleportThreshold); + t.GetPositionAndRotation(out var pos, out var rot); + return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); + } + + /// + /// Returns a new MoveRates based on previous values, and a transforms current position. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetWorldMoveRates(TransformProperties prevValues, TransformAccess t, float duration, float teleportThreshold) + { + t.GetPositionAndRotation(out var pos, out var rot); + return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); } /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetLocalMoveRates(TransformProperties prevValues, Transform t, float duration, float teleportThreshold) { - return GetMoveRates(prevValues.Position, t.localPosition, prevValues.Rotation, t.localRotation, prevValues.Scale, t.localScale, duration, teleportThreshold); + t.GetLocalPositionAndRotation(out var pos, out var rot); + return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); + } + + /// + /// Returns a new MoveRates based on previous values, and a transforms current position. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetLocalMoveRates(TransformProperties prevValues, TransformAccess t, float duration, float teleportThreshold) + { + t.GetCorrectLocalPositionAndRotation(out var pos, out var rot); + return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); } /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetMoveRates(TransformProperties prevValues, TransformProperties nextValues, float duration, float teleportThreshold) { return GetMoveRates(prevValues.Position, nextValues.Position, prevValues.Rotation, nextValues.Rotation, prevValues.Scale, nextValues.Scale, duration, teleportThreshold); @@ -206,7 +271,8 @@ public static MoveRates GetMoveRates(TransformProperties prevValues, TransformPr /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// - public static MoveRates GetMoveRates(Vector3 fromPosition, Vector3 toPosition, Quaternion fromRotation, Quaternion toRotation, Vector3 fromScale, Vector3 toScale, float duration, float teleportThreshold) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MoveRates GetMoveRates(float3 fromPosition, float3 toPosition, quaternion fromRotation, quaternion toRotation, float3 fromScale, float3 toScale, float duration, float teleportThreshold) { using (_pm_GetMoveRatesFull.Auto()) { @@ -232,7 +298,8 @@ public static MoveRates GetMoveRates(Vector3 fromPosition, Vector3 toPosition, Q /// /// Gets a move rate for two Vector3s. /// - public static float GetMoveRate(Vector3 fromPosition, Vector3 toPosition, float duration, float teleportThreshold) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetMoveRate(float3 fromPosition, float3 toPosition, float duration, float teleportThreshold) { using (_pm_GetMoveRatesVec.Auto()) { @@ -258,7 +325,8 @@ public static float GetMoveRate(Vector3 fromPosition, Vector3 toPosition, float /// /// Gets a move rate for two Quaternions. /// - public static float GetMoveRate(Quaternion fromRotation, Quaternion toRotation, float duration) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetMoveRate(quaternion fromRotation, quaternion toRotation, float duration) { float rate = toRotation.GetRate(fromRotation, duration, out _); float rotationRate = rate.SetIfUnderTolerance(0.2f, INSTANT_VALUE); @@ -268,6 +336,7 @@ public static float GetMoveRate(Quaternion fromRotation, Quaternion toRotation, /// /// Moves transform to target values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) { if (!IsValid) @@ -276,10 +345,24 @@ public void Move(Transform movingTransform, TransformProperties goalProperties, Move(movingTransform, TransformPropertiesFlag.Everything, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); TimeRemaining -= delta; } + + /// + /// Moves transform to target values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(TransformAccess movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) + { + if (!IsValid) + return; + + Move(movingTransform, TransformPropertiesFlag.Everything, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); + TimeRemaining -= delta; + } /// /// Moves transform to target values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) { if (!IsValid) @@ -288,11 +371,24 @@ public void Move(Transform movingTransform, TransformProperties goalProperties, Move(movingTransform, movedProperties, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); TimeRemaining -= delta; } + + /// + /// Moves transform to target values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(TransformAccess movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) + { + if (!IsValid) + return; + + Move(movingTransform, movedProperties, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); + TimeRemaining -= delta; + } /// /// Moves transform to target values. /// - public static void Move(Transform movingTransform, TransformPropertiesFlag movedProperties, Vector3 posGoal, float posRate, Quaternion rotGoal, float rotRate, Vector3 scaleGoal, float scaleRate, float delta, bool useWorldSpace) + public static void Move(Transform movingTransform, TransformPropertiesFlag movedProperties, float3 posGoal, float posRate, quaternion rotGoal, float rotRate, float3 scaleGoal, float scaleRate, float delta, bool useWorldSpace) { using (_pm_Move.Auto()) { @@ -302,80 +398,117 @@ public static void Move(Transform movingTransform, TransformPropertiesFlag moved bool containsRotation = movedProperties.FastContains(TransformPropertiesFlag.Rotation); bool containsScale = movedProperties.FastContains(TransformPropertiesFlag.Scale); - //World space. + Vector3 pos; + Quaternion rot; if (useWorldSpace) + t.GetPositionAndRotation(out pos, out rot); + else t.GetLocalPositionAndRotation(out pos, out rot); + + if (containsPosition) + pos = MoveTowardsFast(pos, posGoal, posRate, delta); + + if (containsRotation) + rot = RotateTowardsFast(rot, rotGoal, rotRate, delta); + + if (containsPosition || containsRotation) + ApplyPosRot(t, useWorldSpace, pos, rot); + + if (containsScale) { - if (containsPosition) - { - if (posRate == INSTANT_VALUE) - { - t.position = posGoal; - } - else if (posRate == UNSET_VALUE) { } - else - { - t.position = Vector3.MoveTowards(t.position, posGoal, posRate * delta); - } - } - - if (containsRotation) - { - if (rotRate == INSTANT_VALUE) - { - t.rotation = rotGoal; - } - else if (rotRate == UNSET_VALUE) { } - else - { - t.rotation = Quaternion.RotateTowards(t.rotation, rotGoal, rotRate * delta); - } - } - } - //Local space. - else - { - if (containsPosition) - { - if (posRate == INSTANT_VALUE) - { - t.localPosition = posGoal; - } - else if (posRate == UNSET_VALUE) { } - else - { - t.localPosition = Vector3.MoveTowards(t.localPosition, posGoal, posRate * delta); - } - } - - if (containsRotation) - { - if (rotRate == INSTANT_VALUE) - { - t.localRotation = rotGoal; - } - else if (rotRate == UNSET_VALUE) { } - else - { - t.localRotation = Quaternion.RotateTowards(t.localRotation, rotGoal, rotRate * delta); - } - } + var scale = t.localScale; + t.localScale = MoveTowardsFast(scale, scaleGoal, scaleRate, delta); } + } + } + + /// + /// Moves transform to target values. + /// + public static void Move(TransformAccess movingTransform, TransformPropertiesFlag movedProperties, float3 posGoal, float posRate, quaternion rotGoal, float rotRate, float3 scaleGoal, float scaleRate, float delta, bool useWorldSpace) + { + using (_pm_Move.Auto()) + { + TransformAccess t = movingTransform; - //Scale always uses local. + bool containsPosition = movedProperties.FastContains(TransformPropertiesFlag.Position); + bool containsRotation = movedProperties.FastContains(TransformPropertiesFlag.Rotation); + bool containsScale = movedProperties.FastContains(TransformPropertiesFlag.Scale); + + Vector3 pos; + Quaternion rot; + if (useWorldSpace) + t.GetPositionAndRotation(out pos, out rot); + else t.GetCorrectLocalPositionAndRotation(out pos, out rot); + + if (containsPosition) + pos = MoveTowardsFast(pos, posGoal, posRate, delta); + + if (containsRotation) + rot = RotateTowardsFast(rot, rotGoal, rotRate, delta); + + if (containsPosition || containsRotation) + ApplyPosRot(t, useWorldSpace, pos, rot); + if (containsScale) { - if (scaleRate == INSTANT_VALUE) - { - t.localScale = scaleGoal; - } - else if (scaleRate == UNSET_VALUE) { } - else - { - t.localScale = Vector3.MoveTowards(t.localScale, scaleGoal, scaleRate * delta); - } + var scale = t.localScale; + t.localScale = MoveTowardsFast(scale, scaleGoal, scaleRate, delta); } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static float3 MoveTowardsFast(float3 current, float3 goal, float rate, float delta) + { + if (rate == INSTANT_VALUE) return goal; + if (rate == UNSET_VALUE) return current; + + float3 diff = goal - current; + float maxDelta = math.max(0f, rate * delta); + + float lenSq = math.lengthsq(diff); + if (lenSq <= maxDelta * maxDelta) return goal; + + float invLen = math.rsqrt(lenSq); // 1 / sqrt(lenSq) + float t = math.min(maxDelta * invLen, 1f); + return current + diff * t; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static quaternion RotateTowardsFast(quaternion current, quaternion goal, float rate, float delta) + { + if (rate == INSTANT_VALUE) return goal; + if (rate == UNSET_VALUE) return current; + + float maxDelta = math.max(0f, rate * delta); + + float dot = math.dot(current.value, goal.value); + float c = math.saturate(math.abs(dot)); // min(|dot|, 1) + + float angle = math.degrees(2f * math.acos(c)); + if (angle <= maxDelta) return goal; + + float t = math.min(1f, maxDelta / angle); + return math.slerp(current, goal, t); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ApplyPosRot(Transform t, bool worldSpace, float3 pos, quaternion rot) + { + if (worldSpace) + t.SetPositionAndRotation(pos, rot); + else + t.SetLocalPositionAndRotation(pos, rot); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ApplyPosRot(TransformAccess t, bool worldSpace, float3 pos, quaternion rot) + { + if (worldSpace) + t.SetPositionAndRotation(pos, rot); + else + t.SetLocalPositionAndRotation(pos, rot); + } } /// @@ -429,39 +562,60 @@ public class MoveRatesCls : IResettable /// /// Sets all rates to instant. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetInstantRates() => _moveRates.SetInstantRates(); /// /// Sets all rates to the same value. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float value) => _moveRates.Update(value); /// /// Updates values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale) => _moveRates.Update(position, rotation, scale); /// /// Updates values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale, float timeRemaining) => _moveRates.Update(position, rotation, scale, timeRemaining); /// /// Updaes values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(MoveRatesCls mr) => _moveRates.Update(mr.Position, mr.Rotation, mr.Scale); - + /// /// Moves transform to target values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, delta, useWorldSpace); /// /// Moves transform to target values. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(TransformAccess movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, delta, useWorldSpace); + + /// + /// Moves transform to target values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, movedProperties, delta, useWorldSpace); + + /// + /// Moves transform to target values. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Move(TransformAccess movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, movedProperties, delta, useWorldSpace); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetState() => _moveRates.ResetState(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void InitializeState() { } } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Object/TransformProperties.cs b/Assets/FishNet/Runtime/Object/TransformProperties.cs index 7ee1d7ca..34f26d0c 100644 --- a/Assets/FishNet/Runtime/Object/TransformProperties.cs +++ b/Assets/FishNet/Runtime/Object/TransformProperties.cs @@ -1,6 +1,8 @@ using System; using GameKit.Dependencies.Utilities; +using Unity.Mathematics; using UnityEngine; +using UnityEngine.Jobs; namespace FishNet.Object { @@ -13,17 +15,17 @@ public static class TransformPropertiesExtensions /// public static TransformProperties CreateDirections(this TransformProperties prevProperties, TransformProperties nextProperties, uint divisor = 1) { - Vector3 position = (nextProperties.Position - prevProperties.Position) / divisor; + float3 position = (nextProperties.Position - prevProperties.Position) / divisor; - Quaternion rotation = nextProperties.Rotation.Subtract(prevProperties.Rotation); + quaternion rotation = nextProperties.Rotation.Subtract(prevProperties.Rotation); //If more than 1 tick span then get a portion of the rotation. if (divisor > 1) { float percent = 1f / (float)divisor; - rotation = Quaternion.Lerp(Quaternion.identity, nextProperties.Rotation, percent); + rotation = math.nlerp(quaternion.identity, nextProperties.Rotation, percent); } - Vector3 scale = (nextProperties.Scale - prevProperties.Scale) / divisor; + float3 scale = (nextProperties.Scale - prevProperties.Scale) / divisor; return new(position, rotation, scale); } @@ -52,12 +54,12 @@ public static void SetWorldProperties(this TransformProperties tp, Transform t) [Serializable] public class TransformPropertiesCls : IResettable { - public Vector3 Position; - public Quaternion Rotation; - public Vector3 LocalScale; + public float3 Position; + public quaternion Rotation; + public float3 LocalScale; public TransformPropertiesCls() { } - public TransformPropertiesCls(Vector3 position, Quaternion rotation, Vector3 localScale) + public TransformPropertiesCls(float3 position, quaternion rotation, float3 localScale) { Position = position; Rotation = rotation; @@ -68,7 +70,7 @@ public void InitializeState() { } public void ResetState() { - Update(Vector3.zero, Quaternion.identity, Vector3.zero); + Update(float3.zero, quaternion.identity, float3.zero); } public void Update(Transform t) @@ -86,12 +88,12 @@ public void Update(TransformProperties tp) Update(tp.Position, tp.Rotation, tp.Scale); } - public void Update(Vector3 position, Quaternion rotation) + public void Update(float3 position, quaternion rotation) { Update(position, rotation, LocalScale); } - public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) + public void Update(float3 position, quaternion rotation, float3 localScale) { Position = position; Rotation = rotation; @@ -103,7 +105,7 @@ public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) /// public bool ValuesEquals(TransformPropertiesCls properties) { - return Position == properties.Position && Rotation == properties.Rotation && LocalScale == properties.LocalScale; + return Position.Equals(properties.Position) && Rotation.Equals(properties.Rotation) && LocalScale.Equals(properties.LocalScale); } /// @@ -120,29 +122,64 @@ public TransformProperties ToStruct() [Serializable] public struct TransformProperties { - public Vector3 Position; - public Quaternion Rotation; + public float3 Position; + public quaternion Rotation; [Obsolete("Use Scale.")] //Remove V5 - public Vector3 LocalScale => Scale; - public Vector3 Scale; + public float3 LocalScale => Scale; + public float3 Scale; + public byte IsValidByte; + /// /// Becomes true when values are set through update or constructor. /// - public bool IsValid; + public bool IsValid + { + get => IsValidByte != 0; + set => IsValidByte = (byte)(value ? 1 : 0); + } - public TransformProperties(Vector3 position, Quaternion rotation, Vector3 localScale) + public TransformProperties(float3 position, quaternion rotation, float3 localScale) { Position = position; Rotation = rotation; Scale = localScale; - IsValid = true; + IsValidByte = 1; } /// - /// Creates a TransformProperties with default position and rotation, with Vector3.one scale. + /// Creates a TransformProperties with default position and rotation, with float3.one scale. /// - public static TransformProperties GetTransformDefault() => new(Vector3.zero, Quaternion.identity, Vector3.one); + public static TransformProperties GetTransformDefault() => new(float3.zero, quaternion.identity, new float3(1f, 1f, 1f)); + public static TransformProperties GetOffsetDefault() => new(float3.zero, quaternion.identity, float3.zero); + public static TransformProperties operator +(TransformProperties a, TransformProperties b) + { + if (!a.IsValid) return b; + if (!b.IsValid) return a; + return new TransformProperties( + a.Position + b.Position, + math.mul(a.Rotation, b.Rotation), + a.Scale * b.Scale); + } + + public static TransformProperties operator -(TransformProperties a, TransformProperties b) + { + if (!a.IsValid) return -b; + if (!b.IsValid) return a; + return new TransformProperties( + a.Position - b.Position, + math.mul(a.Rotation, math.inverse(b.Rotation)), + a.Scale / b.Scale); + } + + public static TransformProperties operator -(TransformProperties a) + { + return new TransformProperties( + -a.Position, + math.inverse(a.Rotation), + 1f / a.Scale); + } + public override string ToString() { return $"Position: {Position.ToString()}, Rotation {Rotation.ToString()}, Scale {Scale.ToString()}"; @@ -155,13 +192,20 @@ public TransformProperties(Transform t) : this(t.position, t.rotation, t.localSc public void ResetState() { - Update(Vector3.zero, Quaternion.identity, Vector3.zero); + Update(float3.zero, quaternion.identity, float3.zero); IsValid = false; } public void Update(Transform t) { - Update(t.position, t.rotation, t.localScale); + t.GetPositionAndRotation(out var pos, out var rot); + Update(pos, rot, t.localScale); + } + + public void Update(TransformAccess t) + { + t.GetPositionAndRotation(out var pos, out var rot); + Update(pos, rot, t.localScale); } public void Update(TransformProperties tp) @@ -169,12 +213,12 @@ public void Update(TransformProperties tp) Update(tp.Position, tp.Rotation, tp.Scale); } - public void Update(Vector3 position, Quaternion rotation) + public void Update(float3 position, quaternion rotation) { Update(position, rotation, Scale); } - public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) + public void Update(float3 position, quaternion rotation, float3 localScale) { Position = position; Rotation = rotation; @@ -189,7 +233,7 @@ public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) public void Add(TransformProperties tp) { Position += tp.Position; - Rotation *= tp.Rotation; + Rotation = math.mul(Rotation, tp.Rotation); Scale += tp.Scale; } @@ -200,7 +244,7 @@ public void Add(TransformProperties tp) public void Subtract(TransformProperties tp) { Position -= tp.Position; - Rotation *= Quaternion.Inverse(tp.Rotation); + Rotation = math.mul(Rotation, math.inverse(tp.Rotation)); Scale -= tp.Scale; } @@ -209,7 +253,7 @@ public void Subtract(TransformProperties tp) /// public bool ValuesEquals(TransformProperties properties) { - return Position == properties.Position && Rotation == properties.Rotation && Scale == properties.Scale; + return Position.Equals(properties.Position) && Rotation.Equals(properties.Rotation) && Scale.Equals(properties.Scale); } } } \ No newline at end of file From f38fe3aef0f824cd24dcd25eb72478fb9fad0ac5 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 21:09:52 +0300 Subject: [PATCH 10/23] fix --- .../Component/TickSmoothing/TickSmoothingManager.Types.cs | 2 +- .../Component/TickSmoothing/TickSmoothingManager.cs | 7 ++++--- .../Dependencies/Utilities/Types/StripedRingQueue.cs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs index c73f0890..5b400d53 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs @@ -4,7 +4,7 @@ using FishNet.Object; using FishNet.Object.Prediction; using FishNet.Utility.Extension; -using GameKit.Dependencies.Utilities; +using GameKit.Dependencies.Utilities.Types; using Unity.Burst; using Unity.Collections; using Unity.Jobs; diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs index 92603ce9..4d21bd49 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -7,6 +7,7 @@ using FishNet.Transporting; using FishNet.Utility.Extension; using GameKit.Dependencies.Utilities; +using GameKit.Dependencies.Utilities.Types; using Unity.Collections; using Unity.Jobs; using Unity.Jobs.LowLevel.Unsafe; @@ -326,12 +327,12 @@ private void OnDestroy() } while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) - if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); for (int i = 0; i < _indexToSmoother.Count; i++) { Transform trackerTransform = _trackerTaa[i]; - if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); } if (_moveRates.IsCreated) _moveRates.Dispose(); @@ -795,7 +796,7 @@ private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs arg using (_pm_ClientManager_OnClientConnectionState.Auto()) { while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) - if (trackerTransform.gameObject) Destroy(trackerTransform.gameObject); + if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); if (args.ConnectionState == LocalConnectionState.Started) ChangeSubscriptions(true); diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs index 72b87937..eccd2387 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs @@ -5,7 +5,7 @@ using Unity.Mathematics; using UnityEngine; -namespace GameKit.Dependencies.Utilities +namespace GameKit.Dependencies.Utilities.Types { /// /// A striped ring-buffer that stores N independent queues addressed by i = 0..N-1. From 786c9a0e9491ebccfe9c7261e63fea3061dc1414 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 21:16:58 +0300 Subject: [PATCH 11/23] Update MovementSettingsDrawer.cs --- .../Component/TickSmoothing/Editor/MovementSettingsDrawer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs index d6bcf4df..71bca8c1 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs @@ -26,6 +26,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue"); SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue"); SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties"); + SerializedProperty useLocalSpace = property.FindPropertyRelative("UseLocalSpace"); SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties"); _propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport"); @@ -37,6 +38,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten _propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1); _propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties"); + _propertyDrawer.DrawProperty(useLocalSpace, "Use Local Space"); if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything) _propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1); From 6916a28231dd6acfd5277e7c62b3e8a65d767441 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 17 Dec 2025 22:39:48 +0300 Subject: [PATCH 12/23] Update NetworkTransform.cs --- .../NetworkTransform/NetworkTransform.cs | 35 +------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs b/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs index 1f2de518..46338147 100644 --- a/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs +++ b/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs @@ -1756,27 +1756,6 @@ private void MoveToTarget(float delta) //No more in buffer, see if can extrapolate. else { - //PROSTART - //Can extrapolate. - if (td.ExtrapolationState == TransformData.ExtrapolateState.Available) - { - rd.TimeRemaining = (float)(_extrapolation * _timeManager.TickDelta); - td.ExtrapolationState = TransformData.ExtrapolateState.Active; - if (leftOver > 0f) - MoveToTarget(leftOver); - } - //Ran out of extrapolate. - else if (td.ExtrapolationState == TransformData.ExtrapolateState.Active) - { - rd.TimeRemaining = (float)(_extrapolation * _timeManager.TickDelta); - td.ExtrapolationState = TransformData.ExtrapolateState.Disabled; - if (leftOver > 0f) - MoveToTarget(leftOver); - } - //Extrapolation has ended or was never enabled. - else - { - //PROEND /* If everything matches up then end queue. * Otherwise let it play out until stuff * aligns. Generally the time remaining is enough @@ -1785,9 +1764,7 @@ private void MoveToTarget(float delta) if (!HasChanged(td)) _currentGoalData = null; OnInterpolationComplete?.Invoke(); - //PROSTART - } - //PROEND + } } } @@ -2321,16 +2298,6 @@ private void SetExtrapolatedData(TransformData prev, TransformData next, Channel { //Default value. next.ExtrapolationState = TransformData.ExtrapolateState.Disabled; - - //PROSTART - //Teleports cannot extrapolate. - if (_extrapolation == 0 || !_synchronizePosition || channel == Channel.Reliable || next.Position == prev.Position) - return; - - Vector3 offet = (next.Position - prev.Position) * _extrapolation; - next.ExtrapolatedPosition = next.Position + offet; - next.ExtrapolationState = TransformData.ExtrapolateState.Available; - //PROEND } /// From 4ce08dceb83131bcc6fc08add0e2267151fb27bb Mon Sep 17 00:00:00 2001 From: waterb <143908720+belplaton@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:41:05 +0300 Subject: [PATCH 13/23] Delete Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs.bak --- .../NetworkAnimator/NetworkAnimator.cs.bak | 1621 ----------------- 1 file changed, 1621 deletions(-) delete mode 100644 Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs.bak diff --git a/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs.bak b/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs.bak deleted file mode 100644 index 822d0ccd..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/NetworkAnimator/NetworkAnimator.cs.bak +++ /dev/null @@ -1,1621 +0,0 @@ -#if UNITY_EDITOR || DEVELOPMENT_BUILD -#define DEVELOPMENT -#endif -using FishNet.Component.Transforming; -using FishNet.Connection; -using FishNet.Documenting; -using FishNet.Managing.Logging; -using FishNet.Managing.Server; -using FishNet.Object; -using FishNet.Serializing; -using FishNet.Utility; -using FishNet.Utility.Performance; -using GameKit.Dependencies.Utilities; -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using FishNet.Managing; -using UnityEngine; -using UnityEngine.Profiling; -using Unity.Profiling; -using TimeManagerCls = FishNet.Managing.Timing.TimeManager; - -namespace FishNet.Component.Animating -{ - [AddComponentMenu("FishNet/Component/NetworkAnimator")] - public sealed class NetworkAnimator : NetworkBehaviour - { - #region Types. - /// - /// Data received from the server. - /// - private struct ReceivedServerData - { - /// - /// Gets an Arraysegment of received data. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArraySegment GetArraySegment() => new(_data, 0, _length); - - /// - /// How much data written. - /// - private int _length; - /// - /// Buffer which contains data. - /// - private byte[] _data; - - public ReceivedServerData(ArraySegment segment) - { - _length = segment.Count; - _data = ByteArrayPool.Retrieve(_length); - Buffer.BlockCopy(segment.Array, segment.Offset, _data, 0, _length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - if (_data != null) - ByteArrayPool.Store(_data); - } - } - - private struct StateChange - { - /// - /// Frame which the state was changed. - /// - public int FrameCount; - /// - /// True if a crossfade. - /// - public bool IsCrossfade; - /// - /// Hash to crossfade into. - /// - public int Hash; - /// - /// True if using FixedTime. - /// - public bool FixedTime; - /// - /// Duration of crossfade. - /// - public float DurationTime; - /// - /// Offset time of crossfade. - /// - public float OffsetTime; - /// - /// Normalized transition time of crossfade. - /// - public float NormalizedTransitionTime; - - public StateChange(int frame) - { - FrameCount = frame; - IsCrossfade = default; - Hash = default; - FixedTime = default; - DurationTime = default; - OffsetTime = default; - NormalizedTransitionTime = default; - } - - public StateChange(int frame, int hash, bool fixedTime, float duration, float offset, float normalizedTransition) - { - FrameCount = frame; - IsCrossfade = true; - Hash = hash; - FixedTime = fixedTime; - DurationTime = duration; - OffsetTime = offset; - NormalizedTransitionTime = normalizedTransition; - } - } - - /// - /// Animator updates received from clients when using Client Authoritative. - /// - private class ClientAuthoritativeUpdate - { - /// - /// - public ClientAuthoritativeUpdate() - { - // Start buffers off at 8 bytes nad grow them as needed. - for (int i = 0; i < MAXIMUM_BUFFER_COUNT; i++) - _buffers.Add(new byte[MAXIMUM_DATA_SIZE]); - - _bufferLengths = new int[MAXIMUM_BUFFER_COUNT]; - } - - #region Public. - /// - /// True to force all animator data and ignore buffers. - /// - public bool ForceAll { get; private set; } - /// - /// Number of entries in Buffers. - /// - public int BufferCount = 0; - #endregion - - #region Private. - /// - /// Length of buffers. - /// - private int[] _bufferLengths; - /// - /// Buffers. - /// - private List _buffers = new(); - #endregion - - #region Const. - /// - /// Maximum size data may be. - /// - private const int MAXIMUM_DATA_SIZE = 1000; - /// - /// Maximum number of allowed buffers. - /// - public const int MAXIMUM_BUFFER_COUNT = 2; - #endregion - - public void AddToBuffer(ref ArraySegment data) - { - int dataCount = data.Count; - /* Data will never get this large, it's quite impossible. - * Just ignore the data if it does, client is likely performing - * an attack. */ - if (dataCount > MAXIMUM_DATA_SIZE) - return; - - // If index exceeds buffer count. - if (BufferCount >= MAXIMUM_BUFFER_COUNT) - { - ForceAll = true; - return; - } - - /* If here, can write to buffer. */ - byte[] buffer = _buffers[BufferCount]; - Buffer.BlockCopy(data.Array, data.Offset, buffer, 0, dataCount); - _bufferLengths[BufferCount] = dataCount; - BufferCount++; - } - - /// - /// Sets referenced data to buffer and it's length for index. - /// - /// - /// - /// - public void GetBuffer(int index, ref byte[] buffer, ref int length) - { - if (index > _buffers.Count) - { - NetworkManagerExtensions.LogWarning("Index exceeds Buffers count."); - return; - } - if (index > _bufferLengths.Length) - { - NetworkManagerExtensions.LogWarning("Index exceeds BufferLengths count."); - return; - } - - buffer = _buffers[index]; - length = _bufferLengths[index]; - } - - /// - /// Resets buffers. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Reset() - { - BufferCount = 0; - ForceAll = false; - } - } - - /// - /// Information on how to smooth to a float value. - /// - private struct SmoothedFloat - { - public SmoothedFloat(float rate, float target) - { - Rate = rate; - Target = target; - } - - public readonly float Rate; - public readonly float Target; - } - - /// - /// Details about a trigger update. - /// - private struct TriggerUpdate - { - public byte ParameterIndex; - public bool Setting; - - public TriggerUpdate(byte parameterIndex, bool setting) - { - ParameterIndex = parameterIndex; - Setting = setting; - } - } - - /// - /// Details about an animator parameter. - /// - private class ParameterDetail - { - /// - /// Parameter information. - /// - public readonly AnimatorControllerParameter ControllerParameter = null; - /// - /// Index within the types collection for this parameters value. The exception is with triggers; if the parameter type is a trigger then a value of 1 is set, 0 is unset. - /// - public readonly byte TypeIndex = 0; - /// - /// Hash for the animator string. - /// - public readonly int Hash; - - public ParameterDetail(AnimatorControllerParameter controllerParameter, byte typeIndex) - { - ControllerParameter = controllerParameter; - TypeIndex = typeIndex; - Hash = controllerParameter.nameHash; - } - } - #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. - /// - [SerializeField] - [HideInInspector] - internal List IgnoredParameters = new(); - #endregion - - #region Serialized. - /// - /// The animator component to synchronize. - /// - [Tooltip("The animator component to synchronize.")] - [SerializeField] - private Animator _animator; - /// - /// The animator component to synchronize. - /// - public Animator Animator - { - get { return _animator; } - } - /// - /// True to synchronize changes even when the animator component is disabled. - /// - [Tooltip("True to synchronize changes even when the animator component is disabled.")] - [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.")] - [SerializeField] - private bool _smoothFloats = true; - /// - /// How many ticks to interpolate. - /// - [Tooltip("How many ticks to interpolate.")] - [Range(1, NetworkTransform.MAX_INTERPOLATION)] - [SerializeField] - private ushort _interpolation = 2; - /// - /// - [Tooltip("True if using client authoritative animations.")] - [SerializeField] - private bool _clientAuthoritative = true; - /// - /// True if using client authoritative animations. - /// - public bool ClientAuthoritative - { - get { return _clientAuthoritative; } - } - /// - /// 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. - /// - [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. - /// - /// All parameter values, excluding triggers. - /// - private readonly List _parameterDetails = new(); - /// - /// Last int values. - /// - private readonly List _ints = new(); - /// - /// Last float values. - /// - private readonly List _floats = new(); - /// - /// Last bool values. - /// - private readonly List _bools = new(); - /// - /// Last layer weights. - /// - private float[] _layerWeights; - /// - /// Last speed. - /// - private float _speed; - /// - /// Trigger values set by using SetTrigger and ResetTrigger. - /// - private readonly List _triggerUpdates = new(); - // /// - // /// Updates going to clients. - // /// - // 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; - - if (_animator.enabled || _synchronizeWhenDisabled) - return true; - - return false; - } - } - /// - /// True if the animator is valid but not enabled. - /// - private bool _isAnimatorSet - { - get - { - bool failedChecks = _animator == null || _animator.runtimeAnimatorController == null; - return !failedChecks; - } - } - /// - /// Float valeus to smooth towards. - /// - private Dictionary _smoothedFloats = new(); - /// - /// Returns if floats can be smoothed for this client. - /// - private bool _canSmoothFloats - { - get - { - // Don't smooth on server only. - if (!IsClientStarted) - return false; - // Smoothing is disabled. - if (!_smoothFloats) - return false; - // No reason to smooth for self. - if (IsOwner && ClientAuthoritative) - return false; - - //Fall through. - return true; - } - } - /// - /// Layers which need to have their state synchronized. Key is the layer, Value is the state change information. - /// - private Dictionary _unsynchronizedLayerStates = new(); - /// - /// Last animator set. - /// - private Animator _lastAnimator; - /// - /// Last Controller set. - /// - private RuntimeAnimatorController _lastController; - /// - /// PooledWriter for this animator. - /// - private PooledWriter _writer = new(); - /// - /// Holds client authoritative updates received to send to other clients. - /// - private ClientAuthoritativeUpdate _clientAuthoritativeUpdates; - /// - /// True to forceAll next timed send. - /// - private bool _forceAllOnTimed; - /// - /// Animations received which should be applied. - /// - private Queue _fromServerBuffer = new(); - /// - /// Tick when the buffer may begin to run. - /// - private uint _startTick = TimeManagerCls.UNSET_TICK; - /// - /// True if subscribed to TimeManager for ticks. - /// - private bool _subscribedToTicks; - #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. - ///// - //private const float INTERPOLATION = 0.02f; - /// - /// ParameterDetails index which indicates a layer weight change. - /// - private const byte LAYER_WEIGHT = 240; - /// - /// ParameterDetails index which indicates an animator speed change. - /// - private const byte SPEED = 241; - /// - /// ParameterDetails index which indicates a layer state change. - /// - private const byte STATE = 242; - /// - /// ParameterDetails index which indicates a crossfade change. - /// - private const byte CROSSFADE = 243; - #endregion - - private void Awake() - { - InitializeOnce(); - } - - private void OnDestroy() - { - ChangeTickSubscription(false); - } - - [APIExclude] - public override void OnSpawnServer(NetworkConnection connection) - { - if (!_canSynchronizeAnimator) - return; - if (AnimatorUpdated(out ArraySegment updatedBytes, true)) - TargetAnimatorUpdated(connection, updatedBytes); - } - - public override void OnStartNetwork() - { - ChangeTickSubscription(true); - _isSynchronizationEnabled = true; - } - - [APIExclude] - public override void OnStartServer() - { - //If using client authoritative then initialize clientAuthoritativeUpdates. - if (_clientAuthoritative) - { - _clientAuthoritativeUpdates = new(); - // //Expand to clients buffer count to however many buffers can be held. - // for (int i = 0; i < ClientAuthoritativeUpdate.MAXIMUM_BUFFER_COUNT; i++) - // _toClientsBuffer.Add(new byte[0]); - } - // else - // { - // _toClientsBuffer.Add(new byte[0]); - // } - } - - public override void OnStartClient() - { - TimeManager.OnUpdate += TimeManager_OnUpdate; - } - - public override void OnStopClient() - { - if (TimeManager != null) - TimeManager.OnUpdate -= TimeManager_OnUpdate; - } - - public override void OnStopNetwork() - { - _unsynchronizedLayerStates.Clear(); - ChangeTickSubscription(false); - } - - /// - /// Tries to subscribe to TimeManager ticks. - /// - private void ChangeTickSubscription(bool subscribe) - { - if (subscribe == _subscribedToTicks || NetworkManager == null) - return; - - _subscribedToTicks = subscribe; - if (subscribe) - { - NetworkManager.TimeManager.OnPreTick += TimeManager_OnPreTick; - NetworkManager.TimeManager.OnPostTick += TimeManager_OnPostTick; - } - else - { - NetworkManager.TimeManager.OnPreTick -= TimeManager_OnPreTick; - NetworkManager.TimeManager.OnPostTick -= TimeManager_OnPostTick; - } - } - - /// - /// Called right before a tick occurs, as well before data is read. - /// - private void TimeManager_OnPreTick() - { - using (_pm_OnPreTick.Auto()) - { - if (!_canSynchronizeAnimator) - { - _fromServerBuffer.Clear(); - return; - } - - //Disabled/cannot start. - if (_startTick == 0) - return; - //Nothing in queue. - if (_fromServerBuffer.Count == 0) - { - _startTick = 0; - return; - } - - //Not enough time has passed to start queue. - if (TimeManager.LocalTick < _startTick) - return; - - ReceivedServerData rd = _fromServerBuffer.Dequeue(); - ArraySegment segment = rd.GetArraySegment(); - ApplyParametersUpdated(ref segment); - rd.Dispose(); - } - } - - /* Use post tick values are checked after - * client has an opportunity to use OnTick. */ - /// - /// Called after a tick occurs; physics would have simulated if using PhysicsMode.TimeManager. - /// - private void TimeManager_OnPostTick() - { - using (_pm_OnPostTick.Auto()) - { - //One check rather than per each method. - if (!_canSynchronizeAnimator) - return; - - CheckSendToServer(); - CheckSendToClients(); - } - } - - private void TimeManager_OnUpdate() - { - using (_pm_OnUpdate.Auto()) - { - if (!_canSynchronizeAnimator) - return; - - if (IsClientStarted) - SmoothFloats(); - } - } - - /// - /// Initializes this script for use. - /// - private void InitializeOnce() - { - if (_animator == null) - _animator = GetComponent(); - - //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."); - return; - } - - //Speed. - _speed = _animator.speed; - - //Build layer weights. - _layerWeights = new float[_animator.layerCount]; - for (int i = 0; i < _layerWeights.Length; i++) - _layerWeights[i] = _animator.GetLayerWeight(i); - - _parameterDetails.Clear(); - _bools.Clear(); - _floats.Clear(); - _ints.Clear(); - //Create a parameter detail for each parameter that can be synchronized. - foreach (AnimatorControllerParameter item in _animator.parameters) - { - bool process = !_animator.IsParameterControlledByCurve(item.name); - //PROSTART - - //PROEND - if (process) - { - //Over 250 parameters; who would do this!? - if (_parameterDetails.Count == 240) - { - NetworkManager.LogError($"Parameter {item.name} exceeds the allowed 240 parameter count and is being ignored."); - continue; - } - - int typeIndex = 0; - //Bools. - if (item.type == AnimatorControllerParameterType.Bool) - { - typeIndex = _bools.Count; - _bools.Add(_animator.GetBool(item.nameHash)); - } - //Floats. - else if (item.type == AnimatorControllerParameterType.Float) - { - typeIndex = _floats.Count; - _floats.Add(_animator.GetFloat(item.name)); - } - //Ints. - else if (item.type == AnimatorControllerParameterType.Int) - { - typeIndex = _ints.Count; - _ints.Add(_animator.GetInteger(item.nameHash)); - } - //Triggers. - else if (item.type == AnimatorControllerParameterType.Trigger) - { - /* Triggers aren't persistent so they don't use stored values - * but I do need to make a parameter detail to track the hash. */ - typeIndex = -1; - } - - _parameterDetails.Add(new(item, (byte)typeIndex)); - } - } - } - - /// - /// 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. - /// - /// - public void SetAnimator(Animator animator) - { - //No update required. - if (animator == _lastAnimator) - return; - - _animator = animator; - InitializeOnce(); - _lastAnimator = animator; - } - - /// - /// Sets which controller to use. You must call this with the appropriate controller on all clients and server. This change is not automatically synchronized. - /// - /// - public void SetController(RuntimeAnimatorController controller) - { - //No update required. - if (controller == _lastController) - return; - - _animator.runtimeAnimatorController = controller; - InitializeOnce(); - _lastController = controller; - } - - /// - /// Checks to send animator data from server to clients. - /// - private void CheckSendToServer() - { - using (_pm_CheckSendToServer.Auto()) - { - //Cannot send to server if is server or not client. - if (IsServerStarted || !IsClientInitialized) - return; - //Cannot send to server if not client authoritative or don't have authority. - if (!ClientAuthoritative || !IsOwner) - return; - - /* If there are updated parameters to send. - * Don't really need to worry about mtu here - * because there's no way the sent bytes are - * ever going to come close to the mtu - * when sending a single update. */ - if (AnimatorUpdated(out ArraySegment updatedBytes, _forceAllOnTimed)) - ServerAnimatorUpdated(updatedBytes); - - _forceAllOnTimed = false; - } - } - - /// - /// Checks to send animator data from server to clients. - /// - private void CheckSendToClients() - { - using (_pm_CheckSendToClients.Auto()) - { - //Cannot send to clients if not server initialized. - if (!IsServerInitialized) - return; - - bool sendFromServer; - //If client authoritative. - if (ClientAuthoritative) - { - //If has no owner then use latest values on server. - if (!Owner.IsValid) - { - sendFromServer = true; - } - //If has a owner. - else - { - //If is owner then send latest values on server. - if (IsOwner) - { - sendFromServer = true; - } - //Not owner. - else - { - //Haven't received any data from clients, cannot send yet. - if (_clientAuthoritativeUpdates.BufferCount == 0) - { - return; - } - //Data was received from client; check eligibility to send it. - else - { - /* If forceAll is true then the latest values on - * server must be used, rather than what was received - * from client. This can occur if the client is possibly - * trying to use an attack or if the client is - * excessively sending updates. To prevent relaying that - * same data to others the server will send it's current - * animator settings in this scenario. */ - if (_clientAuthoritativeUpdates.ForceAll) - { - sendFromServer = true; - _clientAuthoritativeUpdates.Reset(); - } - else - { - sendFromServer = false; - } - } - } - } - } - //Not client authoritative, always send from server. - else - { - sendFromServer = true; - } - - /* If client authoritative then use what was received from clients - * if data exist. */ - if (!sendFromServer) - { - byte[] buffer = null; - int bufferLength = 0; - for (int i = 0; i < _clientAuthoritativeUpdates.BufferCount; i++) - { - _clientAuthoritativeUpdates.GetBuffer(i, ref buffer, ref bufferLength); - - //If null was returned then something went wrong. - if (buffer == null || bufferLength == 0) - continue; - - SendSegment(new(buffer, 0, bufferLength)); - } - - //Reset client auth buffer. - _clientAuthoritativeUpdates.Reset(); - } - //Sending from server, send what's changed. - else - { - if (AnimatorUpdated(out ArraySegment updatedBytes, _forceAllOnTimed)) - SendSegment(updatedBytes); - - _forceAllOnTimed = false; - } - - //Sends segment to clients - void SendSegment(ArraySegment data) - { - foreach (NetworkConnection nc in Observers) - { - //If to not send to owner. - if (!_sendToOwner && nc == Owner) - continue; - TargetAnimatorUpdated(nc, data); - } - } - } - } - - /// - /// Smooths floats on clients. - /// - private void SmoothFloats() - { - using (_pm_SmoothFloats.Auto()) - { - //Don't need to smooth on authoritative client. - if (!_canSmoothFloats) - return; - //Nothing to smooth. - if (_smoothedFloats.Count == 0) - return; - - float deltaTime = Time.deltaTime; - - List finishedEntries = new(); - - /* Cycle through each target float and move towards it. - * Once at a target float mark it to be removed from floatTargets. */ - foreach (KeyValuePair item in _smoothedFloats) - { - float current = _animator.GetFloat(item.Key); - float next = Mathf.MoveTowards(current, item.Value.Target, item.Value.Rate * deltaTime); - _animator.SetFloat(item.Key, next); - - if (next == item.Value.Target) - finishedEntries.Add(item.Key); - } - - //Remove finished entries from dictionary. - for (int i = 0; i < finishedEntries.Count; i++) - _smoothedFloats.Remove(finishedEntries[i]); - } - } - - /// - /// Returns if animator is updated and bytes of updated values. - /// - /// - private bool AnimatorUpdated(out ArraySegment updatedBytes, bool forceAll = false) - { - using (_pm_AnimatorUpdated.Auto()) - { - updatedBytes = default; - //Something isn't setup right. - if (_layerWeights == null) - return false; - //Reset the writer. - _writer.Clear(); - - /* Every time a parameter is updated a byte is added - * for it's index, this is why requiredBytes increases - * by 1 when a value updates. ChangedParameter contains - * the index updated and the new value. The requiresBytes - * is increased also by however many bytes are required - * for the type which has changed. Some types use special parameter - * detail indexes, such as layer weights; these can be found under const. */ - for (byte parameterIndex = 0; parameterIndex < _parameterDetails.Count; parameterIndex++) - { - ParameterDetail pd = _parameterDetails[parameterIndex]; - /* Bool. */ - if (pd.ControllerParameter.type == AnimatorControllerParameterType.Bool) - { - bool next = _animator.GetBool(pd.Hash); - //If changed. - if (forceAll || _bools[pd.TypeIndex] != next) - { - _writer.WriteUInt8Unpacked(parameterIndex); - _writer.WriteBoolean(next); - _bools[pd.TypeIndex] = next; - } - } - /* Float. */ - else if (pd.ControllerParameter.type == AnimatorControllerParameterType.Float) - { - float next = _animator.GetFloat(pd.Hash); - //If changed. - if (forceAll || _floats[pd.TypeIndex] != next) - { - _writer.WriteUInt8Unpacked(parameterIndex); - _writer.WriteSingle(next); - _floats[pd.TypeIndex] = next; - } - } - /* Int. */ - else if (pd.ControllerParameter.type == AnimatorControllerParameterType.Int) - { - int next = _animator.GetInteger(pd.Hash); - //If changed. - if (forceAll || _ints[pd.TypeIndex] != next) - { - _writer.WriteUInt8Unpacked(parameterIndex); - _writer.WriteInt32(next); - _ints[pd.TypeIndex] = next; - } - } - } - - /* Don't need to force trigger sends since - * they're one-shots. */ - for (int i = 0; i < _triggerUpdates.Count; i++) - { - _writer.WriteUInt8Unpacked(_triggerUpdates[i].ParameterIndex); - _writer.WriteBoolean(_triggerUpdates[i].Setting); - } - - _triggerUpdates.Clear(); - - /* States. */ - if (forceAll) - { - //Add all layers to layer states. - for (int i = 0; i < _animator.layerCount; i++) - _unsynchronizedLayerStates[i] = new(Time.frameCount); - } - - /* Only iterate if the collection has values. This is to avoid some - * unnecessary caching when collection is empty. */ - if (_unsynchronizedLayerStates.Count > 0) - { - int frameCount = Time.frameCount; - List sentLayers = CollectionCaches.RetrieveList(); - //Go through each layer which needs to be synchronized. - foreach (KeyValuePair item in _unsynchronizedLayerStates) - { - /* If a frame has not passed since the state was created - * then do not send it until next tick. State changes take 1 frame - * to be processed by Unity, this check ensures that. */ - if (frameCount == item.Value.FrameCount) - continue; - - //Add to layers being sent. This is so they can be removed from the collection later. - sentLayers.Add(item.Key); - int layerIndex = item.Key; - StateChange sc = item.Value; - //If a regular state change. - if (!sc.IsCrossfade) - { - if (ReturnCurrentLayerState(out int stateHash, out float normalizedTime, layerIndex)) - { - _writer.WriteUInt8Unpacked(STATE); - _writer.WriteUInt8Unpacked((byte)layerIndex); - //Current hash will always be too large to compress. - _writer.WriteInt32Unpacked(stateHash); - _writer.WriteSingle(normalizedTime); - } - } - //When it's a crossfade then send crossfade data. - else - { - _writer.WriteUInt8Unpacked(CROSSFADE); - _writer.WriteUInt8Unpacked((byte)layerIndex); - //Current hash will always be too large to compress. - _writer.WriteInt32(sc.Hash); - _writer.WriteBoolean(sc.FixedTime); - //Times usually can be compressed. - _writer.WriteSingle(sc.DurationTime); - _writer.WriteSingle(sc.OffsetTime); - _writer.WriteSingle(sc.NormalizedTransitionTime); - } - } - - if (sentLayers.Count > 0) - { - for (int i = 0; i < sentLayers.Count; i++) - _unsynchronizedLayerStates.Remove(sentLayers[i]); - //Store cache. - CollectionCaches.Store(sentLayers); - } - } - - /* Layer weights. */ - for (int layerIndex = 0; layerIndex < _layerWeights.Length; layerIndex++) - { - float next = _animator.GetLayerWeight(layerIndex); - if (forceAll || _layerWeights[layerIndex] != next) - { - _writer.WriteUInt8Unpacked(LAYER_WEIGHT); - _writer.WriteUInt8Unpacked((byte)layerIndex); - _writer.WriteSingle(next); - _layerWeights[layerIndex] = next; - } - } - - /* Speed is similar to layer weights but we don't need the index, - * only the indicator and value. */ - float speedNext = _animator.speed; - if (forceAll || _speed != speedNext) - { - _writer.WriteUInt8Unpacked(SPEED); - _writer.WriteSingle(speedNext); - _speed = speedNext; - } - - //Nothing to update. - if (_writer.Position == 0) - return false; - - updatedBytes = _writer.GetArraySegment(); - return true; - } - } - - /// - /// Applies changed parameters to the animator. - /// - /// - private void ApplyParametersUpdated(ref ArraySegment updatedParameters) - { - using (_pm_ApplyParametersUpdated.Auto()) - { - if (!_canSynchronizeAnimator) - return; - if (_layerWeights == null) - return; - if (updatedParameters.Count == 0) - return; - - PooledReader reader = ReaderPool.Retrieve(updatedParameters, NetworkManager); - - try - { - while (reader.Remaining > 0) - { - byte parameterIndex = reader.ReadUInt8Unpacked(); - //Layer weight - if (parameterIndex == LAYER_WEIGHT) - { - byte layerIndex = reader.ReadUInt8Unpacked(); - float value = reader.ReadSingle(); - _animator.SetLayerWeight((int)layerIndex, value); - } - //Speed. - else if (parameterIndex == SPEED) - { - float value = reader.ReadSingle(); - _animator.speed = value; - } - //State. - else if (parameterIndex == STATE) - { - byte layerIndex = reader.ReadUInt8Unpacked(); - //Hashes will always be too large to compress. - int hash = reader.ReadInt32Unpacked(); - float normalizedTime = reader.ReadSingle(); - //Play results. - _animator.Play(hash, layerIndex, normalizedTime); - } - //Crossfade. - else if (parameterIndex == CROSSFADE) - { - byte layerIndex = reader.ReadUInt8Unpacked(); - //Hashes will always be too large to compress. - int hash = reader.ReadInt32(); - bool useFixedTime = reader.ReadBoolean(); - //Get time values. - float durationTime = reader.ReadSingle(); - float offsetTime = reader.ReadSingle(); - float normalizedTransitionTime = reader.ReadSingle(); - //If using fixed. - if (useFixedTime) - _animator.CrossFadeInFixedTime(hash, durationTime, layerIndex, offsetTime, normalizedTransitionTime); - else - _animator.CrossFade(hash, durationTime, layerIndex, offsetTime, normalizedTransitionTime); - } - //Not a predetermined index, is an actual parameter. - else - { - AnimatorControllerParameterType acpt = _parameterDetails[parameterIndex].ControllerParameter.type; - if (acpt == AnimatorControllerParameterType.Bool) - { - bool value = reader.ReadBoolean(); - _animator.SetBool(_parameterDetails[parameterIndex].Hash, value); - } - //Float. - else if (acpt == AnimatorControllerParameterType.Float) - { - float value = reader.ReadSingle(); - //If able to smooth floats. - if (_canSmoothFloats) - { - float currentValue = _animator.GetFloat(_parameterDetails[parameterIndex].Hash); - float past = (float)TimeManager.TickDelta; - //float past = _synchronizeInterval + INTERPOLATION; - float rate = Mathf.Abs(currentValue - value) / past; - _smoothedFloats[_parameterDetails[parameterIndex].Hash] = new(rate, value); - } - else - { - _animator.SetFloat(_parameterDetails[parameterIndex].Hash, value); - } - } - //Integer. - else if (acpt == AnimatorControllerParameterType.Int) - { - int value = reader.ReadInt32(); - _animator.SetInteger(_parameterDetails[parameterIndex].Hash, value); - } - //Trigger. - else if (acpt == AnimatorControllerParameterType.Trigger) - { - bool value = reader.ReadBoolean(); - if (value) - _animator.SetTrigger(_parameterDetails[parameterIndex].Hash); - else - _animator.ResetTrigger(_parameterDetails[parameterIndex].Hash); - } - //Unhandled. - else - { - NetworkManager.LogWarning($"Unhandled parameter type of {acpt}."); - } - } - } - } - catch - { - NetworkManager.LogWarning("An error occurred while applying updates. This may occur when malformed data is sent or when you change the animator or controller but not on all connections."); - } - finally - { - reader?.Store(); - } - } - } - - /// - /// Outputs the current state and time for a layer. Returns true if stateHash is not 0. - /// - /// - /// - /// - /// - /// - private bool ReturnCurrentLayerState(out int stateHash, out float normalizedTime, int layerIndex) - { - stateHash = 0; - normalizedTime = 0f; - - if (!_canSynchronizeAnimator) - return false; - - AnimatorStateInfo st = _animator.GetCurrentAnimatorStateInfo(layerIndex); - stateHash = st.fullPathHash; - normalizedTime = st.normalizedTime; - - return stateHash != 0; - } - - /// - /// Immediately sends all variables and states of layers. - /// This is a very bandwidth intensive operation. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SendAll() - { - _forceAllOnTimed = true; - } - - #region Play. - /// - /// Plays a state. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Play(string name) - { - Play(Animator.StringToHash(name)); - } - - /// - /// Plays a state. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Play(int hash) - { - for (int i = 0; i < _animator.layerCount; i++) - Play(hash, i, 0f); - } - - /// - /// Plays a state. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Play(string name, int layer) - { - Play(Animator.StringToHash(name), layer); - } - - /// - /// Plays a state. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Play(int hash, int layer) - { - Play(hash, layer, 0f); - } - - /// - /// Plays a state. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Play(string name, int layer, float normalizedTime) - { - Play(Animator.StringToHash(name), layer, normalizedTime); - } - - /// - /// Plays a state. - /// - public void Play(int hash, int layer, float normalizedTime) - { - if (!_canSynchronizeAnimator) - return; - if (_animator.HasState(layer, hash) || hash == 0) - { - _animator.Play(hash, layer, normalizedTime); - _unsynchronizedLayerStates[layer] = new(Time.frameCount); - } - } - - /// - /// Plays a state. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PlayInFixedTime(string name, float fixedTime) - { - PlayInFixedTime(Animator.StringToHash(name), fixedTime); - } - - /// - /// Plays a state. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PlayInFixedTime(int hash, float fixedTime) - { - for (int i = 0; i < _animator.layerCount; i++) - PlayInFixedTime(hash, i, fixedTime); - } - - /// - /// Plays a state. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PlayInFixedTime(string name, int layer, float fixedTime) - { - PlayInFixedTime(Animator.StringToHash(name), layer, fixedTime); - } - - /// - /// Plays a state. - /// - public void PlayInFixedTime(int hash, int layer, float fixedTime) - { - if (!_canSynchronizeAnimator) - return; - if (_animator.HasState(layer, hash) || hash == 0) - { - _animator.PlayInFixedTime(hash, layer, fixedTime); - _unsynchronizedLayerStates[layer] = new(Time.frameCount); - } - } - #endregion - - #region Crossfade. - /// - /// Creates a crossfade from the current state to any other state using normalized times. - /// - /// - /// - /// - /// - /// - [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); - } - - /// - /// Creates a crossfade from the current state to any other state using normalized times. - /// - /// - /// - /// - /// - /// - public void CrossFade(int hash, float normalizedTransitionDuration, int layer, float normalizedTimeOffset = 0.0f, float normalizedTransitionTime = 0.0f) - { - if (!_canSynchronizeAnimator) - return; - if (_animator.HasState(layer, hash) || hash == 0) - { - _animator.CrossFade(hash, normalizedTransitionDuration, layer, normalizedTimeOffset, normalizedTransitionTime); - _unsynchronizedLayerStates[layer] = new(Time.frameCount, hash, false, normalizedTransitionDuration, normalizedTimeOffset, normalizedTransitionTime); - } - } - - /// - /// Creates a crossfade from the current state to any other state using times in seconds. - /// - /// - /// - /// - /// - /// - [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); - } - - /// - /// Creates a crossfade from the current state to any other state using times in seconds. - /// - /// - /// - /// - /// - /// - public void CrossFadeInFixedTime(int hash, float fixedTransitionDuration, int layer, float fixedTimeOffset = 0.0f, float normalizedTransitionTime = 0.0f) - { - if (!_canSynchronizeAnimator) - return; - if (_animator.HasState(layer, hash) || hash == 0) - { - _animator.CrossFadeInFixedTime(hash, fixedTransitionDuration, layer, fixedTimeOffset, normalizedTransitionTime); - _unsynchronizedLayerStates[layer] = new(Time.frameCount, hash, true, fixedTransitionDuration, fixedTimeOffset, normalizedTransitionTime); - } - } - #endregion - - #region Triggers. - /// - /// Sets a trigger on the animator and sends it over the network. - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetTrigger(int hash) - { - if (!_canSynchronizeAnimator) - return; - UpdateTrigger(hash, true); - } - - /// - /// Sets a trigger on the animator and sends it over the network. - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetTrigger(string name) - { - SetTrigger(Animator.StringToHash(name)); - } - - /// - /// Resets a trigger on the animator and sends it over the network. - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ResetTrigger(int hash) - { - UpdateTrigger(hash, false); - } - - /// - /// Resets a trigger on the animator and sends it over the network. - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ResetTrigger(string name) - { - ResetTrigger(Animator.StringToHash(name)); - } - - /// - /// Updates a trigger, sets or resets. - /// - /// - private void UpdateTrigger(int hash, bool set) - { - if (!_canSynchronizeAnimator) - return; - - bool clientAuth = ClientAuthoritative; - //If there is an owner perform checks. - if (Owner.IsValid) - { - //If client auth and not owner. - if (clientAuth && !IsOwner) - return; - } - //There is no owner. - else - { - if (!IsServerStarted) - return; - } - - //Update locally. - if (set) - _animator.SetTrigger(hash); - else - _animator.ResetTrigger(hash); - - /* Can send if any of the following are true: - * ClientAuth + Owner. - * ClientAuth + No Owner + IsServer - * !ClientAuth + IsServer. */ - bool canSend = (clientAuth && IsOwner) || (clientAuth && !Owner.IsValid) || (!clientAuth && IsServerStarted); - - //Only queue a send if proper side. - if (canSend) - { - for (byte i = 0; i < _parameterDetails.Count; i++) - { - if (_parameterDetails[i].Hash == hash) - { - _triggerUpdates.Add(new(i, set)); - return; - } - } - //Fall through, hash not found. - NetworkManager.LogWarning($"Hash {hash} not found while trying to update a trigger."); - } - } - #endregion - - #region Remote actions. - /// - /// Called on clients to receive an animator update. - /// - /// - [TargetRpc(ValidateTarget = false)] - private void TargetAnimatorUpdated(NetworkConnection connection, ArraySegment data) - { - if (!_canSynchronizeAnimator) - return; - - //If receiver is client host then do nothing, clientHost need not process. - if (IsServerInitialized && connection.IsLocalClient) - return; - - bool clientAuth = ClientAuthoritative; - bool isOwner = IsOwner; - /* If set for client auth and owner then do not process. - * This could be the case if an update was meant to come before - * ownership gain but came out of late due to out of order when using unreliable. - * Cannot check sendToOwner given clients may not - * always be aware of owner depending on ShareIds setting. */ - if (clientAuth && isOwner) - return; - /* If not client auth and not to send to owner, and is owner - * then also return. */ - if (!clientAuth && !_sendToOwner && isOwner) - return; - - ReceivedServerData rd = new(data); - _fromServerBuffer.Enqueue(rd); - - if (_startTick == 0) - _startTick = TimeManager.LocalTick + _interpolation; - } - - /// - /// Called on server to receive an animator update. - /// - /// - [ServerRpc] - private void ServerAnimatorUpdated(ArraySegment data) - { - if (!_canSynchronizeAnimator) - return; - if (!ClientAuthoritative) - { - Owner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection Id {Owner.ClientId} has been kicked for trying to update this object without client authority."); - return; - } - - /* Server does not need to apply interpolation. - * Even as clientHost when CSP is being used the - * clientHost will always be on the latest tick. - * Spectators on the other hand will remain behind - * a little depending on their components interpolation. */ - ApplyParametersUpdated(ref data); - _clientAuthoritativeUpdates.AddToBuffer(ref data); - } - #endregion - - #region Editor. - #if UNITY_EDITOR - protected override void Reset() - { - base.Reset(); - if (_animator == null) - SetAnimator(GetComponent()); - } - #endif - #endregion - } -} \ No newline at end of file From dc0606bd222ecd9345f07427b75657c3b9d2f82e Mon Sep 17 00:00:00 2001 From: waterb <143908720+belplaton@users.noreply.github.com> Date: Thu, 18 Dec 2025 03:42:12 +0300 Subject: [PATCH 14/23] Refactor ManagedObjects class for clarity and structure --- Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs b/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs index 5e52c507..2051ee58 100644 --- a/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs +++ b/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs @@ -76,7 +76,7 @@ protected void HandleAdd(NetworkObject nob) protected void HandleRemove(NetworkObject nob) { if (_spawned.Remove(nob.ObjectId)) - OnSpawnedAdd?.Invoke(nob.ObjectId, nob); + OnSpawnedRemove?.Invoke(nob.ObjectId, nob); } protected void HandleClear() @@ -541,4 +541,5 @@ protected void CheckReadSceneObjectDetails(Reader r, ref string sceneName, ref s } #endif } -} \ No newline at end of file + +} From 912bd0ea8f9347aa60bb29d29fe609fb28fb1937 Mon Sep 17 00:00:00 2001 From: waterb <143908720+belplaton@users.noreply.github.com> Date: Sun, 28 Dec 2025 03:06:10 +0300 Subject: [PATCH 15/23] Rename profiler markers for clarity --- .../Component/TickSmoothing/TickSmoothingManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs index 4d21bd49..e315616e 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -29,10 +29,10 @@ public partial class TickSmoothingManager : MonoBehaviour private static readonly ProfilerMarker _pm_Prediction_OnPostReplicateReplay = new("TickSmoothingManager.Prediction_OnPostReplicateReplay()"); private static readonly ProfilerMarker _pm_TimeManager_OnRoundTripTimeUpdated = new("TickSmoothingManager.TimeManager_OnRoundTripTimeUpdated()"); private static readonly ProfilerMarker _pm_MoveToTarget = new("TickSmoothingManager.MoveToTarget()"); - private static readonly ProfilerMarker _pm_ScheduleUpdateRealtimeInterpolation = new("TickSmoothingManager.UpdateRealtimeInterpolation()"); - private static readonly ProfilerMarker _pm_ScheduleDiscardExcessiveTransformPropertiesQueue = new("TickSmoothingManager.DiscardExcessiveTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_ScheduleSetMoveRates = new("TickSmoothingManager.SetMoveRates()"); - private static readonly ProfilerMarker _pm_ScheduleSetMovementMultiplier = new("TickSmoothingManager.SetMovementMultiplier()"); + private static readonly ProfilerMarker _pm_ScheduleUpdateRealtimeInterpolation = new("TickSmoothingManager.ScheduleUpdateRealtimeInterpolation()"); + private static readonly ProfilerMarker _pm_ScheduleDiscardExcessiveTransformPropertiesQueue = new("TickSmoothingManager.ScheduleDiscardExcessiveTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_ScheduleSetMoveRates = new("TickSmoothingManager.ScheduleSetMoveRates()"); + private static readonly ProfilerMarker _pm_ScheduleSetMovementMultiplier = new("TickSmoothingManager.ScheduleSetMovementMultiplier()"); private static readonly ProfilerMarker _pm_ScheduleAddTransformProperties = new("TickSmoothingManager.ScheduleAddTransformProperties()"); private static readonly ProfilerMarker _pm_ScheduleClearTransformPropertiesQueue = new("TickSmoothingManager.ScheduleClearTransformPropertiesQueue()"); private static readonly ProfilerMarker _pm_ScheduleModifyTransformProperties = new("TickSmoothingManager.ScheduleModifyTransformProperties()"); From e44b772a7cc357c39734fc4bf2409528438d8f5b Mon Sep 17 00:00:00 2001 From: waterb <143908720+belplaton@users.noreply.github.com> Date: Wed, 7 Jan 2026 04:29:40 +0300 Subject: [PATCH 16/23] Replace SetLocalPositionAndRotation with SetCorrectLocalPositionAndRotation --- Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs index befe7acb..c4a88970 100644 --- a/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs +++ b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs @@ -507,7 +507,7 @@ static void ApplyPosRot(TransformAccess t, bool worldSpace, float3 pos, quaterni if (worldSpace) t.SetPositionAndRotation(pos, rot); else - t.SetLocalPositionAndRotation(pos, rot); + t.SetCorrectLocalPositionAndRotation(pos, rot); } } @@ -618,4 +618,4 @@ public class MoveRatesCls : IResettable [MethodImpl(MethodImplOptions.AggressiveInlining)] public void InitializeState() { } } -} \ No newline at end of file +} From 52532a161a228e6499c75e44ca735e341d30b027 Mon Sep 17 00:00:00 2001 From: Markez Date: Sun, 11 Jan 2026 17:44:42 +0400 Subject: [PATCH 17/23] feat: fix documentation opening tags for SetFrameRate --- Assets/FishNet/Runtime/Managing/Client/ClientManager.cs | 1 + Assets/FishNet/Runtime/Managing/Server/ServerManager.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs index d3e6ca6d..9b38e82d 100644 --- a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs +++ b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs @@ -120,6 +120,7 @@ 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. diff --git a/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs b/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs index 55d879d0..d9527bab 100644 --- a/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs +++ b/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs @@ -172,6 +172,7 @@ 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. From 63a5e9d900c800ef3eecf7a65376e6d1d5a4b179 Mon Sep 17 00:00:00 2001 From: belplaton Date: Mon, 19 Jan 2026 14:43:43 +0300 Subject: [PATCH 18/23] feat: make guid network serializing non-alloc --- .../Plugins/GameKit/Dependencies/Utilities/Guids.cs | 10 ++++++++++ Assets/FishNet/Runtime/Serializing/Reader.cs | 6 +----- Assets/FishNet/Runtime/Serializing/Writer.cs | 11 +++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Guids.cs 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 54f16396..bd64ac80 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(); From 36b80cee9a6fd72bf92bc9e2b664945c5bd53705 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 21 Jan 2026 14:00:43 +0300 Subject: [PATCH 19/23] feat: add preprocessor markers "THREADED_TICKSMOOTHERS" --- .../Editor/MovementSettingsDrawer.Threaded.cs | 53 + .../MovementSettingsDrawer.Threaded.cs.meta | 11 + .../Editor/MovementSettingsDrawer.cs | 2 + .../Editor/NetworkTickSmootherEditor.cs | 3 +- .../MovementSettings.Threaded.cs | 60 ++ .../MovementSettings.Threaded.cs.meta | 11 + .../TickSmoothing/MovementSettings.cs | 10 +- .../TickSmoothing/NetworkTickSmoother.cs | 3 +- .../TickSmootherController.Threaded.cs | 332 ++++++ .../TickSmootherController.Threaded.cs.meta | 11 + .../TickSmoothing/TickSmootherController.cs | 291 +++--- .../TickSmoothingManager.Types.cs | 2 + .../TickSmoothingManager.Types.cs.meta | 11 + .../TickSmoothing/TickSmoothingManager.cs | 21 + .../TickSmoothingManager.cs.meta | 11 + .../UniversalTickSmoother.Threaded.cs | 983 ++++++++++++++++++ .../UniversalTickSmoother.Threaded.cs.meta | 11 + .../TickSmoothing/UniversalTickSmoother.cs | 170 +-- .../Runtime/Managing/NetworkManager.cs | 6 + .../Runtime/Managing/Timing/TimeManager.cs | 84 +- 20 files changed, 1828 insertions(+), 258 deletions(-) create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs create mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs new file mode 100644 index 00000000..bac3154c --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs @@ -0,0 +1,53 @@ +#if UNITY_EDITOR +using FishNet.Object; +using GameKit.Dependencies.Utilities; +using UnityEditor; +using UnityEngine; + +namespace FishNet.Component.Transforming.Beta.Editing +{ + #if THREADED_TICKSMOOTHERS + [CustomPropertyDrawer(typeof(MovementSettings))] + public class MovementSettingsDrawer : PropertyDrawer + { + private PropertyDrawerTool _propertyDrawer; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(position, label, property); + + _propertyDrawer = new(position); + + // _propertyDrawer.DrawLabel(label, FontStyle.Bold); + + EditorGUI.indentLevel++; + + SerializedProperty enableTeleport = property.FindPropertyRelative("EnableTeleport"); + SerializedProperty teleportThreshold = property.FindPropertyRelative("TeleportThreshold"); + SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue"); + SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue"); + SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties"); + SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties"); + + _propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport"); + if (enableTeleport.boolValue == true) + _propertyDrawer.DrawProperty(teleportThreshold, "Teleport Threshold", indent: 1); + + _propertyDrawer.DrawProperty(adaptiveInterpolationValue, "Adaptive Interpolation"); + if ((AdaptiveInterpolationType)adaptiveInterpolationValue.intValue == AdaptiveInterpolationType.Off) + _propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1); + + _propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties"); + if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything) + _propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1); + + _propertyDrawer.SetIndentToStarting(); + + EditorGUI.EndProperty(); + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight(); + } + #endif +} +#endif \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta new file mode 100644 index 00000000..cb9b31bd --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d3d0f9278ca7424f964926dc8a70515 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs index 71bca8c1..0298f005 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs @@ -6,6 +6,7 @@ namespace FishNet.Component.Transforming.Beta.Editing { + #if !THREADED_TICKSMOOTHERS [CustomPropertyDrawer(typeof(MovementSettings))] public class MovementSettingsDrawer : PropertyDrawer { @@ -49,5 +50,6 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight(); } + #endif } #endif \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs index 533e6ae8..74aa282a 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs @@ -31,7 +31,7 @@ public override void OnInspectorGUI() EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((NetworkTickSmoother)target), typeof(NetworkTickSmoother), false); GUI.enabled = true; - EditorGUILayout.PropertyField(_favorPredictionNetworkTransform); + EditorGUILayout.LabelField("Initialization Settings", EditorStyles.boldLabel); EditorGUILayout.PropertyField(_initializationSettings); @@ -45,7 +45,6 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(_spectatorMovementSettings); } - // EditorGUI.indentLevel--; serializedObject.ApplyModifiedProperties(); diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs new file mode 100644 index 00000000..c3abffd7 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs @@ -0,0 +1,60 @@ +using FishNet.Object; +using UnityEngine; + +namespace FishNet.Component.Transforming.Beta +{ + #if THREADED_TICKSMOOTHERS + [System.Serializable] + public struct MovementSettings + { + /// + /// True to enable teleport threshold. + /// + [Tooltip("True to enable teleport threshold.")] + public bool EnableTeleport; + /// + /// How far the object must move between ticks to teleport rather than smooth. + /// + [Tooltip("How far the object must move between ticks to teleport rather than smooth.")] + [Range(0f, ushort.MaxValue)] + public float TeleportThreshold; + /// + /// Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. + /// In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects. + /// + [Tooltip("Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.")] + public AdaptiveInterpolationType AdaptiveInterpolationValue; + /// + /// Number of ticks to smooth over when not using adaptive interpolation. + /// + [Tooltip("Number of ticks to smooth over when not using adaptive interpolation.")] + public byte InterpolationValue; + /// + /// Properties to smooth. Any value not selected will become offset with every movement. + /// + [Tooltip("Properties to smooth. Any value not selected will become offset with every movement.")] + public TransformPropertiesFlag SmoothedProperties; + /// + /// True to apply smoothing in local space for position and rotation. False to use world space. + /// + [Tooltip("True to apply smoothing in local space for position and rotation. False to use world space.")] + public bool UseLocalSpace; + /// + /// True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick. + /// + [Tooltip("True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.")] + public bool SnapNonSmoothedProperties; + + public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersOnStructsAlready) + { + EnableTeleport = false; + TeleportThreshold = 0f; + AdaptiveInterpolationValue = AdaptiveInterpolationType.Off; + InterpolationValue = 2; + SmoothedProperties = TransformPropertiesFlag.Everything; + UseLocalSpace = false; + SnapNonSmoothedProperties = false; + } + } + #endif +} diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta new file mode 100644 index 00000000..24da146e --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 99cd0db221a6bd94288e6cc02aabca8f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs index 135913c8..e033d7cb 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs @@ -3,6 +3,7 @@ namespace FishNet.Component.Transforming.Beta { + #if !THREADED_TICKSMOOTHERS [System.Serializable] public struct MovementSettings { @@ -34,11 +35,6 @@ public struct MovementSettings [Tooltip("Properties to smooth. Any value not selected will become offset with every movement.")] public TransformPropertiesFlag SmoothedProperties; /// - /// True to apply smoothing in local space for position and rotation. False to use world space. - /// - [Tooltip("True to apply smoothing in local space for position and rotation. False to use world space.")] - public bool UseLocalSpace; - /// /// True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick. /// [Tooltip("True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.")] @@ -51,8 +47,8 @@ public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersO AdaptiveInterpolationValue = AdaptiveInterpolationType.Off; InterpolationValue = 2; SmoothedProperties = TransformPropertiesFlag.Everything; - UseLocalSpace = false; SnapNonSmoothedProperties = false; } } -} + #endif +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs index 1f6cb705..2e199113 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs @@ -1,7 +1,6 @@ using FishNet.Object; using GameKit.Dependencies.Utilities; using UnityEngine; -using UnityEngine.Serialization; namespace FishNet.Component.Transforming.Beta { @@ -53,7 +52,7 @@ private void OnDestroy() public override void OnStartClient() { RetrieveControllers(); - + _initializationSettings.SetNetworkedRuntimeValues(initializingNetworkBehaviour: this, graphicalTransform: transform, _favorPredictionNetworkTransform); SmootherController.Initialize(_initializationSettings, _controllerMovementSettings, _spectatorMovementSettings); diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs new file mode 100644 index 00000000..9acc4914 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs @@ -0,0 +1,332 @@ +using FishNet.Managing.Timing; +using FishNet.Object; +using GameKit.Dependencies.Utilities; +using UnityEngine; +using Unity.Profiling; + +namespace FishNet.Component.Transforming.Beta +{ + #if THREADED_TICKSMOOTHERS + /// + /// Smoothes this object between ticks. + /// + /// This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction. + public class TickSmootherController : IResettable + { + #region Public. + // /// + // /// Logic for owner smoothing. + // /// + // public UniversalTickSmoother UniversalSmoother { get; private set; } + #endregion + + #region Private. + + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("TickSmootherController.TimeManager_OnUpdate()"); + private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPostTick()"); + + #endregion + + /// + /// + private InitializationSettings _initializationSettings = new(); + /// + /// + private MovementSettings _ownerMovementSettings = new(); + /// + /// + private MovementSettings _spectatorMovementSettings = new(); + /// + /// True if OnDestroy has been called. + /// + private bool _destroyed; + /// + /// Cached timeManager reference. + /// + private TimeManager _timeManager; + /// + /// NetworkBehaviour which initialized this object. Value may be null when initialized for an Offline smoother. + /// + private NetworkBehaviour _initializingNetworkBehaviour; + /// + /// TickSmoothingManager. + /// + private TickSmoothingManager _tickSmoothingManager; + /// + /// Transform which initialized this object. + /// + private Transform _graphicalTransform; + /// + /// True if initialized with a null NetworkBehaviour. + /// + private bool _initializedOffline; + /// + /// True if subscribed to events used for adaptiveInterpolation. + /// + private bool _subscribedToAdaptiveEvents; + /// + /// True if currently subscribed to events. + /// + private bool _subscribed; + /// + /// True if initialized. + /// + private bool _isInitialized; + #endregion + + public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) + { + _tickSmoothingManager = + initializationSettings.InitializingNetworkBehaviour?.NetworkManager.TickSmoothingManager ?? + InstanceFinder.NetworkManager.TickSmoothingManager; + _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; + _graphicalTransform = initializationSettings.GraphicalTransform; + + _initializationSettings = initializationSettings; + _ownerMovementSettings = ownerSettings; + _spectatorMovementSettings = spectatorSettings; + + _initializedOffline = initializationSettings.InitializingNetworkBehaviour == null; + + _isInitialized = true; + } + + public void OnDestroy() + { + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Unregister(this); + + // ChangeSubscriptions(false); + // StoreSmoother(); + _destroyed = true; + _isInitialized = false; + } + + public void StartSmoother() + { + if (!_isInitialized) + return; + + bool canStart = _initializedOffline ? StartOffline() : StartOnline(); + + if (!canStart) + return; + + // RetrieveSmoothers(); + // + // UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + // + // UniversalSmoother.StartSmoother(); + + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Register(this, _initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + + bool StartOnline() + { + NetworkBehaviour nb = _initializingNetworkBehaviour; + + SetTimeManager(nb.TimeManager); + + return true; + } + + bool StartOffline() + { + if (_timeManager == null) + return false; + + return true; + } + } + + public void StopSmoother() + { + var tsm = _tickSmoothingManager; + if (tsm != null) + tsm.Unregister(this); + + if (!_initializedOffline) + StopOnline(); + + // if (UniversalSmoother != null) + // UniversalSmoother.StopSmoother(); + + void StopOnline() + { + SetTimeManager(tm: null); + } + + // Intentionally left blank. + // void StopOffline() { } + } + + // public void TimeManager_OnUpdate() + // { + // using (_pm_OnUpdate.Auto()) + // { + // UniversalSmoother.OnUpdate(Time.deltaTime); + // } + // } + // + // public void TimeManager_OnPreTick() + // { + // using (_pm_OnPreTick.Auto()) + // { + // UniversalSmoother.OnPreTick(); + // } + // } + // + // /// + // /// Called after a tick completes. + // /// + // public void TimeManager_OnPostTick() + // { + // using (_pm_OnPostTick.Auto()) + // { + // if (_timeManager != null) + // UniversalSmoother.OnPostTick(_timeManager.LocalTick); + // } + // } + // + // private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) + // { + // UniversalSmoother.OnPostReplicateReplay(clientTick); + // } + // + // private void TimeManager_OnRoundTripTimeUpdated(long rttMs) + // { + // UniversalSmoother.UpdateRealtimeInterpolation(); + // } + // + // /// + // /// Stores smoothers if they have value. + // /// + // private void StoreSmoother() + // { + // if (UniversalSmoother == null) + // return; + // + // ResettableObjectCaches.Store(UniversalSmoother); + // UniversalSmoother = null; + // } + // + // /// + // /// Stores current smoothers and retrieves new ones. + // /// + // private void RetrieveSmoothers() + // { + // StoreSmoother(); + // UniversalSmoother = ResettableObjectCaches.Retrieve(); + // } + + // /// + // /// Sets a target transform to follow. + // /// + // public void SetTargetTransform(Transform value) + // { + // Transform currentTargetTransform = _initializationSettings.TargetTransform; + // + // if (value == currentTargetTransform) + // return; + // + // bool clientStartCalled = (_initializedOffline && _timeManager != null) || (_initializingNetworkBehaviour != null && _initializingNetworkBehaviour.OnStartClientCalled); + // + // bool previousTargetTransformIsValid = (currentTargetTransform != null); + // + // // If target is different and old is not null then reset. + // if (previousTargetTransformIsValid && clientStartCalled) + // OnStopClient(); + // + // _initializationSettings.TargetTransform = value; + // if (previousTargetTransformIsValid && clientStartCalled) + // OnStartClient(); + // } + + /// + /// Sets a new PredictionManager to use. + /// + public void SetTimeManager(TimeManager tm) + { + if (tm == _timeManager) + return; + + // Unsub from current. + // ChangeSubscriptions(false); + //Sub to newest. + _timeManager = tm; + // ChangeSubscriptions(true); + } + + + // /// + // /// Changes the subscription to the TimeManager. + // /// + // private void ChangeSubscriptions(bool subscribe) + // { + // if (_destroyed) + // return; + // TimeManager tm = _timeManager; + // if (tm == null) + // return; + // + // if (subscribe == _subscribed) + // return; + // _subscribed = subscribe; + // + // bool adaptiveIsOff = _ownerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off; + // + // if (subscribe) + // { + // tm.OnUpdate += TimeManager_OnUpdate; + // tm.OnPreTick += TimeManager_OnPreTick; + // tm.OnPostTick += TimeManager_OnPostTick; + // + // if (!adaptiveIsOff) + // { + // tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; + // PredictionManager pm = tm.NetworkManager.PredictionManager; + // pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; + // _subscribedToAdaptiveEvents = true; + // } + // } + // else + // { + // tm.OnUpdate -= TimeManager_OnUpdate; + // tm.OnPreTick -= TimeManager_OnPreTick; + // tm.OnPostTick -= TimeManager_OnPostTick; + // + // if (_subscribedToAdaptiveEvents) + // { + // tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; + // PredictionManager pm = tm.NetworkManager.PredictionManager; + // pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; + // } + // } + // } + + public void ResetState() + { + _initializationSettings = default; + _ownerMovementSettings = default; + _spectatorMovementSettings = default; + + _destroyed = false; + _timeManager = null; + _initializingNetworkBehaviour = null; + _graphicalTransform = null; + + _subscribed = false; + _subscribedToAdaptiveEvents = false; + + _isInitialized = false; + } + + public void InitializeState() { } + } + #endif +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta new file mode 100644 index 00000000..d6c4d59d --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c757af1b1b164aa4c9d239dd72e15b93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs index 4bf63b15..2544587d 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs @@ -1,11 +1,13 @@ -using FishNet.Managing.Timing; +using FishNet.Managing.Predicting; +using FishNet.Managing.Timing; using FishNet.Object; using GameKit.Dependencies.Utilities; -using UnityEngine; using Unity.Profiling; +using UnityEngine; namespace FishNet.Component.Transforming.Beta { + #if !THREADED_TICKSMOOTHERS /// /// Smoothes this object between ticks. /// @@ -13,28 +15,19 @@ namespace FishNet.Component.Transforming.Beta public class TickSmootherController : IResettable { #region Public. - // /// - // /// Logic for owner smoothing. - // /// - // public UniversalTickSmoother UniversalSmoother { get; private set; } + /// + /// Logic for owner smoothing. + /// + public UniversalTickSmoother UniversalSmoother { get; private set; } #endregion - + #region Private. - - #region Private Profiler Markers - - private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("TickSmootherController.TimeManager_OnUpdate()"); - private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPostTick()"); - - #endregion - /// /// private InitializationSettings _initializationSettings = new(); /// /// - private MovementSettings _ownerMovementSettings = new(); + private MovementSettings _controllerMovementSettings = new(); /// /// private MovementSettings _spectatorMovementSettings = new(); @@ -51,10 +44,6 @@ public class TickSmootherController : IResettable /// private NetworkBehaviour _initializingNetworkBehaviour; /// - /// TickSmoothingManager. - /// - private TickSmoothingManager _tickSmoothingManager; - /// /// Transform which initialized this object. /// private Transform _graphicalTransform; @@ -74,18 +63,18 @@ public class TickSmootherController : IResettable /// True if initialized. /// private bool _isInitialized; + private static readonly ProfilerMarker _pm_OnUpdate = new("TickSmootherController.TimeManager_OnUpdate()"); + private static readonly ProfilerMarker _pm_OnPreTick = new("TickSmootherController.TimeManager_OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostTick = new("TickSmootherController.TimeManager_OnPostTick()"); #endregion - public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) + public void Initialize(InitializationSettings initializationSettings, MovementSettings controllerSettings, MovementSettings spectatorSettings) { - _tickSmoothingManager = - initializationSettings.InitializingNetworkBehaviour?.NetworkManager.TickSmoothingManager ?? - InstanceFinder.NetworkManager.TickSmoothingManager; _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; _graphicalTransform = initializationSettings.GraphicalTransform; _initializationSettings = initializationSettings; - _ownerMovementSettings = ownerSettings; + _controllerMovementSettings = controllerSettings; _spectatorMovementSettings = spectatorSettings; _initializedOffline = initializationSettings.InitializingNetworkBehaviour == null; @@ -95,12 +84,8 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe public void OnDestroy() { - var tsm = _tickSmoothingManager; - if (tsm != null) - tsm.Unregister(this); - - // ChangeSubscriptions(false); - // StoreSmoother(); + ChangeSubscriptions(false); + StoreSmoother(); _destroyed = true; _isInitialized = false; } @@ -115,15 +100,11 @@ public void StartSmoother() if (!canStart) return; - // RetrieveSmoothers(); - // - // UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); - // - // UniversalSmoother.StartSmoother(); + RetrieveSmoothers(); + + UniversalSmoother.Initialize(_initializationSettings, _controllerMovementSettings, _spectatorMovementSettings); - var tsm = _tickSmoothingManager; - if (tsm != null) - tsm.Register(this, _initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); + UniversalSmoother.StartSmoother(); bool StartOnline() { @@ -145,15 +126,13 @@ bool StartOffline() public void StopSmoother() { - var tsm = _tickSmoothingManager; - if (tsm != null) - tsm.Unregister(this); - + ChangeSubscriptions(subscribe: false); + if (!_initializedOffline) StopOnline(); - - // if (UniversalSmoother != null) - // UniversalSmoother.StopSmoother(); + + if (UniversalSmoother != null) + UniversalSmoother.StopSmoother(); void StopOnline() { @@ -164,64 +143,64 @@ void StopOnline() // void StopOffline() { } } - // public void TimeManager_OnUpdate() - // { - // using (_pm_OnUpdate.Auto()) - // { - // UniversalSmoother.OnUpdate(Time.deltaTime); - // } - // } - // - // public void TimeManager_OnPreTick() - // { - // using (_pm_OnPreTick.Auto()) - // { - // UniversalSmoother.OnPreTick(); - // } - // } - // - // /// - // /// Called after a tick completes. - // /// - // public void TimeManager_OnPostTick() - // { - // using (_pm_OnPostTick.Auto()) - // { - // if (_timeManager != null) - // UniversalSmoother.OnPostTick(_timeManager.LocalTick); - // } - // } - // - // private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) - // { - // UniversalSmoother.OnPostReplicateReplay(clientTick); - // } - // - // private void TimeManager_OnRoundTripTimeUpdated(long rttMs) - // { - // UniversalSmoother.UpdateRealtimeInterpolation(); - // } - // - // /// - // /// Stores smoothers if they have value. - // /// - // private void StoreSmoother() - // { - // if (UniversalSmoother == null) - // return; - // - // ResettableObjectCaches.Store(UniversalSmoother); - // UniversalSmoother = null; - // } - // - // /// - // /// Stores current smoothers and retrieves new ones. - // /// - // private void RetrieveSmoothers() - // { - // StoreSmoother(); - // UniversalSmoother = ResettableObjectCaches.Retrieve(); - // } + public void TimeManager_OnUpdate() + { + using (_pm_OnUpdate.Auto()) + { + UniversalSmoother.OnUpdate(Time.deltaTime); + } + } + + public void TimeManager_OnPreTick() + { + using (_pm_OnPreTick.Auto()) + { + UniversalSmoother.OnPreTick(); + } + } + + /// + /// Called after a tick completes. + /// + public void TimeManager_OnPostTick() + { + using (_pm_OnPostTick.Auto()) + { + if (_timeManager != null) + UniversalSmoother.OnPostTick(_timeManager.LocalTick); + } + } + + private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) + { + UniversalSmoother.OnPostReplicateReplay(clientTick); + } + + private void TimeManager_OnRoundTripTimeUpdated(long rttMs) + { + UniversalSmoother.UpdateRealtimeInterpolation(); + } + + /// + /// Stores smoothers if they have value. + /// + private void StoreSmoother() + { + if (UniversalSmoother == null) + return; + + ResettableObjectCaches.Store(UniversalSmoother); + UniversalSmoother = null; + } + + /// + /// Stores current smoothers and retrieves new ones. + /// + private void RetrieveSmoothers() + { + StoreSmoother(); + UniversalSmoother = ResettableObjectCaches.Retrieve(); + } // /// // /// Sets a target transform to follow. @@ -255,63 +234,62 @@ public void SetTimeManager(TimeManager tm) return; // Unsub from current. - // ChangeSubscriptions(false); + ChangeSubscriptions(false); //Sub to newest. _timeManager = tm; - // ChangeSubscriptions(true); + ChangeSubscriptions(true); } - - - // /// - // /// Changes the subscription to the TimeManager. - // /// - // private void ChangeSubscriptions(bool subscribe) - // { - // if (_destroyed) - // return; - // TimeManager tm = _timeManager; - // if (tm == null) - // return; - // - // if (subscribe == _subscribed) - // return; - // _subscribed = subscribe; - // - // bool adaptiveIsOff = _ownerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off; - // - // if (subscribe) - // { - // tm.OnUpdate += TimeManager_OnUpdate; - // tm.OnPreTick += TimeManager_OnPreTick; - // tm.OnPostTick += TimeManager_OnPostTick; - // - // if (!adaptiveIsOff) - // { - // tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; - // PredictionManager pm = tm.NetworkManager.PredictionManager; - // pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; - // _subscribedToAdaptiveEvents = true; - // } - // } - // else - // { - // tm.OnUpdate -= TimeManager_OnUpdate; - // tm.OnPreTick -= TimeManager_OnPreTick; - // tm.OnPostTick -= TimeManager_OnPostTick; - // - // if (_subscribedToAdaptiveEvents) - // { - // tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; - // PredictionManager pm = tm.NetworkManager.PredictionManager; - // pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; - // } - // } - // } - + + /// + /// Changes the subscription to the TimeManager. + /// + private void ChangeSubscriptions(bool subscribe) + { + if (_destroyed) + return; + TimeManager tm = _timeManager; + if (tm == null) + return; + + if (subscribe == _subscribed) + return; + _subscribed = subscribe; + + bool adaptiveIsOff = _controllerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off; + + if (subscribe) + { + tm.OnUpdate += TimeManager_OnUpdate; + tm.OnPreTick += TimeManager_OnPreTick; + tm.OnPostTick += TimeManager_OnPostTick; + + if (!adaptiveIsOff) + { + tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; + PredictionManager pm = tm.NetworkManager.PredictionManager; + pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; + _subscribedToAdaptiveEvents = true; + } + } + else + { + tm.OnUpdate -= TimeManager_OnUpdate; + tm.OnPreTick -= TimeManager_OnPreTick; + tm.OnPostTick -= TimeManager_OnPostTick; + + if (_subscribedToAdaptiveEvents) + { + tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; + PredictionManager pm = tm.NetworkManager.PredictionManager; + pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; + } + } + } + public void ResetState() { _initializationSettings = default; - _ownerMovementSettings = default; + _controllerMovementSettings = default; _spectatorMovementSettings = default; _destroyed = false; @@ -327,4 +305,5 @@ public void ResetState() public void InitializeState() { } } + #endif } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs index 5b400d53..3be6e893 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs @@ -15,6 +15,7 @@ namespace FishNet.Component.Transforming.Beta { + #if THREADED_TICKSMOOTHERS public partial class TickSmoothingManager { #region Types. @@ -1312,4 +1313,5 @@ public static TransformProperties GetTrackerWorldProperties(TransformAccess trac return new TransformProperties(pos, rot, scl); } } + #endif } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta new file mode 100644 index 00000000..2f6f484a --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2687216adf6641b3a037f7cfcdad52f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs index e315616e..6a4523ae 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -17,6 +17,7 @@ namespace FishNet.Component.Transforming.Beta { + #if THREADED_TICKSMOOTHERS public partial class TickSmoothingManager : MonoBehaviour { #region Private. @@ -87,6 +88,10 @@ public partial class TickSmoothingManager : MonoBehaviour /// Index to TickSmootherController and NetworkBehaviours lookup. /// private readonly List _indexToNetworkBehaviour = new(); + /// + /// Index to TickSmootherController and redictionNetworkTransform lookup. + /// + private readonly List _indexToPredictionNetworkTransform = new(); /// /// Index to MoveRate lookup. @@ -375,6 +380,7 @@ private void OnDestroy() if (_trackerTaa.isCreated) _trackerTaa.Dispose(); _indexToNetworkBehaviour.Clear(); + _indexToPredictionNetworkTransform.Clear(); _indexToSmoother.Clear(); _lookup.Clear(); @@ -418,6 +424,14 @@ public void Register(TickSmootherController smoother, InitializationSettings ini _lookup[smoother] = index; _indexToSmoother.Add(smoother); _indexToNetworkBehaviour.Add(initializationSettings.InitializingNetworkBehaviour); + _indexToPredictionNetworkTransform.Add( + initializationSettings.FavorPredictionNetworkTransform && + initializationSettings.InitializingNetworkBehaviour != null && + initializationSettings.InitializingNetworkBehaviour.NetworkObject != null && + initializationSettings.InitializingNetworkBehaviour.NetworkObject.IsRigidbodyPredictionType + ? initializationSettings.InitializingNetworkBehaviour.NetworkObject.PredictionNetworkTransform + : null + ); _moveRates.Add(new MoveRates(MoveRates.UNSET_VALUE)); _ownerSettings.Add(ownerSettings); @@ -515,9 +529,12 @@ public void Unregister(TickSmootherController smoother) _lookup[movedSmoother] = index; var movedNetworkBehaviour = _indexToNetworkBehaviour[last]; _indexToNetworkBehaviour[index] = movedNetworkBehaviour; + var movedPredictionNetworkTransform = _indexToPredictionNetworkTransform[last]; + _indexToPredictionNetworkTransform[index] = movedPredictionNetworkTransform; } _indexToNetworkBehaviour.RemoveAt(last); + _indexToPredictionNetworkTransform.RemoveAt(last); _indexToSmoother.RemoveAt(last); _lookup.Remove(smoother); @@ -877,8 +894,11 @@ private void TimeManager_OnPreTick() { Transform graphicalTransform = _graphicalTaa[i]; NetworkBehaviour networkBehaviour = _indexToNetworkBehaviour[i]; + NetworkTransform predictionNetworkTransform = _indexToPredictionNetworkTransform[i]; _canSmoothMask[i] = (byte)(graphicalTransform != null && + (predictionNetworkTransform == null || + !predictionNetworkTransform.DoSettingsAllowSmoothing()) && _networkManager.IsClientStarted ? 1 : 0); _useOwnerSettingsMask[i] = @@ -1334,4 +1354,5 @@ private static int ComputeBatchSize(int length, int minBatch = 1, int maxBatch = return Mathf.Clamp(batch, minBatch, maxBatch); } } + #endif } diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta new file mode 100644 index 00000000..80b9c583 --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bed04a80458a35443b1a283848e3d1c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs new file mode 100644 index 00000000..b6b3d47f --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs @@ -0,0 +1,983 @@ +using System; +using FishNet.Managing; +using FishNet.Managing.Timing; +using FishNet.Object; +using FishNet.Object.Prediction; +using FishNet.Utility.Extension; +using GameKit.Dependencies.Utilities; +using UnityEngine; +using UnityEngine.Profiling; +using Unity.Profiling; +using UnityEngine.Scripting; + +namespace FishNet.Component.Transforming.Beta +{ + #if THREADED_TICKSMOOTHERS + /// + /// This class is under regular development and it's API may change at any time. + /// + public sealed class UniversalTickSmoother : IResettable + { + #region Public. + /// + /// True if currently initialized. + /// + public bool IsInitialized { get; private set; } + #endregion + + #region Private. + + #region Private Profiler Markers + + private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new ProfilerMarker("UniversalTickSmoother.UpdateRealtimeInterpolation()"); + private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("UniversalTickSmoother.OnUpdate(float)"); + private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("UniversalTickSmoother.OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new ProfilerMarker("UniversalTickSmoother.OnPostReplicateReplay(uint)"); + private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("UniversalTickSmoother.OnPostTick(uint)"); + private static readonly ProfilerMarker _pm_ClearTPQ = new ProfilerMarker("UniversalTickSmoother.ClearTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_DiscardTPQ = new ProfilerMarker("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_AddTP = new ProfilerMarker("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)"); + private static readonly ProfilerMarker _pm_ModifyTP = new ProfilerMarker("UniversalTickSmoother.ModifyTransformProperties(uint, uint)"); + private static readonly ProfilerMarker _pm_SetMoveRates = new ProfilerMarker("UniversalTickSmoother.SetMoveRates(in TransformProperties)"); + private static readonly ProfilerMarker _pm_MoveToTarget = new ProfilerMarker("UniversalTickSmoother.MoveToTarget(float)"); + + #endregion + + /// + /// How quickly to move towards goal values. + /// + private MoveRates _moveRates = new(); + /// + /// True if a pretick occurred since last postTick. + /// + private bool _preTicked; + /// + /// World values of the graphical after it's been aligned to initialized values in PreTick. + /// + private TransformProperties _trackerPreTickWorldValues; + /// + /// World values of the graphical after it's been aligned to initialized values in PreTick. + /// + private TransformProperties _graphicsPreTickWorldValues; + /// + /// Cached value of adaptive interpolation value. + /// + private AdaptiveInterpolationType _cachedAdaptiveInterpolationValue; + /// + /// Cached value of flat interpolation value. + /// + private byte _cachedInterpolationValue; + /// + /// Cached properties to smooth of the graphical. + /// + private TransformPropertiesFlag _cachedSmoothedProperties; + /// + /// Cached value of snapping non-smoothed properties. + /// + private bool _cachedSnapNonSmoothedProperties; + /// + /// Squared distance target must travel to cause a teleport. + /// + private float _cachedTeleportThreshold; + /// + /// True if to detach on network start. + /// + private bool _detachOnStart; + /// + /// True to re-attach on network stop. + /// + private bool _attachOnStop; + /// + /// True to begin moving soon as movement data becomes available. Movement will ease in until at interpolation value. False to prevent movement until movement data count meet interpolation. + /// + private bool _moveImmediately; + /// + /// Transform the graphics should follow. + /// + private Transform _targetTransform; + /// + /// Cached value of the object to smooth. + /// + private Transform _graphicalTransform; + /// + /// Empty gameObject containing a transform which has properties checked after each simulation. + /// If the graphical starts off as nested of targetTransform then this object is created where the graphical object is. + /// Otherwise, this object is placed directly beneath targetTransform. + /// + private Transform _trackerTransform; + /// + /// TimeManager tickDelta. + /// + private float _tickDelta; + /// + /// NetworkBehaviour this is initialized for. Value may be null. + /// + private NetworkBehaviour _initializingNetworkBehaviour; + /// + /// TimeManager this is initialized for. + /// + private TimeManager _initializingTimeManager; + /// + /// Value to multiply movement by. This is used to reduce or increase the rate the movement buffer is consumed. + /// + private float _movementMultiplier = 1f; + /// + /// TransformProperties to move towards. + /// + private BasicQueue _transformProperties; + /// + /// True if to smooth using owner settings, false for spectator settings. + /// This is only used for performance gains. + /// + private bool _useOwnerSettings; + /// + /// Last tick this was teleported on. + /// + private uint _teleportedTick = TimeManager.UNSET_TICK; + /// + /// Current interpolation value, be it a flat value or adaptive. + /// + private byte _realtimeInterpolation; + /// + /// Settings to use for owners. + /// + private MovementSettings _controllerMovementSettings; + /// + /// Settings to use for spectators. + /// + private MovementSettings _spectatorMovementSettings; + /// + /// True if moving has started and has not been stopped. + /// + private bool _isMoving; + /// + /// NetworkTransform used when prediction type is set to other. + /// + private NetworkTransform _predictionNetworkTransform; + #endregion + + #region Const. + /// + /// Maximum allowed entries to be queued over the interpolation amount. + /// + private const int MAXIMUM_QUEUED_OVER_INTERPOLATION = 3; + #endregion + + [Preserve] + public UniversalTickSmoother() { } + + ~UniversalTickSmoother() + { + // This is a last resort for if something didnt deinitialize right. + ResetState(); + } + + [Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5 + public void SetGraphicalInitializedOffsetValues(TransformProperties value) { } + + [Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5 + public TransformProperties GetGraphicalInitializedOffsetValues() => default; + + /// + /// Tries to set local properties for the graphical tracker transform. + /// + /// New values. + /// Returns true if the tracker has been setup and values have been applied to teh tracker transform. + /// When false is returned the values are cached and will be set when tracker is created. A cached value will be used every time the tracker is setup; to disable this behavior call this method with null value. + public bool TrySetGraphicalTrackerLocalProperties(TransformProperties? localValues) + { + if (_trackerTransform == null || localValues == null) + { + _queuedTrackerProperties = localValues; + return false; + } + + + _trackerTransform.SetLocalProperties(localValues.Value); + return true; + } + + [Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5 + public void SetAdditionalGraphicalOffsetValues(TransformProperties localValues) { } + + [Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5 + public TransformProperties GetAdditionalGraphicalOffsetValues() => default; + + public TransformProperties GetGraphicalTrackerLocalProperties() + { + if (_trackerTransform != null) + return new(_trackerTransform.localPosition, _trackerTransform.localRotation, _trackerTransform.localScale); + if (_queuedTrackerProperties != null) + return _queuedTrackerProperties.Value; + + // Fall through. + NetworkManager manager = _initializingNetworkBehaviour == null ? null : _initializingNetworkBehaviour.NetworkManager; + manager.LogWarning($"Graphical tracker properties cannot be returned because tracker is not setup yet, and no setup properties have been specified. Use TrySetGraphicalTrackerProperties to set setup properties or call this method after IsInitialized is true."); + return default; + } + + /// + /// Properties for the tracker which are queued to be set when the tracker is setup. + /// + private TransformProperties? _queuedTrackerProperties; + + /// + /// Updates the smoothedProperties value. + /// + /// New value. + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetSmoothedProperties(TransformPropertiesFlag value, bool forController) + { + _controllerMovementSettings.SmoothedProperties = value; + SetCaches(forController); + } + + /// + /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. + /// + /// + public void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother) => SetInterpolationValue(value, forOwnerOrOfflineSmoother, unsetAdaptiveInterpolation: true); + + /// + /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. + /// + private void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother, bool unsetAdaptiveInterpolation) + { + if (value < 1) + value = 1; + + if (forOwnerOrOfflineSmoother) + _controllerMovementSettings.InterpolationValue = value; + else + _spectatorMovementSettings.InterpolationValue = value; + + if (unsetAdaptiveInterpolation) + SetAdaptiveInterpolation(AdaptiveInterpolationType.Off, forOwnerOrOfflineSmoother); + } + + /// + /// Updates the adaptiveInterpolation value. + /// + /// New value. + public void SetAdaptiveInterpolation(AdaptiveInterpolationType value, bool forOwnerOrOfflineSmoother) + { + if (forOwnerOrOfflineSmoother) + _controllerMovementSettings.AdaptiveInterpolationValue = value; + else + _spectatorMovementSettings.AdaptiveInterpolationValue = value; + + UpdateRealtimeInterpolation(); + } + + public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) + { + ResetState(); + + Transform graphicalTransform = initializationSettings.GraphicalTransform; + Transform targetTransform = initializationSettings.TargetTransform; + + if (!TransformsAreValid(graphicalTransform, targetTransform)) + return; + + _transformProperties = CollectionCaches.RetrieveBasicQueue(); + _controllerMovementSettings = ownerSettings; + _spectatorMovementSettings = spectatorSettings; + + /* Unset scale smoothing if not detaching. This is to prevent + * the scale from changing with the parent if nested, as that + * would result in the scale being modified twice, once on the parent + * and once on the graphical. Thanks deo_wh for find! */ + if (!initializationSettings.DetachOnStart) + { + _controllerMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; + _spectatorMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; + } + + _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; + _initializingTimeManager = initializationSettings.InitializingTimeManager; + _targetTransform = targetTransform; + _graphicalTransform = graphicalTransform; + _tickDelta = (float)initializationSettings.InitializingTimeManager.TickDelta; + _detachOnStart = initializationSettings.DetachOnStart; + _attachOnStop = initializationSettings.AttachOnStop; + _moveImmediately = initializationSettings.MoveImmediately; + + if (initializationSettings.FavorPredictionNetworkTransform && _initializingNetworkBehaviour != null) + { + NetworkObject networkObject = _initializingNetworkBehaviour.NetworkObject; + if (!networkObject.IsRigidbodyPredictionType) + _predictionNetworkTransform = networkObject.PredictionNetworkTransform; + else + _predictionNetworkTransform = null; + } + else + { + _predictionNetworkTransform = null; + } + + SetCaches(GetUseOwnerSettings()); + + //Use set method as it has sanity checks. + SetInterpolationValue(_controllerMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: true, unsetAdaptiveInterpolation: false); + SetInterpolationValue(_spectatorMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: false, unsetAdaptiveInterpolation: false); + + SetAdaptiveInterpolation(_controllerMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: true); + SetAdaptiveInterpolation(_spectatorMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: false); + + SetupTrackerTransform(); + /* This is called after setting up the tracker transform in the scenario + * the user set additional offsets before this was initialized. */ + if (_queuedTrackerProperties != null) + TrySetGraphicalTrackerLocalProperties(_queuedTrackerProperties.Value); + + void SetupTrackerTransform() + { + _trackerTransform = new GameObject($"{_graphicalTransform.name}_Tracker").transform; + + if (_detachOnStart) + { + _trackerTransform.SetParent(_targetTransform); + } + else + { + Transform trackerParent = _graphicalTransform.IsChildOf(targetTransform) ? _graphicalTransform.parent : targetTransform; + _trackerTransform.SetParent(trackerParent); + } + + _trackerTransform.SetWorldPositionRotationAndScale(_targetTransform.position, _targetTransform.rotation, graphicalTransform.localScale); + } + + IsInitialized = true; + } + + /// + /// Returns if configured transforms are valid. + /// + /// + private bool TransformsAreValid(Transform graphicalTransform, Transform targetTransform) + { + if (graphicalTransform == null) + { + NetworkManagerExtensions.LogError($"Graphical transform cannot be null."); + return false; + } + if (targetTransform == null) + { + NetworkManagerExtensions.LogError($"Target transform on {graphicalTransform} cannot be null."); + return false; + } + if (targetTransform == graphicalTransform) + { + NetworkManagerExtensions.LogError($"Target transform cannot be the same as graphical transform on {graphicalTransform}."); + return false; + } + + return true; + } + + /// + /// Returns true if to use adaptive interpolation. + /// + /// + private bool GetUseAdaptiveInterpolation() + { + if (_cachedAdaptiveInterpolationValue == AdaptiveInterpolationType.Off || _initializingTimeManager.NetworkManager.IsServerOnlyStarted) + return false; + + return true; + } + + /// + /// Gets if to use owner values. + /// + /// OwnerSettings can be used to read determine this as both owner and spectator settings will have the name InitializingNetworkBehaviour. + /// + private bool GetUseOwnerSettings() + { + /* No networkBehaviour indicates an offline smoother. + * The offline smoothers use owner settings. */ + if (_initializingNetworkBehaviour == null) + return true; + + if (_initializingNetworkBehaviour.IsController) + return true; + + return false; + // return _initializingNetworkBehaviour.IsOwner || !_initializingNetworkBehaviour.Owner.IsValid; + } + + /// + /// Updates OwnerDuringPreTick value and caches if needed. + /// + private void SetCaches(bool useOwnerSettings) + { + MovementSettings movementSettings = useOwnerSettings ? _controllerMovementSettings : _spectatorMovementSettings; + + _cachedSmoothedProperties = movementSettings.SmoothedProperties; + _cachedSnapNonSmoothedProperties = movementSettings.SnapNonSmoothedProperties; + _cachedAdaptiveInterpolationValue = movementSettings.AdaptiveInterpolationValue; + _cachedInterpolationValue = movementSettings.InterpolationValue; + + _cachedTeleportThreshold = movementSettings.EnableTeleport ? movementSettings.TeleportThreshold * movementSettings.TeleportThreshold : MoveRates.UNSET_VALUE; + } + + /// + /// Deinitializes this smoother resetting values. + /// + public void Deinitialize() + { + ResetState(); + IsInitialized = false; + } + + /// + /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. + /// + public void UpdateRealtimeInterpolation() + { + using (_pm_UpdateRealtimeInterpolation.Auto()) + { + /* If not networked, server is started, or if not + * using adaptive interpolation then use + * flat interpolation.*/ + if (!GetUseAdaptiveInterpolation()) + { + _realtimeInterpolation = _cachedInterpolationValue; + return; + } + + /* If here then adaptive interpolation is being calculated. */ + + TimeManager tm = _initializingTimeManager; + + //Calculate roughly what client state tick would be. + uint localTick = tm.LocalTick; + //This should never be the case; this is a precautionary against underflow. + if (localTick == TimeManager.UNSET_TICK) + return; + + //Ensure at least 1 tick. + long rttTime = tm.RoundTripTime; + uint rttTicks = tm.TimeToTicks(rttTime) + 1; + + uint clientStateTick = localTick - rttTicks; + float interpolation = localTick - clientStateTick; + + //Minimum interpolation is that of adaptive interpolation level. + interpolation += (byte)_cachedAdaptiveInterpolationValue; + + //Ensure interpolation is not more than a second. + if (interpolation > tm.TickRate) + interpolation = tm.TickRate; + else if (interpolation > byte.MaxValue) + interpolation = byte.MaxValue; + + /* Only update realtime interpolation if it changed more than 1 + * tick. This is to prevent excessive changing of interpolation value, which + * could result in noticeable speed ups/slow downs given movement multiplier + * may change when buffer is too full or short. */ + if (_realtimeInterpolation == 0 || Math.Abs(_realtimeInterpolation - interpolation) > 1) + _realtimeInterpolation = (byte)Math.Ceiling(interpolation); + } + } + + /// + /// This should be called when OnStartClient is invoked on the initializing NetworkBehaviour. + /// + /// This does not need to be called if there is no initializing NetworkBehaviour. + public void StartSmoother() + { + DetachOnStart(); + } + + /// + /// This should be called when OnStopClient is invoked on the initializing NetworkBehaviour. + /// + /// This does not need to be called if there is no initializing NetworkBehaviour. + internal void StopSmoother() + { + AttachOnStop(); + } + + /// + /// Called every frame. + /// + public void OnUpdate(float delta) + { + using (_pm_OnUpdate.Auto()) + { + if (!CanSmooth()) + return; + + MoveToTarget(delta); + } + } + + /// + /// Called when the TimeManager invokes OnPreTick. + /// + public void OnPreTick() + { + using (_pm_OnPreTick.Auto()) + { + if (!CanSmooth()) + return; + + SetCaches(GetUseOwnerSettings()); + + _preTicked = true; + DiscardExcessiveTransformPropertiesQueue(); + _graphicsPreTickWorldValues = _graphicalTransform.GetWorldProperties(); + _trackerPreTickWorldValues = GetTrackerWorldProperties(); + } + } + + /// + /// Called when the TimeManager invokes OnPostReplay. + /// + /// Replay tick for the local client. + /// This is dependent on the initializing NetworkBehaviour being set. + public void OnPostReplicateReplay(uint clientTick) + { + using (_pm_OnPostReplicateReplay.Auto()) + { + if (!NetworkObjectIsReconciling()) + return; + + if (_transformProperties.Count == 0) + return; + if (clientTick <= _teleportedTick) + return; + uint firstTick = _transformProperties.Peek().Tick; + //Already in motion to first entry, or first entry passed tick. + if (clientTick <= firstTick) + return; + + ModifyTransformProperties(clientTick, firstTick); + } + } + + /// + /// Called when TimeManager invokes OnPostTick. + /// + /// Local tick of the client. + public void OnPostTick(uint clientTick) + { + using (_pm_OnPostTick.Auto()) + { + if (!CanSmooth()) + return; + if (clientTick <= _teleportedTick) + return; + + //If preticked then previous transform values are known. + if (_preTicked) + { + var trackerProps = GetTrackerWorldProperties(); + //Only needs to be put to pretick position if not detached. + if (!_detachOnStart) + _graphicalTransform.SetWorldProperties(_graphicsPreTickWorldValues); + + DiscardExcessiveTransformPropertiesQueue(); + //SnapNonSmoothedProperties(); + AddTransformProperties(clientTick, trackerProps); + } + //If did not pretick then the only thing we can do is snap to instantiated values. + else + { + //Only set to position if not to detach. + if (!_detachOnStart) + _graphicalTransform.SetWorldProperties(GetTrackerWorldProperties()); + } + } + } + + /// + /// Snaps non-smoothed properties to original positoin if setting is enabled. + /// + private void SnapNonSmoothedProperties() + { + //Feature is not enabled. + if (!_cachedSnapNonSmoothedProperties) + return; + + TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; + + //Everything is smoothed. + if (smoothedProperties == TransformPropertiesFlag.Everything) + return; + + TransformProperties goalValeus = GetTrackerWorldProperties(); + + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Position)) + _graphicalTransform.position = goalValeus.Position; + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Rotation)) + _graphicalTransform.rotation = goalValeus.Rotation; + if (!smoothedProperties.FastContains(TransformPropertiesFlag.Scale)) + _graphicalTransform.localScale = goalValeus.Scale; + } + + /// + /// Returns if the initialized NetworkBehaviour's NetworkObject is reconcilling. + /// + private bool NetworkObjectIsReconciling() => _initializingNetworkBehaviour == null || _initializingNetworkBehaviour.NetworkObject.IsObjectReconciling; + + /// + /// Teleports the graphical to it's starting position and clears the internal movement queue. + /// + public void Teleport() + { + if (_initializingTimeManager == null) + return; + + //If using adaptive interpolation then set the tick which was teleported. + if (_controllerMovementSettings.AdaptiveInterpolationValue != AdaptiveInterpolationType.Off) + { + TimeManager tm = _initializingTimeManager == null ? InstanceFinder.TimeManager : _initializingTimeManager; + if (tm != null) + _teleportedTick = tm.LocalTick; + } + + ClearTransformPropertiesQueue(); + + _graphicalTransform.SetWorldProperties(_trackerTransform.GetWorldProperties()); + } + + /// + /// Clears the pending movement queue. + /// + private void ClearTransformPropertiesQueue() + { + using (_pm_ClearTPQ.Auto()) + { + _transformProperties.Clear(); + //Also unset move rates since there is no more queue. + _moveRates = new(MoveRates.UNSET_VALUE); + } + } + + /// + /// Discards datas over interpolation limit from movement queue. + /// + private void DiscardExcessiveTransformPropertiesQueue() + { + using (_pm_DiscardTPQ.Auto()) + { + int propertiesCount = _transformProperties.Count; + int dequeueCount = propertiesCount - (_realtimeInterpolation + MAXIMUM_QUEUED_OVER_INTERPOLATION); + + //If there are entries to dequeue. + if (dequeueCount > 0) + { + TickSmoothingManager.TickTransformProperties ttp = default; + for (int i = 0; i < dequeueCount; i++) + { + ttp = _transformProperties.Dequeue(); + } + + var nextValues = ttp.Properties; + SetMoveRates(nextValues); + } + } + } + + /// + /// Adds a new transform properties and sets move rates if needed. + /// + private void AddTransformProperties(uint tick, TransformProperties properties) + { + using (_pm_AddTP.Auto()) + { + TickSmoothingManager.TickTransformProperties ttp = new(tick, properties); + _transformProperties.Enqueue(ttp); + + //If first entry then set move rates. + if (_transformProperties.Count == 1) + { + TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties(); + SetMoveRates(gfxWorldProperties); + } + } + } + + /// + /// Modifies a transform property for a tick. This does not error check for empty collections. + /// + /// First tick in the queue. If 0 this will be looked up. + private void ModifyTransformProperties(uint clientTick, uint firstTick) + { + using (_pm_ModifyTP.Auto()) + { + int queueCount = _transformProperties.Count; + uint tick = clientTick; + /*Ticks will always be added incremental by 1 so it's safe to jump ahead the difference + * of tick and firstTick. */ + int index = (int)(tick - firstTick); + //Replace with new data. + if (index < queueCount) + { + if (tick != _transformProperties[index].Tick) + { + //Should not be possible. + } + else + { + TransformProperties newProperties = GetTrackerWorldProperties(); + /* Adjust transformProperties to ease into any corrections. + * The corrected value is used the more the index is to the end + * of the queue. */ + /* We want to be fully eased in by the last entry of the queue. */ + + int lastPossibleIndex = queueCount - 1; + int adjustedQueueCount = lastPossibleIndex - 1; + if (adjustedQueueCount < 1) + adjustedQueueCount = 1; + float easePercent = (float)index / adjustedQueueCount; + + //If easing. + if (easePercent < 1f) + { + if (easePercent < 1f) + easePercent = (float)Math.Pow(easePercent, adjustedQueueCount - index); + + TransformProperties oldProperties = _transformProperties[index].Properties; + newProperties.Position = Vector3.Lerp(oldProperties.Position, newProperties.Position, easePercent); + newProperties.Rotation = Quaternion.Lerp(oldProperties.Rotation, newProperties.Rotation, easePercent); + newProperties.Scale = Vector3.Lerp(oldProperties.Scale, newProperties.Scale, easePercent); + } + + _transformProperties[index] = new(tick, newProperties); + } + } + else + { + //This should never happen. + } + } + } + + /// + /// Gets properties of the tracker. + /// + private TransformProperties GetTrackerWorldProperties() + { + /* Return lossyScale if graphical is not attached. Otherwise, + * graphical should retain the tracker localScale so it changes + * with root. */ + + Vector3 scale = _detachOnStart ? _trackerTransform.lossyScale : _trackerTransform.localScale; + return new(_trackerTransform.position, _trackerTransform.rotation, scale); + } + + /// + /// Returns if prediction can be used on this rigidbody. + /// + /// + private bool CanSmooth() + { + //No graphical object is set. + if (_graphicalTransform == null) + return false; + + /* When this is the case the prediction networkTransform exist and is + * configured in a way to smooth the object, therefor this component should not be smoothing. */ + if (_predictionNetworkTransform != null && _predictionNetworkTransform.DoSettingsAllowSmoothing()) + return false; + + return _initializingTimeManager.NetworkManager.IsClientStarted; + } + + /// + /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. + /// + private void SetMoveRates(in TransformProperties prevValues) + { + using (_pm_SetMoveRates.Auto()) + { + if (_transformProperties.Count == 0) + { + _moveRates = new(MoveRates.UNSET_VALUE); + return; + } + + TransformProperties nextValues = _transformProperties.Peek().Properties; + + float duration = _tickDelta; + + _moveRates = MoveRates.GetMoveRates(prevValues, nextValues, duration, _cachedTeleportThreshold); + _moveRates.TimeRemaining = duration; + + SetMovementMultiplier(); + } + } + + private void SetMovementMultiplier() + { + if (_moveImmediately) + { + float percent = Mathf.InverseLerp(0, _realtimeInterpolation, _transformProperties.Count); + _movementMultiplier = percent; + + _movementMultiplier = Mathf.Clamp(_movementMultiplier, 0.5f, 1.05f); + } + //For the time being, not moving immediately uses these multiplier calculations. + else + { + /* If there's more in queue than interpolation then begin to move faster based on overage. + * Move 5% faster for every overage. */ + int overInterpolation = _transformProperties.Count - _realtimeInterpolation; + //If needs to be adjusted. + if (overInterpolation != 0) + { + _movementMultiplier += 0.015f * overInterpolation; + } + //If does not need to be adjusted. + else + { + //If interpolation is 1 then slow down just barely to accomodate for frame delta variance. + if (_realtimeInterpolation == 1) + _movementMultiplier = 1f; + } + + _movementMultiplier = Mathf.Clamp(_movementMultiplier, 0.95f, 1.05f); + } + } + + /// + /// Moves transform to target values. + /// + private void MoveToTarget(float delta) + { + using (_pm_MoveToTarget.Auto()) + { + int tpCount = _transformProperties.Count; + + //No data. + if (tpCount == 0) + return; + + if (_moveImmediately) + { + _isMoving = true; + } + else + { + //Enough in buffer to move. + if (tpCount >= _realtimeInterpolation) + { + _isMoving = true; + } + else if (!_isMoving) + { + return; + } + /* If buffer is considerably under goal then halt + * movement. This will allow the buffer to grow. */ + else if (tpCount - _realtimeInterpolation < -4) + { + _isMoving = false; + return; + } + } + + TickSmoothingManager.TickTransformProperties ttp = _transformProperties.Peek(); + + TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; + + _moveRates.Move(_graphicalTransform, ttp.Properties, smoothedProperties, delta * _movementMultiplier, useWorldSpace: true); + + float tRemaining = _moveRates.TimeRemaining; + //if TimeLeft is <= 0f then transform is at goal. Grab a new goal if possible. + if (tRemaining <= 0f) + { + //Dequeue current entry and if there's another call a move on it. + _transformProperties.Dequeue(); + + //If there are entries left then setup for the next. + if (_transformProperties.Count > 0) + { + SetMoveRates(ttp.Properties); + //If delta is negative then call move again with abs. + if (tRemaining < 0f) + MoveToTarget(Mathf.Abs(tRemaining)); + } + //No remaining, set to snap. + else + { + ClearTransformPropertiesQueue(); + } + } + } + } + + private void DetachOnStart() + { + if (!_detachOnStart) + return; + + TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties(); + _graphicalTransform.SetParent(null); + _graphicalTransform.SetWorldProperties(gfxWorldProperties); + } + + /// + /// Attachs to Target transform is possible. + /// + private void AttachOnStop() + { + //Never detached. + if (!_detachOnStart) + return; + //Graphical is null, nothing can be moved. + if (_graphicalTransform == null) + return; + if (ApplicationState.IsQuitting()) + return; + + /* If not to re-attach or if there's no target to reference + * then the graphical must be destroyed. */ + bool destroy = !_attachOnStop || _targetTransform == null; + //If not to re-attach then destroy graphical if needed. + if (destroy) + { + UnityEngine.Object.Destroy(_graphicalTransform.gameObject); + return; + } + + _graphicalTransform.SetParent(_targetTransform.parent); + _graphicalTransform.SetLocalProperties(_trackerTransform.GetLocalProperties()); + } + + public void ResetState() + { + if (!IsInitialized) + return; + + AttachOnStop(); + + _initializingNetworkBehaviour = null; + _initializingTimeManager = null; + _graphicalTransform = null; + _targetTransform = null; + + _teleportedTick = TimeManager.UNSET_TICK; + _movementMultiplier = 1f; + CollectionCaches.StoreAndDefault(ref _transformProperties); + _moveRates = default; + _preTicked = default; + _queuedTrackerProperties = null; + _trackerPreTickWorldValues = default; + _graphicsPreTickWorldValues = default; + _realtimeInterpolation = default; + _isMoving = default; + + _predictionNetworkTransform = null; + + if (_trackerTransform != null) + UnityEngine.Object.Destroy(_trackerTransform.gameObject); + } + + public void InitializeState() { } + } + #endif +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta new file mode 100644 index 00000000..9180bfaf --- /dev/null +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2fea8ea4886baae46875b4349f9389e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs index 76c83f7c..c7dda861 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs @@ -5,19 +5,53 @@ using FishNet.Object.Prediction; using FishNet.Utility.Extension; using GameKit.Dependencies.Utilities; -using UnityEngine; -using UnityEngine.Profiling; using Unity.Profiling; +using UnityEngine; using UnityEngine.Scripting; namespace FishNet.Component.Transforming.Beta { + #if !THREADED_TICKSMOOTHERS /// /// This class is under regular development and it's API may change at any time. /// public sealed class UniversalTickSmoother : IResettable { - #region Public. + #region Types. + [Preserve] + private struct TickTransformProperties + { + public readonly uint Tick; + public readonly TransformProperties Properties; + + public TickTransformProperties(uint tick, Transform t) + { + Tick = tick; + Properties = new(t.localPosition, t.localRotation, t.localScale); + } + + public TickTransformProperties(uint tick, Transform t, Vector3 localScale) + { + Tick = tick; + Properties = new(t.localPosition, t.localRotation, localScale); + } + + public TickTransformProperties(uint tick, TransformProperties tp) + { + Tick = tick; + Properties = tp; + } + + public TickTransformProperties(uint tick, TransformProperties tp, Vector3 localScale) + { + Tick = tick; + tp.Scale = localScale; + Properties = tp; + } + } + #endregion + + #region public. /// /// True if currently initialized. /// @@ -25,23 +59,6 @@ public sealed class UniversalTickSmoother : IResettable #endregion #region Private. - - #region Private Profiler Markers - - private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new ProfilerMarker("UniversalTickSmoother.UpdateRealtimeInterpolation()"); - private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("UniversalTickSmoother.OnUpdate(float)"); - private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("UniversalTickSmoother.OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new ProfilerMarker("UniversalTickSmoother.OnPostReplicateReplay(uint)"); - private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("UniversalTickSmoother.OnPostTick(uint)"); - private static readonly ProfilerMarker _pm_ClearTPQ = new ProfilerMarker("UniversalTickSmoother.ClearTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_DiscardTPQ = new ProfilerMarker("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_AddTP = new ProfilerMarker("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)"); - private static readonly ProfilerMarker _pm_ModifyTP = new ProfilerMarker("UniversalTickSmoother.ModifyTransformProperties(uint, uint)"); - private static readonly ProfilerMarker _pm_SetMoveRates = new ProfilerMarker("UniversalTickSmoother.SetMoveRates(in TransformProperties)"); - private static readonly ProfilerMarker _pm_MoveToTarget = new ProfilerMarker("UniversalTickSmoother.MoveToTarget(float)"); - - #endregion - /// /// How quickly to move towards goal values. /// @@ -123,12 +140,7 @@ public sealed class UniversalTickSmoother : IResettable /// /// TransformProperties to move towards. /// - private BasicQueue _transformProperties; - /// - /// True if to smooth using owner settings, false for spectator settings. - /// This is only used for performance gains. - /// - private bool _useOwnerSettings; + private BasicQueue _transformProperties; /// /// Last tick this was teleported on. /// @@ -149,6 +161,26 @@ public sealed class UniversalTickSmoother : IResettable /// True if moving has started and has not been stopped. /// private bool _isMoving; + /// + /// NetworkTransform used when prediction type is set to other. + /// + private NetworkTransform _predictionNetworkTransform; + #endregion + + #region Private Profiler Markers + // private static readonly ProfilerMarker _pm_ConsumeFixedOffset = new("UniversalTickSmoother.ConsumeFixedOffset(uint)"); + // private static readonly ProfilerMarker _pm_AxiswiseClamp = new("UniversalTickSmoother.AxiswiseClamp(TransformProperties, TransformProperties)"); + private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new("UniversalTickSmoother.UpdateRealtimeInterpolation()"); + private static readonly ProfilerMarker _pm_OnUpdate = new("UniversalTickSmoother.OnUpdate(float)"); + private static readonly ProfilerMarker _pm_OnPreTick = new("UniversalTickSmoother.OnPreTick()"); + private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new("UniversalTickSmoother.OnPostReplicateReplay(uint)"); + private static readonly ProfilerMarker _pm_OnPostTick = new("UniversalTickSmoother.OnPostTick(uint)"); + private static readonly ProfilerMarker _pm_ClearTPQ = new("UniversalTickSmoother.ClearTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_DiscardTPQ = new("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()"); + private static readonly ProfilerMarker _pm_AddTP = new("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)"); + private static readonly ProfilerMarker _pm_ModifyTP = new("UniversalTickSmoother.ModifyTransformProperties(uint, uint)"); + private static readonly ProfilerMarker _pm_SetMoveRates = new("UniversalTickSmoother.SetMoveRates(in TransformProperties)"); + private static readonly ProfilerMarker _pm_MoveToTarget = new("UniversalTickSmoother.MoveToTarget(float)"); #endregion #region Const. @@ -220,11 +252,11 @@ public TransformProperties GetGraphicalTrackerLocalProperties() /// Updates the smoothedProperties value. /// /// New value. - /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings - public void SetSmoothedProperties(TransformPropertiesFlag value, bool forOwnerOrOfflineSmoother) + /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings + public void SetSmoothedProperties(TransformPropertiesFlag value, bool forController) { _controllerMovementSettings.SmoothedProperties = value; - SetCaches(forOwnerOrOfflineSmoother); + SetCaches(forController); } /// @@ -264,7 +296,7 @@ public void SetAdaptiveInterpolation(AdaptiveInterpolationType value, bool forOw UpdateRealtimeInterpolation(); } - public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) + public void Initialize(InitializationSettings initializationSettings, MovementSettings controllerSettings, MovementSettings spectatorSettings) { ResetState(); @@ -273,9 +305,9 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe if (!TransformsAreValid(graphicalTransform, targetTransform)) return; - - _transformProperties = CollectionCaches.RetrieveBasicQueue(); - _controllerMovementSettings = ownerSettings; + + _transformProperties = CollectionCaches.RetrieveBasicQueue(); + _controllerMovementSettings = controllerSettings; _spectatorMovementSettings = spectatorSettings; /* Unset scale smoothing if not detaching. This is to prevent @@ -296,7 +328,20 @@ public void Initialize(InitializationSettings initializationSettings, MovementSe _detachOnStart = initializationSettings.DetachOnStart; _attachOnStop = initializationSettings.AttachOnStop; _moveImmediately = initializationSettings.MoveImmediately; - + + if (initializationSettings.FavorPredictionNetworkTransform && _initializingNetworkBehaviour != null) + { + NetworkObject networkObject = _initializingNetworkBehaviour.NetworkObject; + if (!networkObject.IsRigidbodyPredictionType) + _predictionNetworkTransform = networkObject.PredictionNetworkTransform; + else + _predictionNetworkTransform = null; + } + else + { + _predictionNetworkTransform = null; + } + SetCaches(GetUseOwnerSettings()); //Use set method as it has sanity checks. @@ -326,7 +371,7 @@ void SetupTrackerTransform() _trackerTransform.SetParent(trackerParent); } - _trackerTransform.SetWorldPositionRotationAndScale(_targetTransform.position, _targetTransform.rotation, graphicalTransform.localScale); + _trackerTransform.SetLocalPositionRotationAndScale(_graphicalTransform.localPosition, graphicalTransform.localRotation, graphicalTransform.localScale); } IsInitialized = true; @@ -374,19 +419,18 @@ private bool GetUseAdaptiveInterpolation() /// /// OwnerSettings can be used to read determine this as both owner and spectator settings will have the name InitializingNetworkBehaviour. /// - private bool GetUseOwnerSettings() => _initializingNetworkBehaviour == null || _initializingNetworkBehaviour.IsOwner || !_initializingNetworkBehaviour.Owner.IsValid; - - /// - /// Updates OwnerDuringPreTick value and caches if needed. - /// - private void SetUseOwnerSettings(bool value, bool force = false) + private bool GetUseOwnerSettings() { - if (value == _useOwnerSettings && !force) - return; + /* No networkBehaviour indicates an offline smoother. + * The offline smoothers use owner settings. */ + if (_initializingNetworkBehaviour == null) + return true; - _useOwnerSettings = value; + if (_initializingNetworkBehaviour.IsController) + return true; - SetCaches(value); + return false; + // return _initializingNetworkBehaviour.IsOwner || !_initializingNetworkBehaviour.Owner.IsValid; } /// @@ -506,7 +550,7 @@ public void OnPreTick() if (!CanSmooth()) return; - SetUseOwnerSettings(GetUseOwnerSettings()); + SetCaches(GetUseOwnerSettings()); _preTicked = true; DiscardExcessiveTransformPropertiesQueue(); @@ -556,14 +600,14 @@ public void OnPostTick(uint clientTick) //If preticked then previous transform values are known. if (_preTicked) { - var trackerProps = GetTrackerWorldProperties(); + DiscardExcessiveTransformPropertiesQueue(); + //Only needs to be put to pretick position if not detached. if (!_detachOnStart) _graphicalTransform.SetWorldProperties(_graphicsPreTickWorldValues); - DiscardExcessiveTransformPropertiesQueue(); //SnapNonSmoothedProperties(); - AddTransformProperties(clientTick, trackerProps); + AddTransformProperties(clientTick); } //If did not pretick then the only thing we can do is snap to instantiated values. else @@ -652,14 +696,11 @@ private void DiscardExcessiveTransformPropertiesQueue() //If there are entries to dequeue. if (dequeueCount > 0) { - TickSmoothingManager.TickTransformProperties ttp = default; + TickTransformProperties tpp = default; for (int i = 0; i < dequeueCount; i++) - { - ttp = _transformProperties.Dequeue(); - } + tpp = _transformProperties.Dequeue(); - var nextValues = ttp.Properties; - SetMoveRates(nextValues); + SetMoveRates(tpp.Properties); } } } @@ -667,12 +708,12 @@ private void DiscardExcessiveTransformPropertiesQueue() /// /// Adds a new transform properties and sets move rates if needed. /// - private void AddTransformProperties(uint tick, TransformProperties properties) + private void AddTransformProperties(uint tick) { using (_pm_AddTP.Auto()) { - TickSmoothingManager.TickTransformProperties ttp = new(tick, properties); - _transformProperties.Enqueue(ttp); + TickTransformProperties tpp = new(tick, GetTrackerWorldProperties()); + _transformProperties.Enqueue(tpp); //If first entry then set move rates. if (_transformProperties.Count == 1) @@ -762,6 +803,11 @@ private bool CanSmooth() if (_graphicalTransform == null) return false; + /* When this is the case the prediction networkTransform exist and is + * configured in a way to smooth the object, therefor this component should not be smoothing. */ + if (_predictionNetworkTransform != null && _predictionNetworkTransform.DoSettingsAllowSmoothing()) + return false; + return _initializingTimeManager.NetworkManager.IsClientStarted; } @@ -858,7 +904,7 @@ private void MoveToTarget(float delta) } } - TickSmoothingManager.TickTransformProperties ttp = _transformProperties.Peek(); + TickTransformProperties ttp = _transformProperties.Peek(); TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; @@ -940,7 +986,7 @@ public void ResetState() _teleportedTick = TimeManager.UNSET_TICK; _movementMultiplier = 1f; - CollectionCaches.StoreAndDefault(ref _transformProperties); + CollectionCaches.StoreAndDefault(ref _transformProperties); _moveRates = default; _preTicked = default; _queuedTrackerProperties = null; @@ -948,11 +994,13 @@ public void ResetState() _graphicsPreTickWorldValues = default; _realtimeInterpolation = default; _isMoving = default; - + _predictionNetworkTransform = null; + if (_trackerTransform != null) UnityEngine.Object.Destroy(_trackerTransform.gameObject); } public void InitializeState() { } } + #endif } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Managing/NetworkManager.cs b/Assets/FishNet/Runtime/Managing/NetworkManager.cs index e31e2767..6bac0da0 100644 --- a/Assets/FishNet/Runtime/Managing/NetworkManager.cs +++ b/Assets/FishNet/Runtime/Managing/NetworkManager.cs @@ -121,10 +121,12 @@ public static IReadOnlyList Instances /// ObserverManager for this NetworkManager. /// public ObserverManager ObserverManager { get; private set; } + #if THREADED_TICKSMOOTHERS /// /// TickSmoothingManager for this NetworkManager. /// public TickSmoothingManager TickSmoothingManager { get; private set; } + #endif /// /// DebugManager for this NetworkManager. /// @@ -323,7 +325,9 @@ private void Awake() TimeManager = GetOrCreateComponent(); SceneManager = GetOrCreateComponent(); ObserverManager = GetOrCreateComponent(); + #if THREADED_TICKSMOOTHERS TickSmoothingManager = GetOrCreateComponent(); + #endif RollbackManager = GetOrCreateComponent(); PredictionManager = GetOrCreateComponent(); StatisticsManager = GetOrCreateComponent(); @@ -368,7 +372,9 @@ private void InitializeComponents() SceneManager.InitializeOnce_Internal(this); ObserverManager.InitializeOnce_Internal(this); + #if THREADED_TICKSMOOTHERS TickSmoothingManager.InitializeOnce_Internal(this); + #endif RollbackManager.InitializeOnce_Internal(this); PredictionManager.InitializeOnce(this); StatisticsManager.InitializeOnce_Internal(this); diff --git a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs index 58c37480..bd4f623f 100644 --- a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs +++ b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs @@ -62,7 +62,7 @@ private enum UpdateOrder : byte /// public event Action OnPreTick; /// - /// Called when a tick occurs. + /// Called after PreTick and before OnPostTick, most similar to FixedUpdate. This is commonly where you run replicate or other network. /// public event Action OnTick; /// @@ -290,6 +290,7 @@ public void SetPhysicsTimeScale(float value) #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)"); @@ -743,14 +744,9 @@ private void IncreaseTick() if (PhysicsMode == PhysicsMode.TimeManager && tickDelta > 0f) { - using (_pm_OnPrePhysicsSimulation.Auto()) - OnPrePhysicsSimulation?.Invoke(tickDelta); - using (_pm_PhysicsSimulate.Auto()) - Physics.Simulate(tickDelta); - using (_pm_Physics2DSimulate.Auto()) - Physics2D.Simulate(tickDelta); - using (_pm_OnPostPhysicsSimulation.Auto()) - OnPostPhysicsSimulation?.Invoke(tickDelta); + InvokeOnSimulation(preSimulation: true, tickDelta); + SimulatePhysics(tickDelta); + InvokeOnSimulation(preSimulation: false, tickDelta); } using (_pm_OnPostTick.Auto()) @@ -1089,33 +1085,61 @@ public uint LocalTickToTick(uint localTick) #endregion /// - /// Tries to iterate incoming or outgoing data. + /// Invokes OnPreSimulation or OnPostSimulation. /// - /// True to iterate incoming. - private void TryIterateData(bool incoming) + internal void InvokeOnSimulation(bool preSimulation, float delta) { - if (incoming) + if (preSimulation) { - /* 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); + using (_pm_OnPrePhysicsSimulation.Auto()) + OnPrePhysicsSimulation?.Invoke(delta); } else { - NetworkManager.TransportManager.IterateOutgoing(asServer: true); - NetworkManager.TransportManager.IterateOutgoing(asServer: false); + using (_pm_OnPostPhysicsSimulation.Auto()) + OnPostPhysicsSimulation?.Invoke(delta); + } + } + + internal void SimulatePhysics(float delta) + { + using (_pm_PhysicsSimulate.Auto()) + Physics.Simulate(delta); + using (_pm_Physics2DSimulate.Auto()) + Physics2D.Simulate(delta); + } + + /// + /// Tries to iterate incoming or outgoing data. + /// + /// True to iterate incoming. + private void TryIterateData(bool incoming) + { + using (_pm_TryIterateData.Auto()) + { + 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); + } } } From ad91b3d7c25c4c28868f5fa6025c9cef5f6878c5 Mon Sep 17 00:00:00 2001 From: belplaton Date: Wed, 21 Jan 2026 14:23:06 +0300 Subject: [PATCH 20/23] feat: profiing, events, qol, etc --- .../NetworkTransform/NetworkTransform.cs | 5 +- .../Runtime/Managing/Client/ClientManager.cs | 487 ++++++++++-------- .../Client/Object/ClientObjects.RpcLinks.cs | 98 ++-- .../Managing/Client/Object/ClientObjects.cs | 399 +++++++------- .../Runtime/Managing/Object/ManagedObjects.cs | 40 +- .../Runtime/Managing/Server/ServerManager.cs | 146 +++--- .../Runtime/Managing/Timing/TimeManager.cs | 51 +- .../Managing/Transporting/TransportManager.cs | 236 +++++---- .../NetworkBehaviour/NetworkBehaviour.cs | 3 +- .../NetworkObject/NetworkObject.Callbacks.cs | 133 ++++- .../Object/NetworkObject/NetworkObject.QOL.cs | 56 +- .../Runtime/Serializing/WriterExtensions.cs | 2 +- 12 files changed, 1004 insertions(+), 652 deletions(-) diff --git a/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs b/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs index f6e6a0a0..f9789010 100644 --- a/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs +++ b/Assets/FishNet/Runtime/Generated/Component/NetworkTransform/NetworkTransform.cs @@ -1744,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 @@ -1752,7 +1752,7 @@ private void MoveToTarget(float delta) if (!HasChanged(td)) _currentGoalData = null; OnInterpolationComplete?.Invoke(); - } + } } } } @@ -2098,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. diff --git a/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs b/Assets/FishNet/Runtime/Managing/Client/ClientManager.cs index 72aa3910..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. @@ -149,6 +148,34 @@ public void SetFrameRate(ushort value) #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 9435fbca..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); @@ -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/Object/ManagedObjects.cs b/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs index 2051ee58..f8af2dad 100644 --- a/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs +++ b/Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs @@ -17,6 +17,7 @@ using FishNet.Managing.Statistic; using UnityEngine; using UnityEngine.SceneManagement; +using Unity.Profiling; namespace FishNet.Managing.Object { @@ -39,6 +40,11 @@ public abstract partial class ManagedObjects #endregion + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_ParseReplicateRpc = + new("ManagedObjects.ParseReplicateRpc(PooledReader, NetworkConnection, Channel)"); + #endregion + #region Protected. /// /// Returns the next ObjectId to use. @@ -497,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 @@ -541,5 +552,4 @@ protected void CheckReadSceneObjectDetails(Reader r, ref string sceneName, ref s } #endif } - } diff --git a/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs b/Assets/FishNet/Runtime/Managing/Server/ServerManager.cs index 1ff887a4..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. @@ -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 b1740cdd..bd4f623f 100644 --- a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs +++ b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs @@ -6,11 +6,8 @@ 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; @@ -293,6 +290,7 @@ public void SetPhysicsTimeScale(float value) #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)"); @@ -1110,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) - { - /* 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 + using (_pm_TryIterateData.Auto()) { - 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 8f947191..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 @@ -124,6 +125,11 @@ public LatencySimulator LatencySimulator /// [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.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs index cb5d3ca0..44867ace 100644 --- a/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs +++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs @@ -1,7 +1,6 @@ #if UNITY_EDITOR || DEVELOPMENT_BUILD #define DEVELOPMENT #endif -using System; using FishNet.CodeGenerating; using FishNet.Documenting; using FishNet.Managing.Transporting; @@ -77,7 +76,7 @@ public byte ComponentIndex #if !UNITY_SERVER /// /// - [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; + private NetworkTrafficStatistics _networkTrafficStatistics; /// /// Name of this NetworkBehaviour. /// diff --git a/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs b/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs index 6d6d0f4e..f8a12645 100644 --- a/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs +++ b/Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs @@ -1,7 +1,9 @@ using FishNet.Connection; using System.Runtime.CompilerServices; +using System; using FishNet.Serializing; using UnityEngine; +using Unity.Profiling; namespace FishNet.Object { @@ -10,6 +12,8 @@ public partial class NetworkObject : MonoBehaviour #region Types public delegate void NetworkObjectCallback(NetworkObject nb); + + public delegate void NetworkObjectInvokeCallback(NetworkObject nb, bool asServer, bool invokeSyncTypeCallbacks); #endregion @@ -22,31 +26,82 @@ public partial class NetworkObject : MonoBehaviour /// True if OnStartClient was called. /// private bool _onStartClientCalled; + /// + /// True if OnStartSyncTypeCallbacks was called. + /// + private bool _onStartSyncTypeCallbacksCalled; - private bool OnStartServerCalled + /// + /// True if OnStartServer was called. + /// + public bool OnStartServerCalled { get => _onStartServerCalled; - set + private set { if (_onStartServerCalled != value) { _onStartServerCalled = value; - if (value) OnStartServerEvent?.Invoke(this); - else OnStopServerEvent?.Invoke(this); + if (value) + { + using (_pm_OnStartServerEvent.Auto()) + OnStartServerEvent?.Invoke(this); + } + else + { + using (_pm_OnStopServerEvent.Auto()) + OnStopServerEvent?.Invoke(this); + } } } } - private bool OnStartClientCalled + /// + /// True if OnStartClient was called. + /// + public bool OnStartClientCalled { get => _onStartClientCalled; - set + private set { if (_onStartClientCalled != value) { _onStartClientCalled = value; - if (value) OnStartClientEvent?.Invoke(this); - else OnStopClientEvent?.Invoke(this); + 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); + } } } } @@ -55,6 +110,48 @@ private bool OnStartClientCalled 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 @@ -64,6 +161,9 @@ private bool OnStartClientCalled /// 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. */ @@ -96,9 +196,15 @@ private void InvokeStartCallbacks(bool asServer, bool invokeSyncTypeCallbacks) } if (invokeSyncTypeCallbacks) + { InvokeOnStartSyncTypeCallbacks(true); + OnStartSyncTypeCallbacksCalled = true; + } InvokeStartCallbacks_Prediction(asServer); + + using (_pm_PostInvokeStartCallbacksEvent.Auto()) + PostInvokeStartCallbacks?.Invoke(this, asServer, invokeSyncTypeCallbacks); } /// @@ -149,10 +255,16 @@ 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) { @@ -178,6 +290,9 @@ internal void InvokeStopCallbacks(bool asServer, bool invokeSyncTypeCallbacks) OnStartClientCalled = false; } + + using (_pm_PostInvokeStopCallbacksEvent.Auto()) + PostInvokeStopCallbacks?.Invoke(this, asServer, invokeSyncTypeCallbacks); void InvokeOnNetwork() { @@ -219,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/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); From af2cc3506881e5b5003ec1e73ed21a2ab2c876a1 Mon Sep 17 00:00:00 2001 From: waterb <143908720+belplaton@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:09:22 +0300 Subject: [PATCH 21/23] fix --- .../Generated/Component/TickSmoothing/TickSmoothingManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs index 6a4523ae..67e54403 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs @@ -955,7 +955,8 @@ private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serve clientTick = clientTick, teleportedTick = _teleportedTick.AsArray(), objectReconcilingMask = _objectReconcilingMask.AsArray(), - + + transformProperties = _transformProperties, modifyTransformPropertiesPayloads = _modifyTransformPropertiesPayloads.AsArray() }; From 3f28097ec23573ecc017ccd57407e4fdbf0622ee Mon Sep 17 00:00:00 2001 From: FirstGearGames Date: Fri, 23 Jan 2026 11:59:44 -0500 Subject: [PATCH 22/23] Revert "Make tick smoothers multithreaded" --- .../Unity.FishNet.CodeGen.asmdef | 5 +- Assets/FishNet/Runtime/FishNet.Runtime.asmdef | 4 +- .../Editor/MovementSettingsDrawer.Threaded.cs | 53 - .../MovementSettingsDrawer.Threaded.cs.meta | 11 - .../Editor/MovementSettingsDrawer.cs | 4 - .../Editor/NetworkTickSmootherEditor.cs | 3 +- .../MovementSettings.Threaded.cs | 60 - .../MovementSettings.Threaded.cs.meta | 11 - .../TickSmoothing/MovementSettings.cs | 2 - .../TickSmoothing/NetworkTickSmoother.cs | 3 +- .../TickSmootherController.Threaded.cs | 332 ---- .../TickSmootherController.Threaded.cs.meta | 11 - .../TickSmoothing/TickSmootherController.cs | 2 - .../TickSmoothingManager.Types.cs | 1317 ---------------- .../TickSmoothingManager.Types.cs.meta | 11 - .../TickSmoothing/TickSmoothingManager.cs | 1359 ----------------- .../TickSmoothingManager.cs.meta | 11 - .../UniversalTickSmoother.Threaded.cs | 983 ------------ .../UniversalTickSmoother.Threaded.cs.meta | 11 - .../TickSmoothing/UniversalTickSmoother.cs | 2 - .../Runtime/Managing/NetworkManager.cs | 13 - .../Runtime/Managing/Timing/TimeManager.cs | 225 ++- .../Runtime/Object/Prediction/MoveRates.cs | 322 +--- .../Runtime/Object/TransformProperties.cs | 100 +- .../Dependencies/GameKit.Dependencies.asmdef | 5 +- .../Dependencies/Utilities/Dictionaries.cs | 10 +- .../GameKit/Dependencies/Utilities/Floats.cs | 9 - .../Dependencies/Utilities/Quaternions.cs | 47 +- .../Utilities/Types/StripedRingQueue.cs | 370 ----- .../GameKit/Dependencies/Utilities/Vectors.cs | 28 - .../Runtime/Utility/Extension/Transforms.cs | 332 +--- 31 files changed, 258 insertions(+), 5398 deletions(-) delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs delete mode 100644 Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta delete mode 100644 Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs diff --git a/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef b/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef index 22a5b611..9aa9b97a 100644 --- a/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef +++ b/Assets/FishNet/CodeGenerating/Unity.FishNet.CodeGen.asmdef @@ -4,10 +4,7 @@ "references": [ "FishNet.Runtime", "FishNet.Codegen.Cecil", - "GameKit.Dependencies", - "Unity.Burst", - "Unity.Mathematics", - "Unity.Collections" + "GameKit.Dependencies" ], "includePlatforms": [ "Editor" diff --git a/Assets/FishNet/Runtime/FishNet.Runtime.asmdef b/Assets/FishNet/Runtime/FishNet.Runtime.asmdef index b2c5f6a9..dc41020c 100644 --- a/Assets/FishNet/Runtime/FishNet.Runtime.asmdef +++ b/Assets/FishNet/Runtime/FishNet.Runtime.asmdef @@ -4,9 +4,7 @@ "references": [ "GUID:894a6cc6ed5cd2645bb542978cbed6a9", "GUID:1d82bdf40e2465b44b34adf79595e74c", - "GUID:d8b63aba1907145bea998dd612889d6b", - "GUID:2665a8d13d1b3f18800f46e256720795", - "GUID:e0cd26848372d4e5c891c569017e11f1" + "GUID:d8b63aba1907145bea998dd612889d6b" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs deleted file mode 100644 index bac3154c..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs +++ /dev/null @@ -1,53 +0,0 @@ -#if UNITY_EDITOR -using FishNet.Object; -using GameKit.Dependencies.Utilities; -using UnityEditor; -using UnityEngine; - -namespace FishNet.Component.Transforming.Beta.Editing -{ - #if THREADED_TICKSMOOTHERS - [CustomPropertyDrawer(typeof(MovementSettings))] - public class MovementSettingsDrawer : PropertyDrawer - { - private PropertyDrawerTool _propertyDrawer; - - public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) - { - EditorGUI.BeginProperty(position, label, property); - - _propertyDrawer = new(position); - - // _propertyDrawer.DrawLabel(label, FontStyle.Bold); - - EditorGUI.indentLevel++; - - SerializedProperty enableTeleport = property.FindPropertyRelative("EnableTeleport"); - SerializedProperty teleportThreshold = property.FindPropertyRelative("TeleportThreshold"); - SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue"); - SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue"); - SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties"); - SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties"); - - _propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport"); - if (enableTeleport.boolValue == true) - _propertyDrawer.DrawProperty(teleportThreshold, "Teleport Threshold", indent: 1); - - _propertyDrawer.DrawProperty(adaptiveInterpolationValue, "Adaptive Interpolation"); - if ((AdaptiveInterpolationType)adaptiveInterpolationValue.intValue == AdaptiveInterpolationType.Off) - _propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1); - - _propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties"); - if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything) - _propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1); - - _propertyDrawer.SetIndentToStarting(); - - EditorGUI.EndProperty(); - } - - public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight(); - } - #endif -} -#endif \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta deleted file mode 100644 index cb9b31bd..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8d3d0f9278ca7424f964926dc8a70515 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs index 0298f005..d6bcf4df 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs @@ -6,7 +6,6 @@ namespace FishNet.Component.Transforming.Beta.Editing { - #if !THREADED_TICKSMOOTHERS [CustomPropertyDrawer(typeof(MovementSettings))] public class MovementSettingsDrawer : PropertyDrawer { @@ -27,7 +26,6 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue"); SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue"); SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties"); - SerializedProperty useLocalSpace = property.FindPropertyRelative("UseLocalSpace"); SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties"); _propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport"); @@ -39,7 +37,6 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten _propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1); _propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties"); - _propertyDrawer.DrawProperty(useLocalSpace, "Use Local Space"); if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything) _propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1); @@ -50,6 +47,5 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight(); } - #endif } #endif \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs index 74aa282a..533e6ae8 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs @@ -31,7 +31,7 @@ public override void OnInspectorGUI() EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((NetworkTickSmoother)target), typeof(NetworkTickSmoother), false); GUI.enabled = true; - EditorGUILayout.LabelField("Initialization Settings", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(_favorPredictionNetworkTransform); EditorGUILayout.PropertyField(_initializationSettings); @@ -45,6 +45,7 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(_spectatorMovementSettings); } + // EditorGUI.indentLevel--; serializedObject.ApplyModifiedProperties(); diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs deleted file mode 100644 index c3abffd7..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs +++ /dev/null @@ -1,60 +0,0 @@ -using FishNet.Object; -using UnityEngine; - -namespace FishNet.Component.Transforming.Beta -{ - #if THREADED_TICKSMOOTHERS - [System.Serializable] - public struct MovementSettings - { - /// - /// True to enable teleport threshold. - /// - [Tooltip("True to enable teleport threshold.")] - public bool EnableTeleport; - /// - /// How far the object must move between ticks to teleport rather than smooth. - /// - [Tooltip("How far the object must move between ticks to teleport rather than smooth.")] - [Range(0f, ushort.MaxValue)] - public float TeleportThreshold; - /// - /// Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. - /// In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects. - /// - [Tooltip("Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.")] - public AdaptiveInterpolationType AdaptiveInterpolationValue; - /// - /// Number of ticks to smooth over when not using adaptive interpolation. - /// - [Tooltip("Number of ticks to smooth over when not using adaptive interpolation.")] - public byte InterpolationValue; - /// - /// Properties to smooth. Any value not selected will become offset with every movement. - /// - [Tooltip("Properties to smooth. Any value not selected will become offset with every movement.")] - public TransformPropertiesFlag SmoothedProperties; - /// - /// True to apply smoothing in local space for position and rotation. False to use world space. - /// - [Tooltip("True to apply smoothing in local space for position and rotation. False to use world space.")] - public bool UseLocalSpace; - /// - /// True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick. - /// - [Tooltip("True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.")] - public bool SnapNonSmoothedProperties; - - public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersOnStructsAlready) - { - EnableTeleport = false; - TeleportThreshold = 0f; - AdaptiveInterpolationValue = AdaptiveInterpolationType.Off; - InterpolationValue = 2; - SmoothedProperties = TransformPropertiesFlag.Everything; - UseLocalSpace = false; - SnapNonSmoothedProperties = false; - } - } - #endif -} diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta deleted file mode 100644 index 24da146e..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 99cd0db221a6bd94288e6cc02aabca8f -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs index e033d7cb..f543473f 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs @@ -3,7 +3,6 @@ namespace FishNet.Component.Transforming.Beta { - #if !THREADED_TICKSMOOTHERS [System.Serializable] public struct MovementSettings { @@ -50,5 +49,4 @@ public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersO SnapNonSmoothedProperties = false; } } - #endif } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs index 2e199113..1f6cb705 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs @@ -1,6 +1,7 @@ using FishNet.Object; using GameKit.Dependencies.Utilities; using UnityEngine; +using UnityEngine.Serialization; namespace FishNet.Component.Transforming.Beta { @@ -52,7 +53,7 @@ private void OnDestroy() public override void OnStartClient() { RetrieveControllers(); - + _initializationSettings.SetNetworkedRuntimeValues(initializingNetworkBehaviour: this, graphicalTransform: transform, _favorPredictionNetworkTransform); SmootherController.Initialize(_initializationSettings, _controllerMovementSettings, _spectatorMovementSettings); diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs deleted file mode 100644 index 9acc4914..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs +++ /dev/null @@ -1,332 +0,0 @@ -using FishNet.Managing.Timing; -using FishNet.Object; -using GameKit.Dependencies.Utilities; -using UnityEngine; -using Unity.Profiling; - -namespace FishNet.Component.Transforming.Beta -{ - #if THREADED_TICKSMOOTHERS - /// - /// Smoothes this object between ticks. - /// - /// This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction. - public class TickSmootherController : IResettable - { - #region Public. - // /// - // /// Logic for owner smoothing. - // /// - // public UniversalTickSmoother UniversalSmoother { get; private set; } - #endregion - - #region Private. - - #region Private Profiler Markers - - private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("TickSmootherController.TimeManager_OnUpdate()"); - private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("TickSmootherController.TimeManager_OnPostTick()"); - - #endregion - - /// - /// - private InitializationSettings _initializationSettings = new(); - /// - /// - private MovementSettings _ownerMovementSettings = new(); - /// - /// - private MovementSettings _spectatorMovementSettings = new(); - /// - /// True if OnDestroy has been called. - /// - private bool _destroyed; - /// - /// Cached timeManager reference. - /// - private TimeManager _timeManager; - /// - /// NetworkBehaviour which initialized this object. Value may be null when initialized for an Offline smoother. - /// - private NetworkBehaviour _initializingNetworkBehaviour; - /// - /// TickSmoothingManager. - /// - private TickSmoothingManager _tickSmoothingManager; - /// - /// Transform which initialized this object. - /// - private Transform _graphicalTransform; - /// - /// True if initialized with a null NetworkBehaviour. - /// - private bool _initializedOffline; - /// - /// True if subscribed to events used for adaptiveInterpolation. - /// - private bool _subscribedToAdaptiveEvents; - /// - /// True if currently subscribed to events. - /// - private bool _subscribed; - /// - /// True if initialized. - /// - private bool _isInitialized; - #endregion - - public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) - { - _tickSmoothingManager = - initializationSettings.InitializingNetworkBehaviour?.NetworkManager.TickSmoothingManager ?? - InstanceFinder.NetworkManager.TickSmoothingManager; - _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; - _graphicalTransform = initializationSettings.GraphicalTransform; - - _initializationSettings = initializationSettings; - _ownerMovementSettings = ownerSettings; - _spectatorMovementSettings = spectatorSettings; - - _initializedOffline = initializationSettings.InitializingNetworkBehaviour == null; - - _isInitialized = true; - } - - public void OnDestroy() - { - var tsm = _tickSmoothingManager; - if (tsm != null) - tsm.Unregister(this); - - // ChangeSubscriptions(false); - // StoreSmoother(); - _destroyed = true; - _isInitialized = false; - } - - public void StartSmoother() - { - if (!_isInitialized) - return; - - bool canStart = _initializedOffline ? StartOffline() : StartOnline(); - - if (!canStart) - return; - - // RetrieveSmoothers(); - // - // UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); - // - // UniversalSmoother.StartSmoother(); - - var tsm = _tickSmoothingManager; - if (tsm != null) - tsm.Register(this, _initializationSettings, _ownerMovementSettings, _spectatorMovementSettings); - - bool StartOnline() - { - NetworkBehaviour nb = _initializingNetworkBehaviour; - - SetTimeManager(nb.TimeManager); - - return true; - } - - bool StartOffline() - { - if (_timeManager == null) - return false; - - return true; - } - } - - public void StopSmoother() - { - var tsm = _tickSmoothingManager; - if (tsm != null) - tsm.Unregister(this); - - if (!_initializedOffline) - StopOnline(); - - // if (UniversalSmoother != null) - // UniversalSmoother.StopSmoother(); - - void StopOnline() - { - SetTimeManager(tm: null); - } - - // Intentionally left blank. - // void StopOffline() { } - } - - // public void TimeManager_OnUpdate() - // { - // using (_pm_OnUpdate.Auto()) - // { - // UniversalSmoother.OnUpdate(Time.deltaTime); - // } - // } - // - // public void TimeManager_OnPreTick() - // { - // using (_pm_OnPreTick.Auto()) - // { - // UniversalSmoother.OnPreTick(); - // } - // } - // - // /// - // /// Called after a tick completes. - // /// - // public void TimeManager_OnPostTick() - // { - // using (_pm_OnPostTick.Auto()) - // { - // if (_timeManager != null) - // UniversalSmoother.OnPostTick(_timeManager.LocalTick); - // } - // } - // - // private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) - // { - // UniversalSmoother.OnPostReplicateReplay(clientTick); - // } - // - // private void TimeManager_OnRoundTripTimeUpdated(long rttMs) - // { - // UniversalSmoother.UpdateRealtimeInterpolation(); - // } - // - // /// - // /// Stores smoothers if they have value. - // /// - // private void StoreSmoother() - // { - // if (UniversalSmoother == null) - // return; - // - // ResettableObjectCaches.Store(UniversalSmoother); - // UniversalSmoother = null; - // } - // - // /// - // /// Stores current smoothers and retrieves new ones. - // /// - // private void RetrieveSmoothers() - // { - // StoreSmoother(); - // UniversalSmoother = ResettableObjectCaches.Retrieve(); - // } - - // /// - // /// Sets a target transform to follow. - // /// - // public void SetTargetTransform(Transform value) - // { - // Transform currentTargetTransform = _initializationSettings.TargetTransform; - // - // if (value == currentTargetTransform) - // return; - // - // bool clientStartCalled = (_initializedOffline && _timeManager != null) || (_initializingNetworkBehaviour != null && _initializingNetworkBehaviour.OnStartClientCalled); - // - // bool previousTargetTransformIsValid = (currentTargetTransform != null); - // - // // If target is different and old is not null then reset. - // if (previousTargetTransformIsValid && clientStartCalled) - // OnStopClient(); - // - // _initializationSettings.TargetTransform = value; - // if (previousTargetTransformIsValid && clientStartCalled) - // OnStartClient(); - // } - - /// - /// Sets a new PredictionManager to use. - /// - public void SetTimeManager(TimeManager tm) - { - if (tm == _timeManager) - return; - - // Unsub from current. - // ChangeSubscriptions(false); - //Sub to newest. - _timeManager = tm; - // ChangeSubscriptions(true); - } - - - // /// - // /// Changes the subscription to the TimeManager. - // /// - // private void ChangeSubscriptions(bool subscribe) - // { - // if (_destroyed) - // return; - // TimeManager tm = _timeManager; - // if (tm == null) - // return; - // - // if (subscribe == _subscribed) - // return; - // _subscribed = subscribe; - // - // bool adaptiveIsOff = _ownerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off; - // - // if (subscribe) - // { - // tm.OnUpdate += TimeManager_OnUpdate; - // tm.OnPreTick += TimeManager_OnPreTick; - // tm.OnPostTick += TimeManager_OnPostTick; - // - // if (!adaptiveIsOff) - // { - // tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; - // PredictionManager pm = tm.NetworkManager.PredictionManager; - // pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; - // _subscribedToAdaptiveEvents = true; - // } - // } - // else - // { - // tm.OnUpdate -= TimeManager_OnUpdate; - // tm.OnPreTick -= TimeManager_OnPreTick; - // tm.OnPostTick -= TimeManager_OnPostTick; - // - // if (_subscribedToAdaptiveEvents) - // { - // tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; - // PredictionManager pm = tm.NetworkManager.PredictionManager; - // pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; - // } - // } - // } - - public void ResetState() - { - _initializationSettings = default; - _ownerMovementSettings = default; - _spectatorMovementSettings = default; - - _destroyed = false; - _timeManager = null; - _initializingNetworkBehaviour = null; - _graphicalTransform = null; - - _subscribed = false; - _subscribedToAdaptiveEvents = false; - - _isInitialized = false; - } - - public void InitializeState() { } - } - #endif -} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta deleted file mode 100644 index d6c4d59d..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c757af1b1b164aa4c9d239dd72e15b93 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs index 2544587d..ae83ced4 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs @@ -7,7 +7,6 @@ namespace FishNet.Component.Transforming.Beta { - #if !THREADED_TICKSMOOTHERS /// /// Smoothes this object between ticks. /// @@ -305,5 +304,4 @@ public void ResetState() public void InitializeState() { } } - #endif } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs deleted file mode 100644 index 3be6e893..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs +++ /dev/null @@ -1,1317 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using FishNet.Managing.Timing; -using FishNet.Object; -using FishNet.Object.Prediction; -using FishNet.Utility.Extension; -using GameKit.Dependencies.Utilities.Types; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; -using UnityEngine; -using UnityEngine.Jobs; -using UnityEngine.Scripting; - -namespace FishNet.Component.Transforming.Beta -{ - #if THREADED_TICKSMOOTHERS - public partial class TickSmoothingManager - { - #region Types. - [Preserve] - public struct TickTransformProperties - { - public readonly uint Tick; - public readonly TransformProperties Properties; - - public TickTransformProperties(uint tick, TransformProperties properties) - { - Tick = tick; - Properties = properties; - } - } - [Preserve] - private struct NullableTransformProperties - { - public readonly byte IsExist; - public readonly TransformProperties Properties; - - public NullableTransformProperties(bool isExist, TransformProperties properties) - { - IsExist = (byte)(isExist ? 1 : 0); - Properties = properties; - } - } - [Preserve] - private struct MoveToTargetPayload - { - public byte executeMask; - public float delta; - - public MoveToTargetPayload(byte executeMask, float delta) - { - this.executeMask = executeMask; - this.delta = delta; - } - } - [Preserve] - private struct UpdateRealtimeInterpolationPayload - { - public byte executeMask; - - public UpdateRealtimeInterpolationPayload(byte executeMask) - { - this.executeMask = executeMask; - } - } - [Preserve] - private struct DiscardExcessiveTransformPropertiesQueuePayload - { - public byte executeMask; - - public DiscardExcessiveTransformPropertiesQueuePayload(byte executeMask) - { - this.executeMask = executeMask; - } - } - [Preserve] - private struct SetMoveRatesPayload - { - public byte executeMask; - public TransformProperties prevValues; - - public SetMoveRatesPayload(byte executeMask, TransformProperties prevValues) - { - this.executeMask = executeMask; - this.prevValues = prevValues; - } - } - [Preserve] - private struct SetMovementMultiplierPayload - { - public byte executeMask; - - public SetMovementMultiplierPayload(byte executeMask) - { - this.executeMask = executeMask; - } - } - [Preserve] - private struct AddTransformPropertiesPayload - { - public byte executeMask; - public TickTransformProperties tickTransformProperties; - - public AddTransformPropertiesPayload(byte executeMask, TickTransformProperties tickTransformProperties) - { - this.executeMask = executeMask; - this.tickTransformProperties = tickTransformProperties; - } - } - [Preserve] - private struct ClearTransformPropertiesQueuePayload - { - public byte executeMask; - - public ClearTransformPropertiesQueuePayload(byte executeMask) - { - this.executeMask = executeMask; - } - } - [Preserve] - private struct ModifyTransformPropertiesPayload - { - public byte executeMask; - public uint clientTick; - public uint firstTick; - - public ModifyTransformPropertiesPayload(byte executeMask, uint clientTick, uint firstTick) - { - this.executeMask = executeMask; - this.clientTick = clientTick; - this.firstTick = firstTick; - } - } - [Preserve] - private struct SnapNonSmoothedPropertiesPayload - { - public byte executeMask; - public TransformProperties goalValues; - - public SnapNonSmoothedPropertiesPayload(byte executeMask, TransformProperties goalValues) - { - this.executeMask = executeMask; - this.goalValues = goalValues; - } - } - [Preserve] - private struct TeleportPayload - { - public byte executeMask; - - public TeleportPayload(byte executeMask) - { - this.executeMask = executeMask; - } - } - #endregion - - #region PreTick. - [BurstCompile] - private struct PreTickMarkJob : IJobParallelFor - { - [ReadOnly] public NativeArray canSmoothMask; - [WriteOnly] public NativeArray preTickedMask; - - [WriteOnly] public NativeArray discardExcessivePayloads; - - public void Execute(int index) - { - discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(0); - - if (canSmoothMask[index] == 0) - return; - - preTickedMask[index] = 1; - discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(1); - } - } - - [BurstCompile] - private struct PreTickCaptureGraphicalJob : IJobParallelForTransform - { - [ReadOnly] public NativeArray canSmoothMask; - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - [WriteOnly] public NativeArray graphicSnapshot; - - public void Execute(int index, TransformAccess graphicalTransform) - { - if (canSmoothMask[index] == 0) - return; - - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - - bool useLocalSpace = settings.UseLocalSpace; - graphicSnapshot[index] = GetTransformProperties(graphicalTransform, useLocalSpace); - } - } - #endregion - - #region PostTick - - [BurstCompile] - private struct PostTickCaptureTrackerJob : IJobParallelForTransform - { - [ReadOnly] public NativeArray canSmoothMask; - [ReadOnly] public NativeArray detachOnStartMask; - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - [ReadOnly] public NativeArray targetSnapshot; - [WriteOnly] public NativeArray trackerSnapshot; - - public void Execute(int index, TransformAccess trackerTransform) - { - if (canSmoothMask[index] == 0) - return; - - bool isDetach = detachOnStartMask[index] != 0; - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - - bool useLocalSpace = settings.UseLocalSpace; - TransformProperties trackerProperties = GetTrackerTransformProperties(trackerTransform, isDetach, useLocalSpace); - if (useLocalSpace) trackerProperties += targetSnapshot[index]; - trackerSnapshot[index] = trackerProperties; - } - } - [BurstCompile] - private struct PostTickJob : IJobParallelForTransform - { - [ReadOnly] public uint clientTick; - [ReadOnly] public NativeArray canSmoothMask; - [ReadOnly] public NativeArray teleportedTick; - [ReadOnly] public NativeArray preTickedMask; - [ReadOnly] public NativeArray detachOnStartMask; - [ReadOnly] public NativeArray postTickTrackerSnapshot; - [ReadOnly] public NativeArray preTickGraphicSnapshot; - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - - [WriteOnly] public NativeArray discardExcessivePayloads; - [WriteOnly] public NativeArray snapNonSmoothedPropertiesPayloads; - [WriteOnly] public NativeArray addTransformPropertiesPayloads; - - public void Execute(int index, TransformAccess graphicalTransform) - { - discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(0); - addTransformPropertiesPayloads[index] = new AddTransformPropertiesPayload(0, default); - - if (canSmoothMask[index] == 0) - return; - - if (clientTick <= teleportedTick[index]) - return; - - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - - bool useLocalSpace = settings.UseLocalSpace; - TransformProperties trackerProps = postTickTrackerSnapshot[index]; - //If preticked then previous transform values are known. - if (preTickedMask[index] != 0) - { - //Only needs to be put to pretick position if not detached. - if (detachOnStartMask[index] == 0) - { - var graphicProps = preTickGraphicSnapshot[index]; - SetTransformProperties(graphicalTransform, graphicProps, useLocalSpace); - } - - TickTransformProperties tickTrackerProps = new TickTransformProperties(clientTick, trackerProps); - discardExcessivePayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(1); - snapNonSmoothedPropertiesPayloads[index] = new SnapNonSmoothedPropertiesPayload(0, trackerProps); - addTransformPropertiesPayloads[index] = new AddTransformPropertiesPayload(1, tickTrackerProps); - } - //If did not pretick then the only thing we can do is snap to instantiated values. - else - { - //Only set to position if not to detach. - if (detachOnStartMask[index] == 0) - { - SetTransformProperties(graphicalTransform, trackerProps, useLocalSpace); - } - } - } - } - #endregion - - #region PostReplicateReplay - [BurstCompile] - private struct PostReplicateReplayJob : IJobParallelFor - { - [ReadOnly] public uint clientTick; - [ReadOnly] public NativeArray teleportedTick; - [ReadOnly] public NativeArray objectReconcilingMask; - - public StripedRingQueue transformProperties; - [WriteOnly] public NativeArray modifyTransformPropertiesPayloads; - - public void Execute(int index) - { - modifyTransformPropertiesPayloads[index] = new ModifyTransformPropertiesPayload(0, default, default); - if (objectReconcilingMask[index] == 0) - return; - - if (transformProperties.GetCount(index) == 0) - return; - if (clientTick <= teleportedTick[index]) - return; - uint firstTick = transformProperties.Peek(index).Tick; - //Already in motion to first entry, or first entry passed tick. - if (clientTick <= firstTick) - return; - - modifyTransformPropertiesPayloads[index] = new ModifyTransformPropertiesPayload(1, clientTick, firstTick); - } - } - #endregion - - #region RoundTripTimeUpdated - [BurstCompile] - private struct RoundTripTimeUpdatedJob : IJobParallelFor - { - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - - [WriteOnly] public NativeArray updateRealtimeInterpolationPayloads; - - public void Execute(int index) - { - updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(0); - - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - - // Update RTT only if Adaptive Interpolation is not Off - if (GetUseAdaptiveInterpolation(settings)) - updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(1); - } - - private static bool GetUseAdaptiveInterpolation(in MovementSettings settings) - { - if (settings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off) - return false; - - return true; - } - } - #endregion - - #region Update - [BurstCompile] - private struct UpdateJob : IJobParallelFor - { - [ReadOnly] public NativeArray canSmoothMask; - [ReadOnly] public float deltaTime; - - [WriteOnly] public NativeArray moveToTargetPayloads; - - public void Execute(int index) - { - moveToTargetPayloads[index] = new MoveToTargetPayload(0, default); - - if (canSmoothMask[index] == 0) - return; - - moveToTargetPayloads[index] = new MoveToTargetPayload(1, deltaTime); - } - } - #endregion - - #region Methods. - [BurstCompile] - private struct CaptureLocalTargetJob : IJobParallelForTransform - { - [ReadOnly] public NativeArray canSmoothMask; - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - [WriteOnly] public NativeArray targetSnapshot; - - public void Execute(int index, TransformAccess targetTransform) - { - if (canSmoothMask[index] == 0) - return; - - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - - bool useLocalSpace = settings.UseLocalSpace; - if (!useLocalSpace) return; - - targetSnapshot[index] = GetTransformProperties(targetTransform, true); - } - } - - [BurstCompile] - private struct MoveToTargetJob : IJobParallelForTransform - { - public NativeArray jobPayloads; - - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - - [ReadOnly] public NativeArray realTimeInterpolations; - [ReadOnly] public NativeArray moveImmediatelyMask; - [ReadOnly] public float tickDelta; - - public NativeArray isMoving; - public NativeArray movementMultipliers; - - public StripedRingQueue transformProperties; - public NativeArray moveRates; - - public NativeArray setMoveRatesPayloads; - public NativeArray setMovementMultiplierPayloads; - public NativeArray clearTransformPropertiesQueuePayloads; - - public void Execute(int index, TransformAccess graphicalTransform) - { - MoveToTarget( - index, - graphicalTransform, - ref jobPayloads, - ref useOwnerSettingsMask, - ref ownerSettings, - ref spectatorSettings, - ref realTimeInterpolations, - ref moveImmediatelyMask, - tickDelta, - ref isMoving, - ref movementMultipliers, - ref transformProperties, - ref moveRates, - ref setMoveRatesPayloads, - ref setMovementMultiplierPayloads, - ref clearTransformPropertiesQueuePayloads); - } - - /// - /// Moves transform to target values. - /// - public static void MoveToTarget( - int index, - TransformAccess graphicalTransform, - ref NativeArray jobPayloads, - ref NativeArray useOwnerSettingsMask, - ref NativeArray ownerSettings, - ref NativeArray spectatorSettings, - ref NativeArray realTimeInterpolations, - ref NativeArray moveImmediatelyMask, - float tickDelta, - ref NativeArray isMoving, - ref NativeArray movementMultipliers, - ref StripedRingQueue transformProperties, - ref NativeArray moveRates, - ref NativeArray setMoveRatesPayloads, - ref NativeArray setMovementMultiplierPayloads, - ref NativeArray clearTransformPropertiesQueuePayloads) - { - MoveToTargetPayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0) - return; - jobPayloads[index] = new MoveToTargetPayload(0, default); - - // We only need the delta once, then clear payload for this frame. - float remainingDelta = jobPayload.delta; - - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - bool useLocalSpace = settings.UseLocalSpace; - - while (remainingDelta > 0f) - { - int tpCount = transformProperties.GetCount(index); - - // No data in queue. - if (tpCount == 0) - return; - - byte realtimeInterpolation = realTimeInterpolations[index]; - if (moveImmediatelyMask[index] != 0) - { - isMoving[index] = 1; - } - else - { - //Enough in buffer to move. - if (tpCount >= realtimeInterpolation) - { - isMoving[index] = 1; - } - else if (isMoving[index] == 0) - { - return; - } - /* If buffer is considerably under goal then halt - * movement. This will allow the buffer to grow. */ - else if (tpCount - realtimeInterpolation < -4) - { - isMoving[index] = 0; - return; - } - } - - TickTransformProperties ttp = transformProperties.Peek(index); - TransformPropertiesFlag smoothedProperties = settings.SmoothedProperties; - - MoveRates moveRatesValue = moveRates[index]; - float movementMultiplier = movementMultipliers[index]; - - moveRatesValue.Move( - graphicalTransform, - ttp.Properties, - smoothedProperties, - remainingDelta * movementMultiplier, - useWorldSpace: !useLocalSpace); - moveRates[index] = moveRatesValue; - - float tRemaining = moveRatesValue.TimeRemaining; - - //if TimeRemaining is <= 0f then transform is at goal. Grab a new goal if possible. - if (tRemaining <= 0f) - { - //Dequeue current entry and if there's another call a move on it. - transformProperties.TryDequeue(index, out _); - - //If there are entries left then setup for the next. - if (transformProperties.GetCount(index) > 0) - { - setMoveRatesPayloads[index] = new SetMoveRatesPayload(1, ttp.Properties); - SetMoveRatesJob.SetMoveRates( - index, - ref setMoveRatesPayloads, - ref useOwnerSettingsMask, - ref ownerSettings, - ref spectatorSettings, - ref transformProperties, - tickDelta, - ref moveRates, - ref setMovementMultiplierPayloads); - - SetMovementMultiplierJob.SetMovementMultiplier( - index, - ref setMovementMultiplierPayloads, - ref transformProperties, - ref realTimeInterpolations, - ref moveImmediatelyMask, - ref movementMultipliers); - - // If there is leftover time, apply it to the next segment in this loop. - if (tRemaining < 0f) - { - remainingDelta = Mathf.Abs(tRemaining); - continue; - } - } - //No remaining, set to snap. - else - { - ClearTransformPropertiesQueueJob.ClearTransformPropertiesQueue( - index, - ref clearTransformPropertiesQueuePayloads, - ref transformProperties, - ref moveRates); - } - } - - // Either we did not finish, or there is no leftover time to consume. - break; - } - } - } - - [BurstCompile] - private struct UpdateRealtimeInterpolationJob : IJobParallelFor - { - public NativeArray jobPayloads; - - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - - [ReadOnly] public float tickDelta; - [ReadOnly] public ushort tickRate; - [ReadOnly] public uint localTick; - [ReadOnly] public long rtt; - [ReadOnly] public bool isServerOnlyStarted; - - public NativeArray realTimeInterpolations; - - public void Execute(int index) - { - UpdateRealtimeInterpolation( - index, - ref jobPayloads, - ref useOwnerSettingsMask, - ref ownerSettings, - ref spectatorSettings, - tickDelta, - tickRate, - localTick, - rtt, - isServerOnlyStarted, - ref realTimeInterpolations); - } - - /// - /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. - /// - public static void UpdateRealtimeInterpolation( - int index, - ref NativeArray jobPayloads, - ref NativeArray useOwnerSettingsMask, - ref NativeArray ownerSettings, - ref NativeArray spectatorSettings, - float tickDelta, - ushort tickRate, - uint localTick, - long rtt, - bool isServerOnlyStarted, - ref NativeArray realTimeInterpolations - ) - { - UpdateRealtimeInterpolationPayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0) - return; - jobPayloads[index] = new UpdateRealtimeInterpolationPayload(0); - - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - - /* If not networked, server is started, or if not - * using adaptive interpolation then use - * flat interpolation.*/ - if (!GetUseAdaptiveInterpolation(settings, isServerOnlyStarted)) - { - realTimeInterpolations[index] = settings.InterpolationValue; - return; - } - - /* If here then adaptive interpolation is being calculated. */ - - //Calculate roughly what client state tick would be. - //This should never be the case; this is a precautionary against underflow. - if (localTick == TimeManager.UNSET_TICK) - return; - - //Ensure at least 1 tick. - uint rttTicks = TimeManager.TimeToTicks(rtt, tickDelta) + 1; - - uint clientStateTick = localTick - rttTicks; - float interpolation = localTick - clientStateTick; - - //Minimum interpolation is that of adaptive interpolation level. - interpolation += (byte)settings.AdaptiveInterpolationValue; - - //Ensure interpolation is not more than a second. - if (interpolation > tickRate) - interpolation = tickRate; - else if (interpolation > byte.MaxValue) - interpolation = byte.MaxValue; - - /* Only update realtime interpolation if it changed more than 1 - * tick. This is to prevent excessive changing of interpolation value, which - * could result in noticeable speed ups/slow downs given movement multiplier - * may change when buffer is too full or short. */ - float realtimeInterpolation = realTimeInterpolations[index]; - if (realtimeInterpolation == 0 || math.abs(realtimeInterpolation - interpolation) > 1) - realTimeInterpolations[index] = (byte)math.ceil(interpolation); - } - - private static bool GetUseAdaptiveInterpolation(in MovementSettings settings, in bool isServerOnlyStarted) - { - if (settings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off || isServerOnlyStarted) - return false; - - return true; - } - } - - [BurstCompile] - private struct DiscardExcessiveTransformPropertiesQueueJob : IJobParallelFor - { - public NativeArray jobPayloads; - - [ReadOnly] public NativeArray realTimeInterpolations; - [ReadOnly] public int requiredQueuedOverInterpolation; - - public StripedRingQueue transformProperties; - [WriteOnly] public NativeArray setMoveRatesPayloads; - - public void Execute(int index) - { - DiscardExcessiveTransformPropertiesQueue( - index, - ref jobPayloads, - ref realTimeInterpolations, - requiredQueuedOverInterpolation, - ref transformProperties, - ref setMoveRatesPayloads); - } - - /// - /// Discards datas over interpolation limit from movement queue. - /// - public static void DiscardExcessiveTransformPropertiesQueue( - int index, - ref NativeArray jobPayloads, - ref NativeArray realTimeInterpolations, - int requiredQueuedOverInterpolation, - ref StripedRingQueue transformProperties, - ref NativeArray setMoveRatesPayloads) - { - setMoveRatesPayloads[index] = new SetMoveRatesPayload(0, default); - - DiscardExcessiveTransformPropertiesQueuePayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0) - return; - jobPayloads[index] = new DiscardExcessiveTransformPropertiesQueuePayload(0); - - int propertiesCount = transformProperties.GetCount(index); - int realtimeInterpolationValue = realTimeInterpolations[index]; - int dequeueCount = propertiesCount - (realtimeInterpolationValue + requiredQueuedOverInterpolation); - - // If there are entries to dequeue. - if (dequeueCount > 0) - { - TickTransformProperties ttp; - transformProperties.DequeueUpTo(index, dequeueCount, out ttp); - - var nextValues = ttp.Properties; - setMoveRatesPayloads[index] = new SetMoveRatesPayload(1, nextValues); - } - } - } - - [BurstCompile] - private struct SetMoveRatesJob : IJobParallelFor - { - public NativeArray jobPayloads; - - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - - public StripedRingQueue transformProperties; - [ReadOnly] public float tickDelta; - - [WriteOnly] public NativeArray moveRates; - [WriteOnly] public NativeArray setMovementMultiplierPayloads; - - public void Execute(int index) - { - SetMoveRates( - index, - ref jobPayloads, - ref useOwnerSettingsMask, - ref ownerSettings, - ref spectatorSettings, - ref transformProperties, - tickDelta, - ref moveRates, - ref setMovementMultiplierPayloads); - } - - /// - /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. - /// - public static void SetMoveRates( - int index, - ref NativeArray jobPayloads, - ref NativeArray useOwnerSettingsMask, - ref NativeArray ownerSettings, - ref NativeArray spectatorSettings, - ref StripedRingQueue transformProperties, - float tickDelta, - ref NativeArray moveRates, - ref NativeArray setMovementMultiplierPayloads) - { - moveRates[index] = new(MoveRates.UNSET_VALUE); - setMovementMultiplierPayloads[index] = new SetMovementMultiplierPayload(0); - - SetMoveRatesPayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0 || transformProperties.GetCount(index) == 0) - return; - jobPayloads[index] = new SetMoveRatesPayload(0, default); - - TransformProperties prevValues = jobPayload.prevValues; - TransformProperties nextValues = transformProperties.Peek(index).Properties; - float duration = tickDelta; - - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - float teleportThreshold = settings.EnableTeleport - ? settings.TeleportThreshold * settings.TeleportThreshold - : MoveRates.UNSET_VALUE; - - MoveRates moveRatesValue = MoveRates.GetMoveRates(prevValues, nextValues, duration, teleportThreshold); - moveRatesValue.TimeRemaining = duration; - moveRates[index] = moveRatesValue; - setMovementMultiplierPayloads[index] = new SetMovementMultiplierPayload(1); - } - } - - [BurstCompile] - private struct SetMovementMultiplierJob : IJobParallelFor - { - public NativeArray jobPayloads; - - public StripedRingQueue transformProperties; - [ReadOnly] public NativeArray realTimeInterpolations; - [ReadOnly] public NativeArray moveImmediatelyMask; - - public NativeArray movementMultipliers; - - public void Execute(int index) - { - SetMovementMultiplier( - index, - ref jobPayloads, - ref transformProperties, - ref realTimeInterpolations, - ref moveImmediatelyMask, - ref movementMultipliers); - } - - public static void SetMovementMultiplier( - int index, - ref NativeArray jobPayloads, - ref StripedRingQueue transformProperties, - ref NativeArray realTimeInterpolations, - ref NativeArray moveImmediatelyMask, - ref NativeArray movementMultipliers) - { - SetMovementMultiplierPayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0) - return; - jobPayloads[index] = new SetMovementMultiplierPayload(0); - - byte moveImmediately = moveImmediatelyMask[index]; - byte realTimeInterpolation = realTimeInterpolations[index]; - float movementMultiplier = movementMultipliers[index]; - int propertiesCount = transformProperties.GetCount(index); - if (moveImmediately != 0) - { - float percent = math.unlerp(0, realTimeInterpolation, propertiesCount); - movementMultiplier = percent; - - movementMultiplier = math.clamp(movementMultiplier, 0.5f, 1.05f); - } - // For the time being, not moving immediately uses these multiplier calculations. - else - { - /* If there's more in queue than interpolation then begin to move faster based on overage. - * Move 5% faster for every overage. */ - int overInterpolation = propertiesCount - realTimeInterpolation; - // If needs to be adjusted. - if (overInterpolation != 0) - { - movementMultiplier += 0.015f * overInterpolation; - } - // If does not need to be adjusted. - else - { - // If interpolation is 1 then slow down just barely to accomodate for frame delta variance. - if (realTimeInterpolation == 1) - movementMultiplier = 1f; - } - - movementMultiplier = math.clamp(movementMultiplier, 0.95f, 1.05f); - } - - movementMultipliers[index] = movementMultiplier; - } - } - - [BurstCompile] - private struct AddTransformPropertiesJob : IJobParallelForTransform - { - public NativeArray jobPayloads; - - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - public StripedRingQueue transformProperties; - [WriteOnly] public NativeArray setMoveRatesPayloads; - - public void Execute(int index, TransformAccess graphicalTransform) - { - AddTransformProperties( - index, - graphicalTransform, - ref jobPayloads, - ref useOwnerSettingsMask, - ref ownerSettings, - ref spectatorSettings, - ref transformProperties, - ref setMoveRatesPayloads); - } - - /// - /// Adds a new transform properties and sets move rates if needed. - /// - public static void AddTransformProperties( - int index, - TransformAccess graphicalTransform, - ref NativeArray jobPayloads, - ref NativeArray useOwnerSettingsMask, - ref NativeArray ownerSettings, - ref NativeArray spectatorSettings, - ref StripedRingQueue transformProperties, - ref NativeArray setMoveRatesPayloads) - { - setMoveRatesPayloads[index] = new SetMoveRatesPayload(0, default); - - AddTransformPropertiesPayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0) - return; - jobPayloads[index] = new AddTransformPropertiesPayload(0, default); - - transformProperties.Enqueue(index, jobPayload.tickTransformProperties); - - //If first entry then set move rates. - if (transformProperties.GetCount(index) == 1) - { - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - - bool useLocalSpace = settings.UseLocalSpace; - TransformProperties gfxProperties = GetTransformProperties(graphicalTransform, useLocalSpace); - setMoveRatesPayloads[index] = new SetMoveRatesPayload(1, gfxProperties); - } - } - } - - [BurstCompile] - private struct ClearTransformPropertiesQueueJob : IJobParallelFor - { - public NativeArray jobPayloads; - public StripedRingQueue transformProperties; - [WriteOnly] public NativeArray moveRates; - - public void Execute(int index) - { - ClearTransformPropertiesQueue( - index, - ref jobPayloads, - ref transformProperties, - ref moveRates); - } - - /// - /// Clears the pending movement queue. - /// - public static void ClearTransformPropertiesQueue( - int index, - ref NativeArray jobPayloads, - ref StripedRingQueue transformProperties, - ref NativeArray moveRates) - { - ClearTransformPropertiesQueuePayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0) - return; - jobPayloads[index] = new ClearTransformPropertiesQueuePayload(0); - - transformProperties.Clear(index); - //Also unset move rates since there is no more queue. - moveRates[index] = new MoveRates(MoveRates.UNSET_VALUE); - } - } - - [BurstCompile] - private struct ModifyTransformPropertiesJob : IJobParallelForTransform - { - public NativeArray jobPayloads; - [ReadOnly] public NativeArray detachOnStartMask; - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - [ReadOnly] public NativeArray targetSnapshot; - - public StripedRingQueue transformProperties; - - public void Execute(int index, TransformAccess trackerTransform) - { - ModifyTransformProperties( - index, - trackerTransform, - ref jobPayloads, - ref detachOnStartMask, - ref useOwnerSettingsMask, - ref ownerSettings, - ref spectatorSettings, - ref targetSnapshot, - ref transformProperties); - } - - /// - /// Modifies a transform property for a tick. This does not error check for empty collections. - /// firstTick - First tick in the queue. If 0 this will be looked up. - /// - public static void ModifyTransformProperties( - int index, - TransformAccess trackerTransform, - ref NativeArray jobPayloads, - ref NativeArray detachOnStartMask, - ref NativeArray useOwnerSettingsMask, - ref NativeArray ownerSettings, - ref NativeArray spectatorSettings, - ref NativeArray targetSnapshot, - ref StripedRingQueue transformProperties) - { - ModifyTransformPropertiesPayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0) - return; - jobPayloads[index] = new ModifyTransformPropertiesPayload(0, default, default); - - int queueCount = transformProperties.GetCount(index); - uint tick = jobPayload.clientTick; - /*Ticks will always be added incremental by 1 so it's safe to jump ahead the difference - * of tick and firstTick. */ - int tickIndex = (int)(tick - jobPayload.firstTick); - //Replace with new data. - if (tickIndex < queueCount) - { - TickTransformProperties tickTransformProperties = transformProperties[index, tickIndex]; - if (tick != tickTransformProperties.Tick) - { - //Should not be possible. - } - else - { - bool isDetach = detachOnStartMask[index] != 0; - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - - bool useLocalSpace = settings.UseLocalSpace; - TransformProperties newProperties = GetTrackerTransformProperties(trackerTransform, isDetach, useLocalSpace); - if (useLocalSpace) newProperties += targetSnapshot[index]; - /* Adjust transformProperties to ease into any corrections. - * The corrected value is used the more the index is to the end - * of the queue. */ - /* We want to be fully eased in by the last entry of the queue. */ - - int lastPossibleIndex = queueCount - 1; - int adjustedQueueCount = lastPossibleIndex - 1; - if (adjustedQueueCount < 1) - adjustedQueueCount = 1; - float easePercent = (float)tickIndex / adjustedQueueCount; - - //If easing. - if (easePercent < 1f) - { - if (easePercent < 1f) - easePercent = (float)Math.Pow(easePercent, adjustedQueueCount - tickIndex); - - TransformProperties oldProperties = tickTransformProperties.Properties; - newProperties.Position = Vector3.Lerp(oldProperties.Position, newProperties.Position, easePercent); - newProperties.Rotation = Quaternion.Lerp(oldProperties.Rotation, newProperties.Rotation, easePercent); - newProperties.Scale = Vector3.Lerp(oldProperties.Scale, newProperties.Scale, easePercent); - } - - transformProperties[index, tickIndex] = new TickTransformProperties(tick, newProperties); - } - } - else - { - //This should never happen. - } - } - } - - [BurstCompile] - private struct SnapNonSmoothedPropertiesJob : IJobParallelForTransform - { - public NativeArray jobPayloads; - - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - - public void Execute(int index, TransformAccess graphicalTransform) - { - SnapNonSmoothedProperties( - index, - graphicalTransform, - ref jobPayloads, - ref useOwnerSettingsMask, - ref ownerSettings, - ref spectatorSettings); - } - - /// - /// Snaps non-smoothed properties to original positoin if setting is enabled. - /// - public static void SnapNonSmoothedProperties( - int index, - TransformAccess graphicalTransform, - ref NativeArray jobPayloads, - ref NativeArray useOwnerSettingsMask, - ref NativeArray ownerSettings, - ref NativeArray spectatorSettings) - { - SnapNonSmoothedPropertiesPayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0) - return; - jobPayloads[index] = new SnapNonSmoothedPropertiesPayload(0, default); - - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - bool useLocalSpace = settings.UseLocalSpace; - - bool snapNonSmoothedProperties = settings.SnapNonSmoothedProperties; - //Feature is not enabled. - if (!snapNonSmoothedProperties) - return; - - TransformPropertiesFlag smoothedProperties = settings.SmoothedProperties; - - //Everything is smoothed. - if (smoothedProperties == TransformPropertiesFlag.Everything) - return; - - TransformProperties goalValues = jobPayload.goalValues; - - if (!smoothedProperties.FastContains(TransformPropertiesFlag.Position)) - { - if (useLocalSpace) - graphicalTransform.localPosition = goalValues.Position; - else - graphicalTransform.position = goalValues.Position; - } - - if (!smoothedProperties.FastContains(TransformPropertiesFlag.Rotation)) - { - if (useLocalSpace) - graphicalTransform.localRotation = goalValues.Rotation; - else - graphicalTransform.rotation = goalValues.Rotation; - } - - if (!smoothedProperties.FastContains(TransformPropertiesFlag.Scale)) - graphicalTransform.localScale = goalValues.Scale; - } - } - - [BurstCompile] - private struct TeleportJob : IJobParallelForTransform - { - public NativeArray jobPayloads; - - [ReadOnly] public NativeArray useOwnerSettingsMask; - [ReadOnly] public NativeArray ownerSettings; - [ReadOnly] public NativeArray spectatorSettings; - [ReadOnly] public NativeArray preTickTrackerSnapshot; - [ReadOnly] public uint localTick; - - public StripedRingQueue transformProperties; - public NativeArray clearTransformPropertiesQueuePayloads; - [WriteOnly] public NativeArray moveRates; - [WriteOnly] public NativeArray teleportedTick; - - public void Execute(int index, TransformAccess graphicalTransform) - { - Teleport( - index, - graphicalTransform, - ref jobPayloads, - ref useOwnerSettingsMask, - ref ownerSettings, - ref spectatorSettings, - ref preTickTrackerSnapshot, - localTick, - ref transformProperties, - ref clearTransformPropertiesQueuePayloads, - ref moveRates, - ref teleportedTick); - } - - /// - /// Snaps non-smoothed properties to original positoin if setting is enabled. - /// - public static void Teleport( - int index, - TransformAccess graphicalTransform, - ref NativeArray jobPayloads, - ref NativeArray useOwnerSettingsMask, - ref NativeArray ownerSettings, - ref NativeArray spectatorSettings, - ref NativeArray preTickTrackerSnapshot, - uint localTick, - ref StripedRingQueue transformProperties, - ref NativeArray clearTransformPropertiesQueuePayloads, - ref NativeArray moveRates, - ref NativeArray teleportedTick) - { - TeleportPayload jobPayload = jobPayloads[index]; - if (jobPayload.executeMask == 0) - return; - jobPayloads[index] = new TeleportPayload(0); - - byte isOwner = useOwnerSettingsMask[index]; - MovementSettings settings = isOwner != 0 ? ownerSettings[index] : spectatorSettings[index]; - bool useLocalSpace = settings.UseLocalSpace; - - AdaptiveInterpolationType adaptiveInterpolationValue = settings.AdaptiveInterpolationValue; - - //If using adaptive interpolation then set the tick which was teleported. - if (adaptiveInterpolationValue != AdaptiveInterpolationType.Off) - teleportedTick[index] = localTick; - - clearTransformPropertiesQueuePayloads[index] = new ClearTransformPropertiesQueuePayload(1); - ClearTransformPropertiesQueueJob.ClearTransformPropertiesQueue( - index, - ref clearTransformPropertiesQueuePayloads, - ref transformProperties, - ref moveRates); - - TransformProperties trackerProps = preTickTrackerSnapshot[index]; - SetTransformProperties(graphicalTransform, trackerProps, useLocalSpace); - } - } - - /// - /// Gets properties for the graphical transform in the desired space. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TransformProperties GetTransformProperties(TransformAccess transform, bool useLocalSpace) - { - if (useLocalSpace) - return transform.GetLocalProperties(); - else - return transform.GetWorldProperties(); - } - - /// - /// Gets properties for the tracker transform in the desired space, accounting for detach. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TransformProperties GetTrackerTransformProperties(TransformAccess trackerTransform, bool isDetach, bool useLocalSpace) - { - /* Return lossyScale if graphical is not attached. Otherwise, - * graphical should retain the tracker localScale so it changes - * with root. */ - - Vector3 scale = isDetach ? ExtractLossyScale(trackerTransform) : trackerTransform.localScale; - Vector3 pos; - Quaternion rot; - - if (useLocalSpace) - trackerTransform.GetCorrectLocalPositionAndRotation(out pos, out rot); - else - trackerTransform.GetPositionAndRotation(out pos, out rot); - - return new TransformProperties(pos, rot, scale); - } - - /// - /// Applies properties to a transform using the desired space for position/rotation. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetTransformProperties(TransformAccess transform, in TransformProperties properties, bool useLocalSpace) - { - if (useLocalSpace) - transform.SetLocalProperties(properties); - else - transform.SetWorldProperties(properties); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector3 ExtractLossyScale(TransformAccess transform) - { - var m = transform.localToWorldMatrix; - var c0 = new float3(m.m00, m.m10, m.m20); - var c1 = new float3(m.m01, m.m11, m.m21); - var c2 = new float3(m.m02, m.m12, m.m22); - return new Vector3(math.length(c0), math.length(c1), math.length(c2)); - } - - #endregion - - - public static TransformProperties GetGraphicalWorldProperties(TransformAccess graphicalTransform) - { - return graphicalTransform.GetWorldProperties(); - } - - public static TransformProperties GetTrackerWorldProperties(TransformAccess trackerTransform, bool isDetach) - { - /* Return lossyScale if graphical is not attached. Otherwise, - * graphical should retain the tracker localScale so it changes - * with root. */ - - trackerTransform.GetPositionAndRotation(out var pos, out var rot); - Vector3 scl; - if (isDetach) - { - var m = trackerTransform.localToWorldMatrix; - var c0 = new float3(m.m00, m.m10, m.m20); - var c1 = new float3(m.m01, m.m11, m.m21); - var c2 = new float3(m.m02, m.m12, m.m22); - scl = new Vector3(math.length(c0), math.length(c1), math.length(c2)); - } - else scl = trackerTransform.localScale; - return new TransformProperties(pos, rot, scl); - } - } - #endif -} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta deleted file mode 100644 index 2f6f484a..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2687216adf6641b3a037f7cfcdad52f1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs deleted file mode 100644 index 67e54403..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs +++ /dev/null @@ -1,1359 +0,0 @@ -using System.Collections.Generic; -using FishNet.Managing; -using FishNet.Managing.Predicting; -using FishNet.Managing.Timing; -using FishNet.Object; -using FishNet.Object.Prediction; -using FishNet.Transporting; -using FishNet.Utility.Extension; -using GameKit.Dependencies.Utilities; -using GameKit.Dependencies.Utilities.Types; -using Unity.Collections; -using Unity.Jobs; -using Unity.Jobs.LowLevel.Unsafe; -using Unity.Profiling; -using UnityEngine; -using UnityEngine.Jobs; - -namespace FishNet.Component.Transforming.Beta -{ - #if THREADED_TICKSMOOTHERS - public partial class TickSmoothingManager : MonoBehaviour - { - #region Private. - - #region Private Profiler Markers - private static readonly ProfilerMarker _pm_ClientManager_OnClientConnectionState = new("TickSmoothingManager.Client_OnClientConnectionState"); - private static readonly ProfilerMarker _pm_OnUpdate = new("TickSmoothingManager.TimeManager_OnUpdate()"); - private static readonly ProfilerMarker _pm_OnPreTick = new("TickSmoothingManager.TimeManager_OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostTick = new("TickSmoothingManager.TimeManager_OnPostTick()"); - private static readonly ProfilerMarker _pm_Prediction_OnPostReplicateReplay = new("TickSmoothingManager.Prediction_OnPostReplicateReplay()"); - private static readonly ProfilerMarker _pm_TimeManager_OnRoundTripTimeUpdated = new("TickSmoothingManager.TimeManager_OnRoundTripTimeUpdated()"); - private static readonly ProfilerMarker _pm_MoveToTarget = new("TickSmoothingManager.MoveToTarget()"); - private static readonly ProfilerMarker _pm_ScheduleUpdateRealtimeInterpolation = new("TickSmoothingManager.ScheduleUpdateRealtimeInterpolation()"); - private static readonly ProfilerMarker _pm_ScheduleDiscardExcessiveTransformPropertiesQueue = new("TickSmoothingManager.ScheduleDiscardExcessiveTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_ScheduleSetMoveRates = new("TickSmoothingManager.ScheduleSetMoveRates()"); - private static readonly ProfilerMarker _pm_ScheduleSetMovementMultiplier = new("TickSmoothingManager.ScheduleSetMovementMultiplier()"); - private static readonly ProfilerMarker _pm_ScheduleAddTransformProperties = new("TickSmoothingManager.ScheduleAddTransformProperties()"); - private static readonly ProfilerMarker _pm_ScheduleClearTransformPropertiesQueue = new("TickSmoothingManager.ScheduleClearTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_ScheduleModifyTransformProperties = new("TickSmoothingManager.ScheduleModifyTransformProperties()"); - private static readonly ProfilerMarker _pm_ScheduleSnapNonSmoothedProperties = new("TickSmoothingManager.ScheduleSnapNonSmoothedProperties()"); - private static readonly ProfilerMarker _pm_ScheduleTeleport = new("TickSmoothingManager.ScheduleTeleport()"); - private static readonly ProfilerMarker _pm_Register = new("TickSmoothingManager.Register()"); - private static readonly ProfilerMarker _pm_Unregister = new("TickSmoothingManager.Unregister()"); - #endregion - - #region Const. - /// - /// Maximum allowed entries. - /// - private const int MAXIMUM_QUEUED = 256; - /// - /// Maximum allowed entries to be queued over the interpolation amount. - /// - private const int REQUIRED_QUEUED_OVER_INTERPOLATION = 3; - #endregion - - /// - /// NetworkManager on the same object as this script. - /// - private NetworkManager _networkManager; - /// - /// TimeManager on the same object as this script. - /// - private TimeManager _timeManager; - /// - /// PredictionManager on the same object as this script. - /// - private PredictionManager _predictionManager; - - /// - /// TrackerTransformsPool. - /// - private readonly Stack _trackerTransformsPool = new(); - /// - /// TrackerTransformsPoolHolder. - /// - private Transform _trackerTransformsPoolHolder; - - /// - /// TickSmootherController to index lookup. - /// - private readonly Dictionary _lookup = new(); - /// - /// Index to TickSmootherController and InitializationSettings lookup. - /// - private readonly List _indexToSmoother = new(); - /// - /// Index to TickSmootherController and NetworkBehaviours lookup. - /// - private readonly List _indexToNetworkBehaviour = new(); - /// - /// Index to TickSmootherController and redictionNetworkTransform lookup. - /// - private readonly List _indexToPredictionNetworkTransform = new(); - - /// - /// Index to MoveRate lookup. - /// How quickly to move towards goal values. - /// - private NativeList _moveRates; - /// - /// Index to Owner MovementSettings lookup. - /// Settings to use for owners. - /// - private NativeList _ownerSettings; - /// - /// Index to Spectator MovementSettings lookup. - /// Settings to use for spectators. - /// - private NativeList _spectatorSettings; - - /// - /// Index to PreTickedMask lookup. - /// True if a pretick occurred since last postTick. - /// - private NativeList _preTickedMask; - /// - /// Index to MoveImmediatelyMask lookup. - /// True to begin moving soon as movement data becomes available. Movement will ease in until at interpolation value. False to prevent movement until movement data count meet interpolation. - /// - private NativeList _moveImmediatelyMask; - /// - /// Index to CanSmoothMask lookup. - /// Returns if prediction can be used on this rigidbody. - /// - private NativeList _canSmoothMask; - /// - /// Index to UseOwnerSettingsMask lookup. - /// True if to smooth using owner settings, false for spectator settings. - /// This is only used for performance gains. - /// - private NativeList _useOwnerSettingsMask; - /// - /// Index to ObjectReconcilingMask lookup. - /// - private NativeList _objectReconcilingMask; - /// - /// Index to DetachOnStartMask lookup. - /// True if to detach on smoothing start. - /// - private NativeList _detachOnStartMask; - /// - /// Index to AttachOnStopMask lookup. - /// True if to attach on smoothing stop. - /// - private NativeList _attachOnStopMask; - /// - /// Index to IsMoving lookup. - /// True if moving has started and has not been stopped. - /// - private NativeList _isMoving; - /// - /// Index to TeleportedTick lookup. - /// Last tick this was teleported on. - /// - private NativeList _teleportedTick; - /// - /// Index to RealTimeInterpolation lookup. - /// Current interpolation value, be it a flat value or adaptive. - /// - private NativeList _realTimeInterpolations; - /// - /// Index to MovementMultiplier lookup. - /// Value to multiply movement by. This is used to reduce or increase the rate the movement buffer is consumed. - /// - private NativeList _movementMultipliers; - - /// - /// Index to TransformProperties lookup. - /// TransformProperties to move towards - /// - private StripedRingQueue _transformProperties; - /// - /// Index to PreTick Graphic TransformProperties Snapshot lookup. - /// World values of the graphical after it's been aligned to initialized values in PreTick. - /// - private NativeList _preTickGraphicSnapshot; - /// - /// Index to PreTick Tracker TransformProperties Snapshot lookup. - /// World values of the graphical after it's been aligned to initialized values in PreTick. - /// - private NativeList _postTickTrackerSnapshot; - /// - /// Index to Temp Target TransformProperties Snapshot lookup. - /// - private NativeList _tempTargetSnapshot; - /// - /// Index to OutSnapGraphicWorld lookup. - /// - private NativeList _outSnapGraphicWorld; - /// - /// Index to OutEnqueueTrackerWorld lookup. - /// - private NativeList _outEnqueueTrackerWorld; - /// - /// Index to QueuedTrackerProperties lookup. - /// Properties for the tracker which are queued to be set when the tracker is setup. - /// - private NativeList _queuedTrackerProperties; - - /// - /// Index to MoveToTargetPayloads lookup. - /// - private NativeList _moveToTargetPayloads; - /// - /// Index to UpdateRealtimeInterpolationPayloads lookup. - /// - private NativeList _updateRealtimeInterpolationPayloads; - /// - /// Index to DiscardExcessiveTransformPropertiesQueuePayloads lookup. - /// - private NativeList _discardExcessivePayloads; - /// - /// Index to SetMoveRatesPayloads lookup. - /// - private NativeList _setMoveRatesPayloads; - /// - /// Index to SetMovementMultiplierPayloads lookup. - /// - private NativeList _setMovementMultiplierPayloads; - /// - /// Index to AddTransformPropertiesPayloads lookup. - /// - private NativeList _addTransformPropertiesPayloads; - /// - /// Index to ClearTransformPropertiesQueuePayloads lookup. - /// - private NativeList _clearTransformPropertiesQueuePayloads; - /// - /// Index to ModifyTransformPropertiesPayloads lookup. - /// - private NativeList _modifyTransformPropertiesPayloads; - /// - /// Index to SnapNonSmoothedPropertiesPayloads lookup. - /// - private NativeList _snapNonSmoothedPropertiesPayloads; - /// - /// Index to TeleportPayloads lookup. - /// - private NativeList _teleportPayloads; - - /// - /// Target objects TransformAccessArray. - /// Transform the graphics should follow. - /// - private TransformAccessArray _targetTaa; - /// - /// Graphical objects TransformAccessArray. - /// Cached value of the object to smooth. - /// - private TransformAccessArray _graphicalTaa; - /// - /// Tracker objects TransformAccessArray. - /// Empty gameObject containing a transform which has properties checked after each simulation. - /// If the graphical starts off as nested of targetTransform then this object is created where the graphical object is. - /// Otherwise, this object is placed directly beneath targetTransform. - /// - private TransformAccessArray _trackerTaa; - - /// - /// Subscription to callbacks state. - /// - private bool _subscribed; - #endregion - - /// - /// Initialize once from NetworkManager (pattern mirrors RollbackManager). - /// - internal void InitializeOnce_Internal(NetworkManager manager) - { - _networkManager = manager; - _timeManager = manager.TimeManager; - _predictionManager = manager.PredictionManager; - - if (!_trackerTransformsPoolHolder) - { - _trackerTransformsPoolHolder = new GameObject("Tracker Transforms Pool Holder").transform; - DontDestroyOnLoad(_trackerTransformsPoolHolder.gameObject); - } - - if (!_moveRates.IsCreated) _moveRates = new NativeList(64, Allocator.Persistent); - if (!_ownerSettings.IsCreated) _ownerSettings = new NativeList(64, Allocator.Persistent); - if (!_spectatorSettings.IsCreated) _spectatorSettings = new NativeList(64, Allocator.Persistent); - - if (!_preTickedMask.IsCreated) _preTickedMask = new NativeList(64, Allocator.Persistent); - if (!_canSmoothMask.IsCreated) _canSmoothMask = new NativeList(64, Allocator.Persistent); - if (!_useOwnerSettingsMask.IsCreated) _useOwnerSettingsMask = new NativeList(64, Allocator.Persistent); - if (!_objectReconcilingMask.IsCreated) _objectReconcilingMask = new NativeList(64, Allocator.Persistent); - if (!_detachOnStartMask.IsCreated) _detachOnStartMask = new NativeList(64, Allocator.Persistent); - if (!_attachOnStopMask.IsCreated) _attachOnStopMask = new NativeList(64, Allocator.Persistent); - if (!_moveImmediatelyMask.IsCreated) _moveImmediatelyMask = new NativeList(64, Allocator.Persistent); - if (!_isMoving.IsCreated) _isMoving = new NativeList(64, Allocator.Persistent); - if (!_teleportedTick.IsCreated) _teleportedTick = new NativeList(64, Allocator.Persistent); - if (!_realTimeInterpolations.IsCreated) _realTimeInterpolations = new NativeList(64, Allocator.Persistent); - if (!_movementMultipliers.IsCreated) _movementMultipliers = new NativeList(64, Allocator.Persistent); - - if (!_transformProperties.IsCreated) _transformProperties = new StripedRingQueue(64, MAXIMUM_QUEUED, Allocator.Persistent); - if (!_preTickGraphicSnapshot.IsCreated) _preTickGraphicSnapshot = new NativeList(64, Allocator.Persistent); - if (!_postTickTrackerSnapshot.IsCreated) _postTickTrackerSnapshot = new NativeList(64, Allocator.Persistent); - if (!_tempTargetSnapshot.IsCreated) _tempTargetSnapshot = new NativeList(64, Allocator.Persistent); - if (!_outSnapGraphicWorld.IsCreated) _outSnapGraphicWorld = new NativeList(64, Allocator.Persistent); - if (!_outEnqueueTrackerWorld.IsCreated) _outEnqueueTrackerWorld = new NativeList(64, Allocator.Persistent); - if (!_queuedTrackerProperties.IsCreated) _queuedTrackerProperties = new NativeList(64, Allocator.Persistent); - - if (!_moveToTargetPayloads.IsCreated) _moveToTargetPayloads = new NativeList(64, Allocator.Persistent); - if (!_updateRealtimeInterpolationPayloads.IsCreated) _updateRealtimeInterpolationPayloads = new NativeList(64, Allocator.Persistent); - if (!_discardExcessivePayloads.IsCreated) _discardExcessivePayloads = new NativeList(64, Allocator.Persistent); - if (!_setMoveRatesPayloads.IsCreated) _setMoveRatesPayloads = new NativeList(64, Allocator.Persistent); - if (!_setMovementMultiplierPayloads.IsCreated) _setMovementMultiplierPayloads = new NativeList(64, Allocator.Persistent); - if (!_addTransformPropertiesPayloads.IsCreated) _addTransformPropertiesPayloads = new NativeList(64, Allocator.Persistent); - if (!_clearTransformPropertiesQueuePayloads.IsCreated) _clearTransformPropertiesQueuePayloads = new NativeList(64, Allocator.Persistent); - if (!_modifyTransformPropertiesPayloads.IsCreated) _modifyTransformPropertiesPayloads = new NativeList(64, Allocator.Persistent); - if (!_snapNonSmoothedPropertiesPayloads.IsCreated) _snapNonSmoothedPropertiesPayloads = new NativeList(64, Allocator.Persistent); - if (!_teleportPayloads.IsCreated) _teleportPayloads = new NativeList(64, Allocator.Persistent); - - if (!_targetTaa.isCreated) _targetTaa = new TransformAccessArray(64); - if (!_graphicalTaa.isCreated) _graphicalTaa = new TransformAccessArray(64); - if (!_trackerTaa.isCreated) _trackerTaa = new TransformAccessArray(64); - - // Subscribe to client connection state to (un)hook timing/prediction. - _networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState; - if (_networkManager.ClientManager.Started) ChangeSubscriptions(true); - } - - private void OnDestroy() - { - ChangeSubscriptions(false); - - if (_networkManager != null) - { - _networkManager.ClientManager.OnClientConnectionState -= ClientManager_OnClientConnectionState; - } - - while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) - if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); - - for (int i = 0; i < _indexToSmoother.Count; i++) - { - Transform trackerTransform = _trackerTaa[i]; - if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); - } - - if (_moveRates.IsCreated) _moveRates.Dispose(); - if (_ownerSettings.IsCreated) _ownerSettings.Dispose(); - if (_spectatorSettings.IsCreated) _spectatorSettings.Dispose(); - - if (_preTickedMask.IsCreated) _preTickedMask.Dispose(); - if (_canSmoothMask.IsCreated) _canSmoothMask.Dispose(); - if (_useOwnerSettingsMask.IsCreated) _useOwnerSettingsMask.Dispose(); - if (_objectReconcilingMask.IsCreated) _objectReconcilingMask.Dispose(); - if (_detachOnStartMask.IsCreated) _detachOnStartMask.Dispose(); - if (_attachOnStopMask.IsCreated) _attachOnStopMask.Dispose(); - if (_moveImmediatelyMask.IsCreated) _moveImmediatelyMask.Dispose(); - if (_isMoving.IsCreated) _isMoving.Dispose(); - if (_teleportedTick.IsCreated) _teleportedTick.Dispose(); - if (_realTimeInterpolations.IsCreated) _realTimeInterpolations.Dispose(); - if (_movementMultipliers.IsCreated) _movementMultipliers.Dispose(); - - if (_transformProperties.IsCreated) _transformProperties.Dispose(); - if (_preTickGraphicSnapshot.IsCreated) _preTickGraphicSnapshot.Dispose(); - if (_postTickTrackerSnapshot.IsCreated) _postTickTrackerSnapshot.Dispose(); - if (_tempTargetSnapshot.IsCreated) _tempTargetSnapshot.Dispose(); - if (_outSnapGraphicWorld.IsCreated) _outSnapGraphicWorld.Dispose(); - if (_outEnqueueTrackerWorld.IsCreated) _outEnqueueTrackerWorld.Dispose(); - if (_queuedTrackerProperties.IsCreated) _queuedTrackerProperties.Dispose(); - - if (_moveToTargetPayloads.IsCreated) _moveToTargetPayloads.Dispose(); - if (_updateRealtimeInterpolationPayloads.IsCreated) _updateRealtimeInterpolationPayloads.Dispose(); - if (_discardExcessivePayloads.IsCreated) _discardExcessivePayloads.Dispose(); - if (_setMoveRatesPayloads.IsCreated) _setMoveRatesPayloads.Dispose(); - if (_setMovementMultiplierPayloads.IsCreated) _setMovementMultiplierPayloads.Dispose(); - if (_addTransformPropertiesPayloads.IsCreated) _addTransformPropertiesPayloads.Dispose(); - if (_clearTransformPropertiesQueuePayloads.IsCreated) _clearTransformPropertiesQueuePayloads.Dispose(); - if (_modifyTransformPropertiesPayloads.IsCreated) _modifyTransformPropertiesPayloads.Dispose(); - if (_snapNonSmoothedPropertiesPayloads.IsCreated) _snapNonSmoothedPropertiesPayloads.Dispose(); - if (_teleportPayloads.IsCreated) _teleportPayloads.Dispose(); - - if (_targetTaa.isCreated) _targetTaa.Dispose(); - if (_graphicalTaa.isCreated) _graphicalTaa.Dispose(); - if (_trackerTaa.isCreated) _trackerTaa.Dispose(); - - _indexToNetworkBehaviour.Clear(); - _indexToPredictionNetworkTransform.Clear(); - _indexToSmoother.Clear(); - _lookup.Clear(); - - _networkManager = null; - _timeManager = null; - _predictionManager = null; - } - - /// - /// Register a TickSmootherController with associated settings. - /// - public void Register(TickSmootherController smoother, InitializationSettings initializationSettings, - MovementSettings ownerSettings, MovementSettings spectatorSettings) - { - using (_pm_Register.Auto()) - { - if (smoother == null) - return; - - if (!TransformsAreValid(initializationSettings.GraphicalTransform, initializationSettings.TargetTransform)) - return; - - /* Unset scale smoothing if not detaching. This is to prevent - * the scale from changing with the parent if nested, as that - * would result in the scale being modified twice, once on the parent - * and once on the graphical. Thanks deo_wh for find! */ - if (!initializationSettings.DetachOnStart) - { - ownerSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; - spectatorSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; - } - - if (_lookup.TryGetValue(smoother, out int index)) - { - _ownerSettings[index] = ownerSettings; - _spectatorSettings[index] = spectatorSettings; - return; - } - index = _indexToSmoother.Count; - - _lookup[smoother] = index; - _indexToSmoother.Add(smoother); - _indexToNetworkBehaviour.Add(initializationSettings.InitializingNetworkBehaviour); - _indexToPredictionNetworkTransform.Add( - initializationSettings.FavorPredictionNetworkTransform && - initializationSettings.InitializingNetworkBehaviour != null && - initializationSettings.InitializingNetworkBehaviour.NetworkObject != null && - initializationSettings.InitializingNetworkBehaviour.NetworkObject.IsRigidbodyPredictionType - ? initializationSettings.InitializingNetworkBehaviour.NetworkObject.PredictionNetworkTransform - : null - ); - - _moveRates.Add(new MoveRates(MoveRates.UNSET_VALUE)); - _ownerSettings.Add(ownerSettings); - _spectatorSettings.Add(spectatorSettings); - - _preTickedMask.Add(0); - _canSmoothMask.Add( - (byte)(initializationSettings.GraphicalTransform != null && - _networkManager.IsClientStarted ? 1 : 0)); - _useOwnerSettingsMask.Add( - (byte)(initializationSettings.InitializingNetworkBehaviour == null || - initializationSettings.InitializingNetworkBehaviour.IsOwner || - !initializationSettings.InitializingNetworkBehaviour.Owner.IsValid ? 1 : 0)); - _objectReconcilingMask.Add( - (byte)(initializationSettings.InitializingNetworkBehaviour == null || - initializationSettings.InitializingNetworkBehaviour.NetworkObject == null || - initializationSettings.InitializingNetworkBehaviour.NetworkObject.IsObjectReconciling ? 1 : 0)); - _detachOnStartMask.Add( - (byte)(initializationSettings.DetachOnStart ? 1 : 0)); - _attachOnStopMask.Add( - (byte)(initializationSettings.AttachOnStop ? 1 : 0)); - _moveImmediatelyMask.Add( - (byte)(initializationSettings.MoveImmediately ? 1 : 0)); - - _isMoving.Add(default); - _teleportedTick.Add(TimeManager.UNSET_TICK); - _realTimeInterpolations.Add(default); - _movementMultipliers.Add(default); - - _transformProperties.AddQueue(); - _preTickGraphicSnapshot.Add(default); - _postTickTrackerSnapshot.Add(default); - _tempTargetSnapshot.Add(default); - _outSnapGraphicWorld.Add(default); - _outEnqueueTrackerWorld.Add(default); - _queuedTrackerProperties.Add(new NullableTransformProperties(false, default)); - - _moveToTargetPayloads.Add(new MoveToTargetPayload(0, default)); - _updateRealtimeInterpolationPayloads.Add(new UpdateRealtimeInterpolationPayload(0)); - _discardExcessivePayloads.Add(new DiscardExcessiveTransformPropertiesQueuePayload(0)); - _setMoveRatesPayloads.Add(new SetMoveRatesPayload(0, default)); - _setMovementMultiplierPayloads.Add(new SetMovementMultiplierPayload(0)); - _addTransformPropertiesPayloads.Add(new AddTransformPropertiesPayload(0, default)); - _clearTransformPropertiesQueuePayloads.Add(new ClearTransformPropertiesQueuePayload(0)); - _modifyTransformPropertiesPayloads.Add(new ModifyTransformPropertiesPayload(0, default, default)); - _snapNonSmoothedPropertiesPayloads.Add(new SnapNonSmoothedPropertiesPayload(0, default)); - _teleportPayloads.Add(new TeleportPayload(0)); - - Transform targetTransform = initializationSettings.TargetTransform; - Transform graphicalTransform = initializationSettings.GraphicalTransform; - if (!_trackerTransformsPool.TryPop(out Transform trackerTransform)) - trackerTransform = new GameObject().transform; - ProcessTransformsOnStart(trackerTransform, targetTransform, graphicalTransform, initializationSettings.DetachOnStart); - - _targetTaa.Add(targetTransform); - _graphicalTaa.Add(graphicalTransform); - _trackerTaa.Add(trackerTransform); - - //Use set method as it has sanity checks. - SetInterpolationValue(smoother, ownerSettings.InterpolationValue, forOwnerOrOfflineSmoother: true, unsetAdaptiveInterpolation: false); - SetInterpolationValue(smoother, spectatorSettings.InterpolationValue, forOwnerOrOfflineSmoother: false, unsetAdaptiveInterpolation: false); - - SetAdaptiveInterpolation(smoother, ownerSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: true); - SetAdaptiveInterpolation(smoother, spectatorSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: false); - } - } - - /// - /// Unregister a TickSmootherController. - /// - public void Unregister(TickSmootherController smoother) - { - using (_pm_Unregister.Auto()) - { - if (smoother == null || !_lookup.TryGetValue(smoother, out int index)) - return; - - bool isDetachOnStart = _detachOnStartMask[index] != 0; - bool isAttachOnStop = _attachOnStopMask[index] != 0; - Transform targetTransform = _targetTaa[index]; - Transform graphicalTransform = _graphicalTaa[index]; - Transform trackerTransform = _trackerTaa[index]; - ProcessTransformsOnStop(trackerTransform, targetTransform, graphicalTransform, isDetachOnStart, isAttachOnStop); - if (trackerTransform) - { - _trackerTransformsPool.Push(trackerTransform); - trackerTransform.SetParent(_trackerTransformsPoolHolder); - } - - int last = _indexToSmoother.Count - 1; - if (index != last) - { - var movedSmoother = _indexToSmoother[last]; - _indexToSmoother[index] = movedSmoother; - _lookup[movedSmoother] = index; - var movedNetworkBehaviour = _indexToNetworkBehaviour[last]; - _indexToNetworkBehaviour[index] = movedNetworkBehaviour; - var movedPredictionNetworkTransform = _indexToPredictionNetworkTransform[last]; - _indexToPredictionNetworkTransform[index] = movedPredictionNetworkTransform; - } - - _indexToNetworkBehaviour.RemoveAt(last); - _indexToPredictionNetworkTransform.RemoveAt(last); - _indexToSmoother.RemoveAt(last); - _lookup.Remove(smoother); - - _moveRates.RemoveAtSwapBack(index); - _ownerSettings.RemoveAtSwapBack(index); - _spectatorSettings.RemoveAtSwapBack(index); - - _preTickedMask.RemoveAtSwapBack(index); - _canSmoothMask.RemoveAtSwapBack(index); - _useOwnerSettingsMask.RemoveAtSwapBack(index); - _objectReconcilingMask.RemoveAtSwapBack(index); - _detachOnStartMask.RemoveAtSwapBack(index); - _attachOnStopMask.RemoveAtSwapBack(index); - _moveImmediatelyMask.RemoveAtSwapBack(index); - - _isMoving.RemoveAtSwapBack(index); - _teleportedTick.RemoveAtSwapBack(index); - _realTimeInterpolations.RemoveAtSwapBack(index); - _movementMultipliers.RemoveAtSwapBack(index); - - _transformProperties.RemoveQueueAtSwapBack(index); - _preTickGraphicSnapshot.RemoveAtSwapBack(index); - _postTickTrackerSnapshot.RemoveAtSwapBack(index); - _tempTargetSnapshot.RemoveAtSwapBack(index); - _outSnapGraphicWorld.RemoveAtSwapBack(index); - _outEnqueueTrackerWorld.RemoveAtSwapBack(index); - _queuedTrackerProperties.RemoveAtSwapBack(index); - - _targetTaa.RemoveAtSwapBack(index); - _graphicalTaa.RemoveAtSwapBack(index); - _trackerTaa.RemoveAtSwapBack(index); - - _moveToTargetPayloads.RemoveAtSwapBack(index); - _updateRealtimeInterpolationPayloads.RemoveAtSwapBack(index); - _discardExcessivePayloads.RemoveAtSwapBack(index); - _setMoveRatesPayloads.RemoveAtSwapBack(index); - _setMovementMultiplierPayloads.RemoveAtSwapBack(index); - _addTransformPropertiesPayloads.RemoveAtSwapBack(index); - _clearTransformPropertiesQueuePayloads.RemoveAtSwapBack(index); - _modifyTransformPropertiesPayloads.RemoveAtSwapBack(index); - _snapNonSmoothedPropertiesPayloads.RemoveAtSwapBack(index); - _teleportPayloads.RemoveAtSwapBack(index); - } - } - - /// - /// Returns if configured transforms are valid. - /// - /// - private static bool TransformsAreValid(Transform graphicalTransform, Transform targetTransform) - { - if (graphicalTransform == null) - { - NetworkManagerExtensions.LogError($"Graphical transform cannot be null."); - return false; - } - if (targetTransform == null) - { - NetworkManagerExtensions.LogError($"Target transform on {graphicalTransform} cannot be null."); - return false; - } - if (targetTransform == graphicalTransform) - { - NetworkManagerExtensions.LogError($"Target transform cannot be the same as graphical transform on {graphicalTransform}."); - return false; - } - - return true; - } - - private static void ProcessTransformsOnStart(Transform trackerTransform, Transform targetTransform, Transform graphicalTransform, bool isDetachOnStart) - { - if (isDetachOnStart) - { - trackerTransform.SetParent(targetTransform); - - TransformProperties gfxWorldProperties = graphicalTransform.GetWorldProperties(); - graphicalTransform.SetParent(null); - graphicalTransform.SetWorldProperties(gfxWorldProperties); - } - else - { - Transform trackerParent = graphicalTransform.IsChildOf(targetTransform) ? graphicalTransform.parent : targetTransform; - trackerTransform.SetParent(trackerParent); - } - - targetTransform.GetPositionAndRotation(out var pos, out var rot); - trackerTransform.SetWorldPositionRotationAndScale(pos, rot, graphicalTransform.localScale); - trackerTransform.gameObject.name = $"{graphicalTransform.name}_Tracker"; - } - - private static void ProcessTransformsOnStop(Transform trackerTransform, Transform targetTransform, Transform graphicalTransform, bool isDetachOnStart, bool isAttachOnStop) - { - if (trackerTransform == null || targetTransform == null || graphicalTransform == null) - return; - if (ApplicationState.IsQuitting()) - return; - - trackerTransform.SetParent(null); - if (isDetachOnStart && isAttachOnStop) - { - graphicalTransform.SetParent(targetTransform.parent); - graphicalTransform.SetLocalProperties(trackerTransform.GetLocalProperties()); - } - } - - /// - /// Updates movement settings for a registered smoother. - /// Both owner and spectator settings are applied atomically. - /// - public void SetSettings(TickSmootherController smoother, in MovementSettings owner, in MovementSettings spectator) - { - if (smoother == null) return; - if (!_lookup.TryGetValue(smoother, out int index)) return; - - _ownerSettings[index] = owner; - _spectatorSettings[index] = spectator; - } - - /// - /// Sets transforms for a registered smoother (target, graphical, tracker). - /// - public void SetTransforms(TickSmootherController smoother, Transform target, Transform graphical) - { - if (smoother == null) return; - if (!_lookup.TryGetValue(smoother, out int index)) return; - - bool isDetachOnStart = _detachOnStartMask[index] != 0; - bool isAttachOnStop = _attachOnStopMask[index] != 0; - - Transform tracker = _trackerTaa[index]; - Transform prevTarget = _targetTaa[index]; - Transform prevGraphical = _graphicalTaa[index]; - ProcessTransformsOnStop(tracker, prevTarget, prevGraphical, isDetachOnStart, isAttachOnStop); - - _targetTaa[index] = target; - _graphicalTaa[index] = graphical; - ProcessTransformsOnStart(tracker, target, graphical, isDetachOnStart); - } - - /// - /// Updates the smoothedProperties value. - /// - /// TickSmootherController. - /// New value. - /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings - public void SetSmoothedProperties(TickSmootherController smoother, TransformPropertiesFlag value, bool forOwnerOrOfflineSmoother) - { - if (smoother == null) return; - if (!_lookup.TryGetValue(smoother, out int index)) return; - - MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; - settings.SmoothedProperties = value; - - if (forOwnerOrOfflineSmoother) - _ownerSettings[index] = settings; - else - _spectatorSettings[index] = settings; - } - - /// - /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. - /// - /// TickSmootherController. - /// New value. - /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings - public void SetInterpolationValue(TickSmootherController smoother, byte value, bool forOwnerOrOfflineSmoother) => SetInterpolationValue(smoother, value, forOwnerOrOfflineSmoother, unsetAdaptiveInterpolation: true); - - /// - /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. - /// - /// TickSmootherController. - /// New value. - /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings - /// - private void SetInterpolationValue(TickSmootherController smoother, byte value, bool forOwnerOrOfflineSmoother, bool unsetAdaptiveInterpolation) - { - if (smoother == null) return; - if (!_lookup.TryGetValue(smoother, out int index)) return; - - if (value < 1) - value = 1; - - MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; - settings.InterpolationValue = value; - - if (forOwnerOrOfflineSmoother) - _ownerSettings[index] = settings; - else - _spectatorSettings[index] = settings; - - if (unsetAdaptiveInterpolation) - SetAdaptiveInterpolation(smoother, AdaptiveInterpolationType.Off, forOwnerOrOfflineSmoother); - } - - /// - /// Updates the adaptiveInterpolation value. - /// - /// TickSmootherController. - /// New value. - /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings - public void SetAdaptiveInterpolation(TickSmootherController smoother, AdaptiveInterpolationType value, bool forOwnerOrOfflineSmoother) - { - if (smoother == null) return; - if (!_lookup.TryGetValue(smoother, out int index)) return; - - MovementSettings settings = forOwnerOrOfflineSmoother ? _ownerSettings[index] : _spectatorSettings[index]; - settings.AdaptiveInterpolationValue = value; - - if (forOwnerOrOfflineSmoother) - _ownerSettings[index] = settings; - else - _spectatorSettings[index] = settings; - - _updateRealtimeInterpolationPayloads[index] = new UpdateRealtimeInterpolationPayload(1); - } - - /// - /// Tries to set local properties for the graphical tracker transform. - /// - /// New values. - /// Returns true if the tracker has been setup and values have been applied to teh tracker transform. - /// When false is returned the values are cached and will be set when tracker is created. A cached value will be used every time the tracker is setup; to disable this behavior call this method with null value. - public bool TrySetGraphicalTrackerLocalProperties(TickSmootherController smoother, TransformProperties? localValues) - { - if (smoother == null) return false; - if (!_lookup.TryGetValue(smoother, out int index)) return false; - - if (_trackerTaa[index] == null || localValues == null) - { - _queuedTrackerProperties[index] = new NullableTransformProperties(localValues != null, localValues ?? default); - return false; - } - - _trackerTaa[index].SetLocalProperties(localValues.Value); - return true; - } - - public bool TryGetGraphicalTrackerLocalProperties(TickSmootherController smoother, out TransformProperties localValues) - { - localValues = default; - if (smoother == null) return false; - if (!_lookup.TryGetValue(smoother, out int index)) return false; - - Transform trackerTransform = _trackerTaa[index]; - if (trackerTransform != null) - { - localValues = new(trackerTransform.localPosition, trackerTransform.localRotation, trackerTransform.localScale); - return true; - } - - NullableTransformProperties queuedTrackerProperties = _queuedTrackerProperties[index]; - if (queuedTrackerProperties.IsExist != 0) - { - localValues = queuedTrackerProperties.Properties; - return true; - } - - // Fall through. - return false; - } - - /// - /// Marks to teleports the graphical to it's starting position and clears the internal movement queue at the PreTick. - /// - public void Teleport(TickSmootherController smoother) - { - if (smoother == null) return; - if (!_lookup.TryGetValue(smoother, out int index)) return; - - _teleportPayloads[index] = new TeleportPayload(1); - } - - private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs args) - { - using (_pm_ClientManager_OnClientConnectionState.Auto()) - { - while (_trackerTransformsPool.TryPop(out Transform trackerTransform)) - if (trackerTransform && trackerTransform.gameObject) Destroy(trackerTransform.gameObject); - - if (args.ConnectionState == LocalConnectionState.Started) - ChangeSubscriptions(true); - else - ChangeSubscriptions(false); - } - } - - private void ChangeSubscriptions(bool subscribe) - { - if (_timeManager == null) - return; - - if (_subscribed == subscribe) - return; - - _subscribed = subscribe; - - if (subscribe) - { - _timeManager.OnUpdate += TimeManager_OnUpdate; - _timeManager.OnPreTick += TimeManager_OnPreTick; - _timeManager.OnPostTick += TimeManager_OnPostTick; - _timeManager.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated; - - if (_predictionManager != null) - _predictionManager.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay; - } - else - { - _timeManager.OnUpdate -= TimeManager_OnUpdate; - _timeManager.OnPreTick -= TimeManager_OnPreTick; - _timeManager.OnPostTick -= TimeManager_OnPostTick; - _timeManager.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated; - - if (_predictionManager != null) - _predictionManager.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay; - } - } - - /// - /// Called every frame. - /// - private void TimeManager_OnUpdate() - { - using (_pm_OnUpdate.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return; - int batchSize = ComputeBatchSize(count); - - var job = new UpdateJob - { - canSmoothMask = _canSmoothMask.AsArray(), - deltaTime = Time.deltaTime, - - moveToTargetPayloads = _moveToTargetPayloads.AsArray() - }; - - JobHandle innerHandle = job.Schedule(count, batchSize); - JobHandle moveToTargetHandle = ScheduleMoveToTarget(innerHandle); - moveToTargetHandle.Complete(); - } - } - - /// - /// Called when the TimeManager invokes OnPreTick. - /// - private void TimeManager_OnPreTick() - { - using (_pm_OnPreTick.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return; - int batchSize = ComputeBatchSize(count); - - for (int i = 0; i < count; i++) - { - Transform graphicalTransform = _graphicalTaa[i]; - NetworkBehaviour networkBehaviour = _indexToNetworkBehaviour[i]; - NetworkTransform predictionNetworkTransform = _indexToPredictionNetworkTransform[i]; - _canSmoothMask[i] = - (byte)(graphicalTransform != null && - (predictionNetworkTransform == null || - !predictionNetworkTransform.DoSettingsAllowSmoothing()) && - _networkManager.IsClientStarted ? 1 : 0); - - _useOwnerSettingsMask[i] = - (byte)(networkBehaviour == null || - networkBehaviour.IsOwner || - !networkBehaviour.Owner.IsValid ? 1 : 0); - - _objectReconcilingMask[i] = - (byte)(networkBehaviour == null || - networkBehaviour.NetworkObject == null || - networkBehaviour.NetworkObject.IsObjectReconciling ? 1 : 0); - } - - JobHandle preTickMarkHandle = new PreTickMarkJob - { - canSmoothMask = _canSmoothMask.AsArray(), - preTickedMask = _preTickedMask.AsArray(), - discardExcessivePayloads = _discardExcessivePayloads.AsArray() - }.Schedule(count, batchSize); - - JobHandle discardExcessiveHandle = ScheduleDiscardExcessiveTransformPropertiesQueue(preTickMarkHandle); - - JobHandle teleportHandle = ScheduleTeleport(discardExcessiveHandle); - - JobHandle preTickCaptureGraphicalHandle = new PreTickCaptureGraphicalJob - { - canSmoothMask = _canSmoothMask.AsArray(), - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - graphicSnapshot = _preTickGraphicSnapshot.AsArray() - }.Schedule(_graphicalTaa, teleportHandle); - - preTickCaptureGraphicalHandle.Complete(); - } - } - - /// - /// Called when the TimeManager invokes OnPostReplay. - /// - /// Replay tick for the local client. - /// - /// This is dependent on the initializing NetworkBehaviour being set. - private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick) - { - using (_pm_Prediction_OnPostReplicateReplay.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return; - int batchSize = ComputeBatchSize(count); - - var job = new PostReplicateReplayJob - { - clientTick = clientTick, - teleportedTick = _teleportedTick.AsArray(), - objectReconcilingMask = _objectReconcilingMask.AsArray(), - - transformProperties = _transformProperties, - modifyTransformPropertiesPayloads = _modifyTransformPropertiesPayloads.AsArray() - }; - - JobHandle innerHandle = job.Schedule(count, batchSize); - JobHandle modifyTransformPropertiesHandle = ScheduleModifyTransformProperties(innerHandle); - modifyTransformPropertiesHandle.Complete(); - } - } - - /// - /// Called when TimeManager invokes OnPostTick. - /// - private void TimeManager_OnPostTick() - { - using (_pm_OnPostTick.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return; - - JobHandle captureLocalTargetHandle = new CaptureLocalTargetJob - { - canSmoothMask = _canSmoothMask.AsArray(), - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - targetSnapshot = _tempTargetSnapshot.AsArray() - }.Schedule(_targetTaa); - - JobHandle postTickCaptureTrackerHandle = new PostTickCaptureTrackerJob - { - canSmoothMask = _canSmoothMask.AsArray(), - detachOnStartMask = _detachOnStartMask.AsArray(), - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - targetSnapshot = _tempTargetSnapshot.AsArray(), - trackerSnapshot = _postTickTrackerSnapshot.AsArray() - }.Schedule(_trackerTaa, captureLocalTargetHandle); - - JobHandle postTickHandle = new PostTickJob - { - clientTick = _timeManager.LocalTick, - canSmoothMask = _canSmoothMask.AsArray(), - teleportedTick = _teleportedTick.AsArray(), - preTickedMask = _preTickedMask.AsArray(), - detachOnStartMask = _detachOnStartMask.AsArray(), - postTickTrackerSnapshot = _postTickTrackerSnapshot.AsArray(), - preTickGraphicSnapshot = _preTickGraphicSnapshot.AsArray(), - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - - discardExcessivePayloads = _discardExcessivePayloads.AsArray(), - snapNonSmoothedPropertiesPayloads = _snapNonSmoothedPropertiesPayloads.AsArray(), - addTransformPropertiesPayloads = _addTransformPropertiesPayloads.AsArray() - }.Schedule(_graphicalTaa, postTickCaptureTrackerHandle); - - JobHandle discardExcessiveHandle = ScheduleDiscardExcessiveTransformPropertiesQueue(postTickHandle); - JobHandle snapNonSmoothedPropertiesHandle = ScheduleSnapNonSmoothedProperties(discardExcessiveHandle); - JobHandle addTransformPropertiesHandle = ScheduleAddTransformProperties(snapNonSmoothedPropertiesHandle); - addTransformPropertiesHandle.Complete(); - } - } - - private void TimeManager_OnRoundTripTimeUpdated(long rttMs) - { - using (_pm_TimeManager_OnRoundTripTimeUpdated.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return; - int batchSize = ComputeBatchSize(count); - - var job = new RoundTripTimeUpdatedJob - { - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - - updateRealtimeInterpolationPayloads = _updateRealtimeInterpolationPayloads.AsArray() - }; - - JobHandle innerHandle = job.Schedule(count, batchSize); - JobHandle updateRealtimeInterpolationHandle = ScheduleUpdateRealtimeInterpolation(innerHandle); - updateRealtimeInterpolationHandle.Complete(); - } - } - - /// - /// Moves transform to target values. - /// - public JobHandle ScheduleMoveToTarget(in JobHandle outerHandle = default) - { - using (_pm_MoveToTarget.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - - var job = new MoveToTargetJob - { - jobPayloads = _moveToTargetPayloads.AsArray(), - - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - - realTimeInterpolations = _realTimeInterpolations.AsArray(), - moveImmediatelyMask = _moveImmediatelyMask.AsArray(), - tickDelta = (float)_timeManager.TickDelta, - - isMoving = _isMoving.AsArray(), - movementMultipliers = _movementMultipliers.AsArray(), - - transformProperties = _transformProperties, - moveRates = _moveRates.AsArray(), - - setMoveRatesPayloads = _setMoveRatesPayloads.AsArray(), - setMovementMultiplierPayloads = _setMovementMultiplierPayloads.AsArray(), - clearTransformPropertiesQueuePayloads = _clearTransformPropertiesQueuePayloads.AsArray() - }; - - JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); - return innerHandle; - } - } - - /// - /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. - /// - public JobHandle ScheduleUpdateRealtimeInterpolation(in JobHandle outerHandle = default) - { - using (_pm_ScheduleUpdateRealtimeInterpolation.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - int batchSize = ComputeBatchSize(count); - - var job = new UpdateRealtimeInterpolationJob - { - jobPayloads = _updateRealtimeInterpolationPayloads.AsArray(), - - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - - tickDelta = (float)_timeManager.TickDelta, - tickRate = _timeManager.TickRate, - rtt = _timeManager.RoundTripTime, - localTick = _timeManager.LocalTick, - isServerOnlyStarted = _networkManager.IsServerOnlyStarted, - - realTimeInterpolations = _realTimeInterpolations.AsArray() - }; - - JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); - return innerHandle; - } - } - - /// - /// Discards datas over interpolation limit from movement queue. - /// - private JobHandle ScheduleDiscardExcessiveTransformPropertiesQueue(in JobHandle outerHandle = default) - { - using (_pm_ScheduleDiscardExcessiveTransformPropertiesQueue.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - int batchSize = ComputeBatchSize(count); - - var job = new DiscardExcessiveTransformPropertiesQueueJob - { - jobPayloads = _discardExcessivePayloads.AsArray(), - - realTimeInterpolations = _realTimeInterpolations.AsArray(), - requiredQueuedOverInterpolation = REQUIRED_QUEUED_OVER_INTERPOLATION, - - transformProperties = _transformProperties, - setMoveRatesPayloads = _setMoveRatesPayloads.AsArray() - }; - - JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); - JobHandle setMoveRatesHandle = ScheduleSetMoveRates(innerHandle); - return setMoveRatesHandle; - } - } - - /// - /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. - /// - private JobHandle ScheduleSetMoveRates(in JobHandle outerHandle = default) - { - using (_pm_ScheduleSetMoveRates.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - int batchSize = ComputeBatchSize(count); - - var job = new SetMoveRatesJob - { - jobPayloads = _setMoveRatesPayloads.AsArray(), - - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - - transformProperties = _transformProperties, - tickDelta = (float)_timeManager.TickDelta, - - moveRates = _moveRates.AsArray(), - setMovementMultiplierPayloads = _setMovementMultiplierPayloads.AsArray(), - }; - JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); - JobHandle setMovementMultiplierHandle = ScheduleSetMovementMultiplier(innerHandle); - return setMovementMultiplierHandle; - } - } - - private JobHandle ScheduleSetMovementMultiplier(in JobHandle outerHandle = default) - { - using (_pm_ScheduleSetMovementMultiplier.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - int batchSize = ComputeBatchSize(count); - - var job = new SetMovementMultiplierJob - { - jobPayloads = _setMovementMultiplierPayloads.AsArray(), - - transformProperties = _transformProperties, - realTimeInterpolations = _realTimeInterpolations.AsArray(), - moveImmediatelyMask = _moveImmediatelyMask.AsArray(), - - movementMultipliers = _movementMultipliers.AsArray() - }; - JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); - return innerHandle; - } - } - - /// - /// Adds a new transform properties and sets move rates if needed. - /// - private JobHandle ScheduleAddTransformProperties(JobHandle outerHandle) - { - using (_pm_ScheduleAddTransformProperties.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - - var job = new AddTransformPropertiesJob - { - jobPayloads = _addTransformPropertiesPayloads.AsArray(), - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - - transformProperties = _transformProperties, - setMoveRatesPayloads = _setMoveRatesPayloads.AsArray() - }; - - JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); - JobHandle setMoveRatesHandle = ScheduleSetMoveRates(innerHandle); - return setMoveRatesHandle; - } - } - - /// - /// Clears the pending movement queue. - /// - private JobHandle ScheduleClearTransformPropertiesQueue(JobHandle outerHandle) - { - using (_pm_ScheduleClearTransformPropertiesQueue.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - int batchSize = ComputeBatchSize(count); - - var job = new ClearTransformPropertiesQueueJob - { - jobPayloads = _clearTransformPropertiesQueuePayloads.AsArray(), - - transformProperties = _transformProperties, - moveRates = _moveRates.AsArray() - }; - - JobHandle innerHandle = job.Schedule(count, batchSize, outerHandle); - return innerHandle; - } - } - - /// - /// Modifies a transform property for a tick. This does not error check for empty collections. - /// firstTick - First tick in the queue. If 0 this will be looked up. - /// - private JobHandle ScheduleModifyTransformProperties(JobHandle outerHandle) - { - using (_pm_ScheduleModifyTransformProperties.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - - JobHandle captureLocalTargetHandle = new CaptureLocalTargetJob - { - canSmoothMask = _canSmoothMask.AsArray(), - targetSnapshot = _tempTargetSnapshot.AsArray(), - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray() - }.Schedule(_targetTaa, outerHandle); - - JobHandle modifyTransformPropertiesHandle = new ModifyTransformPropertiesJob - { - jobPayloads = _modifyTransformPropertiesPayloads.AsArray(), - detachOnStartMask = _detachOnStartMask.AsArray(), - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - targetSnapshot = _tempTargetSnapshot.AsArray(), - transformProperties = _transformProperties, - }.Schedule(_trackerTaa, captureLocalTargetHandle); - - return modifyTransformPropertiesHandle; - } - } - - /// - /// Snaps non-smoothed properties to original positoin if setting is enabled. - /// - private JobHandle ScheduleSnapNonSmoothedProperties(JobHandle outerHandle) - { - using (_pm_ScheduleSnapNonSmoothedProperties.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - - var job = new SnapNonSmoothedPropertiesJob - { - jobPayloads = _snapNonSmoothedPropertiesPayloads.AsArray(), - - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - }; - - JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); - return innerHandle; - } - } - - /// - /// Teleports the graphical to it's starting position and clears the internal movement queue. - /// - private JobHandle ScheduleTeleport(JobHandle outerHandle) - { - using (_pm_ScheduleTeleport.Auto()) - { - int count = _indexToSmoother.Count; - if (count == 0) return outerHandle; - - var job = new TeleportJob - { - jobPayloads = _teleportPayloads.AsArray(), - - useOwnerSettingsMask = _useOwnerSettingsMask.AsArray(), - ownerSettings = _ownerSettings.AsArray(), - spectatorSettings = _spectatorSettings.AsArray(), - preTickTrackerSnapshot = _postTickTrackerSnapshot.AsArray(), - localTick = _timeManager.LocalTick, - - transformProperties = _transformProperties, - clearTransformPropertiesQueuePayloads = _clearTransformPropertiesQueuePayloads.AsArray(), - moveRates = _moveRates.AsArray(), - teleportedTick = _teleportedTick.AsArray() - }; - - JobHandle innerHandle = job.Schedule(_graphicalTaa, outerHandle); - JobHandle clearTransformPropertiesQueueHandle = ScheduleClearTransformPropertiesQueue(innerHandle); - return clearTransformPropertiesQueueHandle; - } - } - - private static int ComputeBatchSize(int length, int minBatch = 1, int maxBatch = 128) - { - if (length <= 0) return 1; - - // +1: main thread + worker threads - int workers = JobsUtility.JobWorkerCount + 1; - - // Aim for ~4 waves of batches across all workers. - int targetBatches = Mathf.Max(1, workers * 4); - - // CeilDiv to get iterations per batch - int batch = (length + targetBatches - 1) / targetBatches; - - return Mathf.Clamp(batch, minBatch, maxBatch); - } - } - #endif -} diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta deleted file mode 100644 index 80b9c583..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: bed04a80458a35443b1a283848e3d1c9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs deleted file mode 100644 index b6b3d47f..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs +++ /dev/null @@ -1,983 +0,0 @@ -using System; -using FishNet.Managing; -using FishNet.Managing.Timing; -using FishNet.Object; -using FishNet.Object.Prediction; -using FishNet.Utility.Extension; -using GameKit.Dependencies.Utilities; -using UnityEngine; -using UnityEngine.Profiling; -using Unity.Profiling; -using UnityEngine.Scripting; - -namespace FishNet.Component.Transforming.Beta -{ - #if THREADED_TICKSMOOTHERS - /// - /// This class is under regular development and it's API may change at any time. - /// - public sealed class UniversalTickSmoother : IResettable - { - #region Public. - /// - /// True if currently initialized. - /// - public bool IsInitialized { get; private set; } - #endregion - - #region Private. - - #region Private Profiler Markers - - private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new ProfilerMarker("UniversalTickSmoother.UpdateRealtimeInterpolation()"); - private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("UniversalTickSmoother.OnUpdate(float)"); - private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("UniversalTickSmoother.OnPreTick()"); - private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new ProfilerMarker("UniversalTickSmoother.OnPostReplicateReplay(uint)"); - private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("UniversalTickSmoother.OnPostTick(uint)"); - private static readonly ProfilerMarker _pm_ClearTPQ = new ProfilerMarker("UniversalTickSmoother.ClearTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_DiscardTPQ = new ProfilerMarker("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()"); - private static readonly ProfilerMarker _pm_AddTP = new ProfilerMarker("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)"); - private static readonly ProfilerMarker _pm_ModifyTP = new ProfilerMarker("UniversalTickSmoother.ModifyTransformProperties(uint, uint)"); - private static readonly ProfilerMarker _pm_SetMoveRates = new ProfilerMarker("UniversalTickSmoother.SetMoveRates(in TransformProperties)"); - private static readonly ProfilerMarker _pm_MoveToTarget = new ProfilerMarker("UniversalTickSmoother.MoveToTarget(float)"); - - #endregion - - /// - /// How quickly to move towards goal values. - /// - private MoveRates _moveRates = new(); - /// - /// True if a pretick occurred since last postTick. - /// - private bool _preTicked; - /// - /// World values of the graphical after it's been aligned to initialized values in PreTick. - /// - private TransformProperties _trackerPreTickWorldValues; - /// - /// World values of the graphical after it's been aligned to initialized values in PreTick. - /// - private TransformProperties _graphicsPreTickWorldValues; - /// - /// Cached value of adaptive interpolation value. - /// - private AdaptiveInterpolationType _cachedAdaptiveInterpolationValue; - /// - /// Cached value of flat interpolation value. - /// - private byte _cachedInterpolationValue; - /// - /// Cached properties to smooth of the graphical. - /// - private TransformPropertiesFlag _cachedSmoothedProperties; - /// - /// Cached value of snapping non-smoothed properties. - /// - private bool _cachedSnapNonSmoothedProperties; - /// - /// Squared distance target must travel to cause a teleport. - /// - private float _cachedTeleportThreshold; - /// - /// True if to detach on network start. - /// - private bool _detachOnStart; - /// - /// True to re-attach on network stop. - /// - private bool _attachOnStop; - /// - /// True to begin moving soon as movement data becomes available. Movement will ease in until at interpolation value. False to prevent movement until movement data count meet interpolation. - /// - private bool _moveImmediately; - /// - /// Transform the graphics should follow. - /// - private Transform _targetTransform; - /// - /// Cached value of the object to smooth. - /// - private Transform _graphicalTransform; - /// - /// Empty gameObject containing a transform which has properties checked after each simulation. - /// If the graphical starts off as nested of targetTransform then this object is created where the graphical object is. - /// Otherwise, this object is placed directly beneath targetTransform. - /// - private Transform _trackerTransform; - /// - /// TimeManager tickDelta. - /// - private float _tickDelta; - /// - /// NetworkBehaviour this is initialized for. Value may be null. - /// - private NetworkBehaviour _initializingNetworkBehaviour; - /// - /// TimeManager this is initialized for. - /// - private TimeManager _initializingTimeManager; - /// - /// Value to multiply movement by. This is used to reduce or increase the rate the movement buffer is consumed. - /// - private float _movementMultiplier = 1f; - /// - /// TransformProperties to move towards. - /// - private BasicQueue _transformProperties; - /// - /// True if to smooth using owner settings, false for spectator settings. - /// This is only used for performance gains. - /// - private bool _useOwnerSettings; - /// - /// Last tick this was teleported on. - /// - private uint _teleportedTick = TimeManager.UNSET_TICK; - /// - /// Current interpolation value, be it a flat value or adaptive. - /// - private byte _realtimeInterpolation; - /// - /// Settings to use for owners. - /// - private MovementSettings _controllerMovementSettings; - /// - /// Settings to use for spectators. - /// - private MovementSettings _spectatorMovementSettings; - /// - /// True if moving has started and has not been stopped. - /// - private bool _isMoving; - /// - /// NetworkTransform used when prediction type is set to other. - /// - private NetworkTransform _predictionNetworkTransform; - #endregion - - #region Const. - /// - /// Maximum allowed entries to be queued over the interpolation amount. - /// - private const int MAXIMUM_QUEUED_OVER_INTERPOLATION = 3; - #endregion - - [Preserve] - public UniversalTickSmoother() { } - - ~UniversalTickSmoother() - { - // This is a last resort for if something didnt deinitialize right. - ResetState(); - } - - [Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5 - public void SetGraphicalInitializedOffsetValues(TransformProperties value) { } - - [Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5 - public TransformProperties GetGraphicalInitializedOffsetValues() => default; - - /// - /// Tries to set local properties for the graphical tracker transform. - /// - /// New values. - /// Returns true if the tracker has been setup and values have been applied to teh tracker transform. - /// When false is returned the values are cached and will be set when tracker is created. A cached value will be used every time the tracker is setup; to disable this behavior call this method with null value. - public bool TrySetGraphicalTrackerLocalProperties(TransformProperties? localValues) - { - if (_trackerTransform == null || localValues == null) - { - _queuedTrackerProperties = localValues; - return false; - } - - - _trackerTransform.SetLocalProperties(localValues.Value); - return true; - } - - [Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5 - public void SetAdditionalGraphicalOffsetValues(TransformProperties localValues) { } - - [Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5 - public TransformProperties GetAdditionalGraphicalOffsetValues() => default; - - public TransformProperties GetGraphicalTrackerLocalProperties() - { - if (_trackerTransform != null) - return new(_trackerTransform.localPosition, _trackerTransform.localRotation, _trackerTransform.localScale); - if (_queuedTrackerProperties != null) - return _queuedTrackerProperties.Value; - - // Fall through. - NetworkManager manager = _initializingNetworkBehaviour == null ? null : _initializingNetworkBehaviour.NetworkManager; - manager.LogWarning($"Graphical tracker properties cannot be returned because tracker is not setup yet, and no setup properties have been specified. Use TrySetGraphicalTrackerProperties to set setup properties or call this method after IsInitialized is true."); - return default; - } - - /// - /// Properties for the tracker which are queued to be set when the tracker is setup. - /// - private TransformProperties? _queuedTrackerProperties; - - /// - /// Updates the smoothedProperties value. - /// - /// New value. - /// True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings - public void SetSmoothedProperties(TransformPropertiesFlag value, bool forController) - { - _controllerMovementSettings.SmoothedProperties = value; - SetCaches(forController); - } - - /// - /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. - /// - /// - public void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother) => SetInterpolationValue(value, forOwnerOrOfflineSmoother, unsetAdaptiveInterpolation: true); - - /// - /// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation. - /// - private void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother, bool unsetAdaptiveInterpolation) - { - if (value < 1) - value = 1; - - if (forOwnerOrOfflineSmoother) - _controllerMovementSettings.InterpolationValue = value; - else - _spectatorMovementSettings.InterpolationValue = value; - - if (unsetAdaptiveInterpolation) - SetAdaptiveInterpolation(AdaptiveInterpolationType.Off, forOwnerOrOfflineSmoother); - } - - /// - /// Updates the adaptiveInterpolation value. - /// - /// New value. - public void SetAdaptiveInterpolation(AdaptiveInterpolationType value, bool forOwnerOrOfflineSmoother) - { - if (forOwnerOrOfflineSmoother) - _controllerMovementSettings.AdaptiveInterpolationValue = value; - else - _spectatorMovementSettings.AdaptiveInterpolationValue = value; - - UpdateRealtimeInterpolation(); - } - - public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings) - { - ResetState(); - - Transform graphicalTransform = initializationSettings.GraphicalTransform; - Transform targetTransform = initializationSettings.TargetTransform; - - if (!TransformsAreValid(graphicalTransform, targetTransform)) - return; - - _transformProperties = CollectionCaches.RetrieveBasicQueue(); - _controllerMovementSettings = ownerSettings; - _spectatorMovementSettings = spectatorSettings; - - /* Unset scale smoothing if not detaching. This is to prevent - * the scale from changing with the parent if nested, as that - * would result in the scale being modified twice, once on the parent - * and once on the graphical. Thanks deo_wh for find! */ - if (!initializationSettings.DetachOnStart) - { - _controllerMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; - _spectatorMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale; - } - - _initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour; - _initializingTimeManager = initializationSettings.InitializingTimeManager; - _targetTransform = targetTransform; - _graphicalTransform = graphicalTransform; - _tickDelta = (float)initializationSettings.InitializingTimeManager.TickDelta; - _detachOnStart = initializationSettings.DetachOnStart; - _attachOnStop = initializationSettings.AttachOnStop; - _moveImmediately = initializationSettings.MoveImmediately; - - if (initializationSettings.FavorPredictionNetworkTransform && _initializingNetworkBehaviour != null) - { - NetworkObject networkObject = _initializingNetworkBehaviour.NetworkObject; - if (!networkObject.IsRigidbodyPredictionType) - _predictionNetworkTransform = networkObject.PredictionNetworkTransform; - else - _predictionNetworkTransform = null; - } - else - { - _predictionNetworkTransform = null; - } - - SetCaches(GetUseOwnerSettings()); - - //Use set method as it has sanity checks. - SetInterpolationValue(_controllerMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: true, unsetAdaptiveInterpolation: false); - SetInterpolationValue(_spectatorMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: false, unsetAdaptiveInterpolation: false); - - SetAdaptiveInterpolation(_controllerMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: true); - SetAdaptiveInterpolation(_spectatorMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: false); - - SetupTrackerTransform(); - /* This is called after setting up the tracker transform in the scenario - * the user set additional offsets before this was initialized. */ - if (_queuedTrackerProperties != null) - TrySetGraphicalTrackerLocalProperties(_queuedTrackerProperties.Value); - - void SetupTrackerTransform() - { - _trackerTransform = new GameObject($"{_graphicalTransform.name}_Tracker").transform; - - if (_detachOnStart) - { - _trackerTransform.SetParent(_targetTransform); - } - else - { - Transform trackerParent = _graphicalTransform.IsChildOf(targetTransform) ? _graphicalTransform.parent : targetTransform; - _trackerTransform.SetParent(trackerParent); - } - - _trackerTransform.SetWorldPositionRotationAndScale(_targetTransform.position, _targetTransform.rotation, graphicalTransform.localScale); - } - - IsInitialized = true; - } - - /// - /// Returns if configured transforms are valid. - /// - /// - private bool TransformsAreValid(Transform graphicalTransform, Transform targetTransform) - { - if (graphicalTransform == null) - { - NetworkManagerExtensions.LogError($"Graphical transform cannot be null."); - return false; - } - if (targetTransform == null) - { - NetworkManagerExtensions.LogError($"Target transform on {graphicalTransform} cannot be null."); - return false; - } - if (targetTransform == graphicalTransform) - { - NetworkManagerExtensions.LogError($"Target transform cannot be the same as graphical transform on {graphicalTransform}."); - return false; - } - - return true; - } - - /// - /// Returns true if to use adaptive interpolation. - /// - /// - private bool GetUseAdaptiveInterpolation() - { - if (_cachedAdaptiveInterpolationValue == AdaptiveInterpolationType.Off || _initializingTimeManager.NetworkManager.IsServerOnlyStarted) - return false; - - return true; - } - - /// - /// Gets if to use owner values. - /// - /// OwnerSettings can be used to read determine this as both owner and spectator settings will have the name InitializingNetworkBehaviour. - /// - private bool GetUseOwnerSettings() - { - /* No networkBehaviour indicates an offline smoother. - * The offline smoothers use owner settings. */ - if (_initializingNetworkBehaviour == null) - return true; - - if (_initializingNetworkBehaviour.IsController) - return true; - - return false; - // return _initializingNetworkBehaviour.IsOwner || !_initializingNetworkBehaviour.Owner.IsValid; - } - - /// - /// Updates OwnerDuringPreTick value and caches if needed. - /// - private void SetCaches(bool useOwnerSettings) - { - MovementSettings movementSettings = useOwnerSettings ? _controllerMovementSettings : _spectatorMovementSettings; - - _cachedSmoothedProperties = movementSettings.SmoothedProperties; - _cachedSnapNonSmoothedProperties = movementSettings.SnapNonSmoothedProperties; - _cachedAdaptiveInterpolationValue = movementSettings.AdaptiveInterpolationValue; - _cachedInterpolationValue = movementSettings.InterpolationValue; - - _cachedTeleportThreshold = movementSettings.EnableTeleport ? movementSettings.TeleportThreshold * movementSettings.TeleportThreshold : MoveRates.UNSET_VALUE; - } - - /// - /// Deinitializes this smoother resetting values. - /// - public void Deinitialize() - { - ResetState(); - IsInitialized = false; - } - - /// - /// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off. - /// - public void UpdateRealtimeInterpolation() - { - using (_pm_UpdateRealtimeInterpolation.Auto()) - { - /* If not networked, server is started, or if not - * using adaptive interpolation then use - * flat interpolation.*/ - if (!GetUseAdaptiveInterpolation()) - { - _realtimeInterpolation = _cachedInterpolationValue; - return; - } - - /* If here then adaptive interpolation is being calculated. */ - - TimeManager tm = _initializingTimeManager; - - //Calculate roughly what client state tick would be. - uint localTick = tm.LocalTick; - //This should never be the case; this is a precautionary against underflow. - if (localTick == TimeManager.UNSET_TICK) - return; - - //Ensure at least 1 tick. - long rttTime = tm.RoundTripTime; - uint rttTicks = tm.TimeToTicks(rttTime) + 1; - - uint clientStateTick = localTick - rttTicks; - float interpolation = localTick - clientStateTick; - - //Minimum interpolation is that of adaptive interpolation level. - interpolation += (byte)_cachedAdaptiveInterpolationValue; - - //Ensure interpolation is not more than a second. - if (interpolation > tm.TickRate) - interpolation = tm.TickRate; - else if (interpolation > byte.MaxValue) - interpolation = byte.MaxValue; - - /* Only update realtime interpolation if it changed more than 1 - * tick. This is to prevent excessive changing of interpolation value, which - * could result in noticeable speed ups/slow downs given movement multiplier - * may change when buffer is too full or short. */ - if (_realtimeInterpolation == 0 || Math.Abs(_realtimeInterpolation - interpolation) > 1) - _realtimeInterpolation = (byte)Math.Ceiling(interpolation); - } - } - - /// - /// This should be called when OnStartClient is invoked on the initializing NetworkBehaviour. - /// - /// This does not need to be called if there is no initializing NetworkBehaviour. - public void StartSmoother() - { - DetachOnStart(); - } - - /// - /// This should be called when OnStopClient is invoked on the initializing NetworkBehaviour. - /// - /// This does not need to be called if there is no initializing NetworkBehaviour. - internal void StopSmoother() - { - AttachOnStop(); - } - - /// - /// Called every frame. - /// - public void OnUpdate(float delta) - { - using (_pm_OnUpdate.Auto()) - { - if (!CanSmooth()) - return; - - MoveToTarget(delta); - } - } - - /// - /// Called when the TimeManager invokes OnPreTick. - /// - public void OnPreTick() - { - using (_pm_OnPreTick.Auto()) - { - if (!CanSmooth()) - return; - - SetCaches(GetUseOwnerSettings()); - - _preTicked = true; - DiscardExcessiveTransformPropertiesQueue(); - _graphicsPreTickWorldValues = _graphicalTransform.GetWorldProperties(); - _trackerPreTickWorldValues = GetTrackerWorldProperties(); - } - } - - /// - /// Called when the TimeManager invokes OnPostReplay. - /// - /// Replay tick for the local client. - /// This is dependent on the initializing NetworkBehaviour being set. - public void OnPostReplicateReplay(uint clientTick) - { - using (_pm_OnPostReplicateReplay.Auto()) - { - if (!NetworkObjectIsReconciling()) - return; - - if (_transformProperties.Count == 0) - return; - if (clientTick <= _teleportedTick) - return; - uint firstTick = _transformProperties.Peek().Tick; - //Already in motion to first entry, or first entry passed tick. - if (clientTick <= firstTick) - return; - - ModifyTransformProperties(clientTick, firstTick); - } - } - - /// - /// Called when TimeManager invokes OnPostTick. - /// - /// Local tick of the client. - public void OnPostTick(uint clientTick) - { - using (_pm_OnPostTick.Auto()) - { - if (!CanSmooth()) - return; - if (clientTick <= _teleportedTick) - return; - - //If preticked then previous transform values are known. - if (_preTicked) - { - var trackerProps = GetTrackerWorldProperties(); - //Only needs to be put to pretick position if not detached. - if (!_detachOnStart) - _graphicalTransform.SetWorldProperties(_graphicsPreTickWorldValues); - - DiscardExcessiveTransformPropertiesQueue(); - //SnapNonSmoothedProperties(); - AddTransformProperties(clientTick, trackerProps); - } - //If did not pretick then the only thing we can do is snap to instantiated values. - else - { - //Only set to position if not to detach. - if (!_detachOnStart) - _graphicalTransform.SetWorldProperties(GetTrackerWorldProperties()); - } - } - } - - /// - /// Snaps non-smoothed properties to original positoin if setting is enabled. - /// - private void SnapNonSmoothedProperties() - { - //Feature is not enabled. - if (!_cachedSnapNonSmoothedProperties) - return; - - TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; - - //Everything is smoothed. - if (smoothedProperties == TransformPropertiesFlag.Everything) - return; - - TransformProperties goalValeus = GetTrackerWorldProperties(); - - if (!smoothedProperties.FastContains(TransformPropertiesFlag.Position)) - _graphicalTransform.position = goalValeus.Position; - if (!smoothedProperties.FastContains(TransformPropertiesFlag.Rotation)) - _graphicalTransform.rotation = goalValeus.Rotation; - if (!smoothedProperties.FastContains(TransformPropertiesFlag.Scale)) - _graphicalTransform.localScale = goalValeus.Scale; - } - - /// - /// Returns if the initialized NetworkBehaviour's NetworkObject is reconcilling. - /// - private bool NetworkObjectIsReconciling() => _initializingNetworkBehaviour == null || _initializingNetworkBehaviour.NetworkObject.IsObjectReconciling; - - /// - /// Teleports the graphical to it's starting position and clears the internal movement queue. - /// - public void Teleport() - { - if (_initializingTimeManager == null) - return; - - //If using adaptive interpolation then set the tick which was teleported. - if (_controllerMovementSettings.AdaptiveInterpolationValue != AdaptiveInterpolationType.Off) - { - TimeManager tm = _initializingTimeManager == null ? InstanceFinder.TimeManager : _initializingTimeManager; - if (tm != null) - _teleportedTick = tm.LocalTick; - } - - ClearTransformPropertiesQueue(); - - _graphicalTransform.SetWorldProperties(_trackerTransform.GetWorldProperties()); - } - - /// - /// Clears the pending movement queue. - /// - private void ClearTransformPropertiesQueue() - { - using (_pm_ClearTPQ.Auto()) - { - _transformProperties.Clear(); - //Also unset move rates since there is no more queue. - _moveRates = new(MoveRates.UNSET_VALUE); - } - } - - /// - /// Discards datas over interpolation limit from movement queue. - /// - private void DiscardExcessiveTransformPropertiesQueue() - { - using (_pm_DiscardTPQ.Auto()) - { - int propertiesCount = _transformProperties.Count; - int dequeueCount = propertiesCount - (_realtimeInterpolation + MAXIMUM_QUEUED_OVER_INTERPOLATION); - - //If there are entries to dequeue. - if (dequeueCount > 0) - { - TickSmoothingManager.TickTransformProperties ttp = default; - for (int i = 0; i < dequeueCount; i++) - { - ttp = _transformProperties.Dequeue(); - } - - var nextValues = ttp.Properties; - SetMoveRates(nextValues); - } - } - } - - /// - /// Adds a new transform properties and sets move rates if needed. - /// - private void AddTransformProperties(uint tick, TransformProperties properties) - { - using (_pm_AddTP.Auto()) - { - TickSmoothingManager.TickTransformProperties ttp = new(tick, properties); - _transformProperties.Enqueue(ttp); - - //If first entry then set move rates. - if (_transformProperties.Count == 1) - { - TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties(); - SetMoveRates(gfxWorldProperties); - } - } - } - - /// - /// Modifies a transform property for a tick. This does not error check for empty collections. - /// - /// First tick in the queue. If 0 this will be looked up. - private void ModifyTransformProperties(uint clientTick, uint firstTick) - { - using (_pm_ModifyTP.Auto()) - { - int queueCount = _transformProperties.Count; - uint tick = clientTick; - /*Ticks will always be added incremental by 1 so it's safe to jump ahead the difference - * of tick and firstTick. */ - int index = (int)(tick - firstTick); - //Replace with new data. - if (index < queueCount) - { - if (tick != _transformProperties[index].Tick) - { - //Should not be possible. - } - else - { - TransformProperties newProperties = GetTrackerWorldProperties(); - /* Adjust transformProperties to ease into any corrections. - * The corrected value is used the more the index is to the end - * of the queue. */ - /* We want to be fully eased in by the last entry of the queue. */ - - int lastPossibleIndex = queueCount - 1; - int adjustedQueueCount = lastPossibleIndex - 1; - if (adjustedQueueCount < 1) - adjustedQueueCount = 1; - float easePercent = (float)index / adjustedQueueCount; - - //If easing. - if (easePercent < 1f) - { - if (easePercent < 1f) - easePercent = (float)Math.Pow(easePercent, adjustedQueueCount - index); - - TransformProperties oldProperties = _transformProperties[index].Properties; - newProperties.Position = Vector3.Lerp(oldProperties.Position, newProperties.Position, easePercent); - newProperties.Rotation = Quaternion.Lerp(oldProperties.Rotation, newProperties.Rotation, easePercent); - newProperties.Scale = Vector3.Lerp(oldProperties.Scale, newProperties.Scale, easePercent); - } - - _transformProperties[index] = new(tick, newProperties); - } - } - else - { - //This should never happen. - } - } - } - - /// - /// Gets properties of the tracker. - /// - private TransformProperties GetTrackerWorldProperties() - { - /* Return lossyScale if graphical is not attached. Otherwise, - * graphical should retain the tracker localScale so it changes - * with root. */ - - Vector3 scale = _detachOnStart ? _trackerTransform.lossyScale : _trackerTransform.localScale; - return new(_trackerTransform.position, _trackerTransform.rotation, scale); - } - - /// - /// Returns if prediction can be used on this rigidbody. - /// - /// - private bool CanSmooth() - { - //No graphical object is set. - if (_graphicalTransform == null) - return false; - - /* When this is the case the prediction networkTransform exist and is - * configured in a way to smooth the object, therefor this component should not be smoothing. */ - if (_predictionNetworkTransform != null && _predictionNetworkTransform.DoSettingsAllowSmoothing()) - return false; - - return _initializingTimeManager.NetworkManager.IsClientStarted; - } - - /// - /// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties. - /// - private void SetMoveRates(in TransformProperties prevValues) - { - using (_pm_SetMoveRates.Auto()) - { - if (_transformProperties.Count == 0) - { - _moveRates = new(MoveRates.UNSET_VALUE); - return; - } - - TransformProperties nextValues = _transformProperties.Peek().Properties; - - float duration = _tickDelta; - - _moveRates = MoveRates.GetMoveRates(prevValues, nextValues, duration, _cachedTeleportThreshold); - _moveRates.TimeRemaining = duration; - - SetMovementMultiplier(); - } - } - - private void SetMovementMultiplier() - { - if (_moveImmediately) - { - float percent = Mathf.InverseLerp(0, _realtimeInterpolation, _transformProperties.Count); - _movementMultiplier = percent; - - _movementMultiplier = Mathf.Clamp(_movementMultiplier, 0.5f, 1.05f); - } - //For the time being, not moving immediately uses these multiplier calculations. - else - { - /* If there's more in queue than interpolation then begin to move faster based on overage. - * Move 5% faster for every overage. */ - int overInterpolation = _transformProperties.Count - _realtimeInterpolation; - //If needs to be adjusted. - if (overInterpolation != 0) - { - _movementMultiplier += 0.015f * overInterpolation; - } - //If does not need to be adjusted. - else - { - //If interpolation is 1 then slow down just barely to accomodate for frame delta variance. - if (_realtimeInterpolation == 1) - _movementMultiplier = 1f; - } - - _movementMultiplier = Mathf.Clamp(_movementMultiplier, 0.95f, 1.05f); - } - } - - /// - /// Moves transform to target values. - /// - private void MoveToTarget(float delta) - { - using (_pm_MoveToTarget.Auto()) - { - int tpCount = _transformProperties.Count; - - //No data. - if (tpCount == 0) - return; - - if (_moveImmediately) - { - _isMoving = true; - } - else - { - //Enough in buffer to move. - if (tpCount >= _realtimeInterpolation) - { - _isMoving = true; - } - else if (!_isMoving) - { - return; - } - /* If buffer is considerably under goal then halt - * movement. This will allow the buffer to grow. */ - else if (tpCount - _realtimeInterpolation < -4) - { - _isMoving = false; - return; - } - } - - TickSmoothingManager.TickTransformProperties ttp = _transformProperties.Peek(); - - TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties; - - _moveRates.Move(_graphicalTransform, ttp.Properties, smoothedProperties, delta * _movementMultiplier, useWorldSpace: true); - - float tRemaining = _moveRates.TimeRemaining; - //if TimeLeft is <= 0f then transform is at goal. Grab a new goal if possible. - if (tRemaining <= 0f) - { - //Dequeue current entry and if there's another call a move on it. - _transformProperties.Dequeue(); - - //If there are entries left then setup for the next. - if (_transformProperties.Count > 0) - { - SetMoveRates(ttp.Properties); - //If delta is negative then call move again with abs. - if (tRemaining < 0f) - MoveToTarget(Mathf.Abs(tRemaining)); - } - //No remaining, set to snap. - else - { - ClearTransformPropertiesQueue(); - } - } - } - } - - private void DetachOnStart() - { - if (!_detachOnStart) - return; - - TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties(); - _graphicalTransform.SetParent(null); - _graphicalTransform.SetWorldProperties(gfxWorldProperties); - } - - /// - /// Attachs to Target transform is possible. - /// - private void AttachOnStop() - { - //Never detached. - if (!_detachOnStart) - return; - //Graphical is null, nothing can be moved. - if (_graphicalTransform == null) - return; - if (ApplicationState.IsQuitting()) - return; - - /* If not to re-attach or if there's no target to reference - * then the graphical must be destroyed. */ - bool destroy = !_attachOnStop || _targetTransform == null; - //If not to re-attach then destroy graphical if needed. - if (destroy) - { - UnityEngine.Object.Destroy(_graphicalTransform.gameObject); - return; - } - - _graphicalTransform.SetParent(_targetTransform.parent); - _graphicalTransform.SetLocalProperties(_trackerTransform.GetLocalProperties()); - } - - public void ResetState() - { - if (!IsInitialized) - return; - - AttachOnStop(); - - _initializingNetworkBehaviour = null; - _initializingTimeManager = null; - _graphicalTransform = null; - _targetTransform = null; - - _teleportedTick = TimeManager.UNSET_TICK; - _movementMultiplier = 1f; - CollectionCaches.StoreAndDefault(ref _transformProperties); - _moveRates = default; - _preTicked = default; - _queuedTrackerProperties = null; - _trackerPreTickWorldValues = default; - _graphicsPreTickWorldValues = default; - _realtimeInterpolation = default; - _isMoving = default; - - _predictionNetworkTransform = null; - - if (_trackerTransform != null) - UnityEngine.Object.Destroy(_trackerTransform.gameObject); - } - - public void InitializeState() { } - } - #endif -} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta deleted file mode 100644 index 9180bfaf..00000000 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2fea8ea4886baae46875b4349f9389e3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs index c7dda861..4f84f96c 100644 --- a/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs +++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs @@ -11,7 +11,6 @@ namespace FishNet.Component.Transforming.Beta { - #if !THREADED_TICKSMOOTHERS /// /// This class is under regular development and it's API may change at any time. /// @@ -1002,5 +1001,4 @@ public void ResetState() public void InitializeState() { } } - #endif } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Managing/NetworkManager.cs b/Assets/FishNet/Runtime/Managing/NetworkManager.cs index 6bac0da0..93f43be4 100644 --- a/Assets/FishNet/Runtime/Managing/NetworkManager.cs +++ b/Assets/FishNet/Runtime/Managing/NetworkManager.cs @@ -25,7 +25,6 @@ using FishNet.Managing.Statistic; using FishNet.Utility.Performance; using FishNet.Component.ColliderRollback; -using FishNet.Component.Transforming.Beta; using FishNet.Configuring; using FishNet.Configuring.EditorCloning; using FishNet.Managing.Predicting; @@ -121,12 +120,6 @@ public static IReadOnlyList Instances /// ObserverManager for this NetworkManager. /// public ObserverManager ObserverManager { get; private set; } - #if THREADED_TICKSMOOTHERS - /// - /// TickSmoothingManager for this NetworkManager. - /// - public TickSmoothingManager TickSmoothingManager { get; private set; } - #endif /// /// DebugManager for this NetworkManager. /// @@ -325,9 +318,6 @@ private void Awake() TimeManager = GetOrCreateComponent(); SceneManager = GetOrCreateComponent(); ObserverManager = GetOrCreateComponent(); - #if THREADED_TICKSMOOTHERS - TickSmoothingManager = GetOrCreateComponent(); - #endif RollbackManager = GetOrCreateComponent(); PredictionManager = GetOrCreateComponent(); StatisticsManager = GetOrCreateComponent(); @@ -372,9 +362,6 @@ private void InitializeComponents() SceneManager.InitializeOnce_Internal(this); ObserverManager.InitializeOnce_Internal(this); - #if THREADED_TICKSMOOTHERS - TickSmoothingManager.InitializeOnce_Internal(this); - #endif RollbackManager.InitializeOnce_Internal(this); PredictionManager.InitializeOnce(this); StatisticsManager.InitializeOnce_Internal(this); diff --git a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs index bd4f623f..931c6c63 100644 --- a/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs +++ b/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs @@ -6,9 +6,11 @@ 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 Unity.Mathematics; +using FishNet.Object; using Unity.Profiling; using UnityEngine; using SystemStopwatch = System.Diagnostics.Stopwatch; @@ -285,12 +287,10 @@ public void SetPhysicsTimeScale(float value) /// /// - [NonSerialized] private NetworkTrafficStatistics _networkTrafficStatistics; + 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,95 +686,92 @@ internal void SendPong(NetworkConnection conn, uint clientTick) /// private void IncreaseTick() { - using (_pm_IncreaseTick.Auto()) + bool isClient = NetworkManager.IsClientStarted; + bool isServer = NetworkManager.IsServerStarted; + + double timePerSimulation = isServer ? TickDelta : _adjustedTickDelta; + if (timePerSimulation == 0d) { - bool isClient = NetworkManager.IsClientStarted; - bool isServer = NetworkManager.IsServerStarted; + NetworkManager.LogWarning($"Simulation delta cannot be 0. Network timing will not continue."); + return; + } - double timePerSimulation = isServer ? TickDelta : _adjustedTickDelta; - if (timePerSimulation == 0d) - { - NetworkManager.LogWarning($"Simulation delta cannot be 0. Network timing will not continue."); - return; - } + double time = Time.unscaledDeltaTime; - double time = Time.unscaledDeltaTime; + _elapsedTickTime += time; + FrameTicked = _elapsedTickTime >= timePerSimulation; - _elapsedTickTime += time; - FrameTicked = _elapsedTickTime >= timePerSimulation; + // Number of ticks to occur this frame. + int ticksCount = Mathf.FloorToInt((float)(_elapsedTickTime / timePerSimulation)); + if (ticksCount > 1) + _lastMultipleTicksTime = Time.unscaledTime; - // Number of ticks to occur this frame. - int ticksCount = Mathf.FloorToInt((float)(_elapsedTickTime / timePerSimulation)); - if (ticksCount > 1) - _lastMultipleTicksTime = Time.unscaledTime; + if (_allowTickDropping) + { + // If ticks require dropping. Set exactly to maximum ticks. + if (ticksCount > _maximumFrameTicks) + _elapsedTickTime = timePerSimulation * (double)_maximumFrameTicks; + } - if (_allowTickDropping) + bool variableTiming = _timingType == TimingType.Variable; + bool frameTicked = FrameTicked; + float tickDelta = (float)TickDelta * GetPhysicsTimeScale(); + + do + { + if (frameTicked) { - // If ticks require dropping. Set exactly to maximum ticks. - if (ticksCount > _maximumFrameTicks) - _elapsedTickTime = timePerSimulation * (double)_maximumFrameTicks; + using (_pm_OnPreTick.Auto()) + OnPreTick?.Invoke(); } - bool variableTiming = _timingType == TimingType.Variable; - bool frameTicked = FrameTicked; - float tickDelta = (float)TickDelta * GetPhysicsTimeScale(); + /* 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); - do + if (frameTicked) { - if (frameTicked) - { - using (_pm_OnPreTick.Auto()) - OnPreTick?.Invoke(); - } + // Tell predicted objecs to reconcile before OnTick. + NetworkManager.PredictionManager.ReconcileToStates(); - /* 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); + using (_pm_OnTick.Auto()) + OnTick?.Invoke(); - if (frameTicked) + if (PhysicsMode == PhysicsMode.TimeManager && tickDelta > 0f) { - // 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(); + InvokeOnSimulation(preSimulation: true, tickDelta); + SimulatePhysics(tickDelta); + InvokeOnSimulation(preSimulation: false, tickDelta); } - // Send out data. - if (frameTicked || variableTiming) - TryIterateData(false); + 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(); + } - if (frameTicked) - { - _elapsedTickTime -= timePerSimulation; - Tick++; - LocalTick++; - } - } while (_elapsedTickTime >= timePerSimulation); - } + // Send out data. + if (frameTicked || variableTiming) + TryIterateData(false); + + if (frameTicked) + { + _elapsedTickTime -= timePerSimulation; + Tick++; + LocalTick++; + } + } while (_elapsedTickTime >= timePerSimulation); } #region Tick conversions. @@ -796,14 +793,12 @@ 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(); @@ -814,7 +809,6 @@ 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; @@ -896,7 +890,6 @@ public double TicksToTime(TickType tickType = TickType.LocalTick) /// /// PreciseTick to convert. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(PreciseTick pt) { double tickTime = TicksToTime(pt.Tick); @@ -909,7 +902,6 @@ public double TicksToTime(PreciseTick pt) /// /// Ticks to convert. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public double TicksToTime(uint ticks) { return TickDelta * (double)ticks; @@ -999,28 +991,16 @@ public double TimePassed(uint previousTick, bool allowNegative = false) /// /// Time to convert as decimal. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint TimeToTicks(double time, double tickDelta, TickRounding rounding = TickRounding.RoundNearest) + public uint TimeToTicks(double time, 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.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); + return (uint)Math.Ceiling(result); } /// @@ -1028,7 +1008,6 @@ 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; @@ -1040,7 +1019,6 @@ public uint TimeToTicks(long time, TickRounding rounding = TickRounding.RoundNea /// /// Time to convert. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public PreciseTick TimeToPreciseTick(double time) => time.AsPreciseTick(TickDelta); /// @@ -1108,38 +1086,35 @@ 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) { - using (_pm_TryIterateData.Auto()) + if (incoming) { - 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); - } + /* 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/Object/Prediction/MoveRates.cs b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs index c4a88970..e647b7ef 100644 --- a/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs +++ b/Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs @@ -1,10 +1,6 @@ -using System.Runtime.CompilerServices; -using FishNet.Utility.Extension; -using GameKit.Dependencies.Utilities; -using Unity.Mathematics; -using UnityEngine; +using GameKit.Dependencies.Utilities; using Unity.Profiling; -using UnityEngine.Jobs; +using UnityEngine; using UnityEngine.Scripting; namespace FishNet.Object.Prediction @@ -15,14 +11,6 @@ namespace FishNet.Object.Prediction [Preserve] public struct MoveRates { - #region Private Profiler Markers - - private static readonly ProfilerMarker _pm_GetMoveRatesFull = new ProfilerMarker("MoveRates.GetMoveRates(float3, float3, quaternion, quaternion, float3, float3, float, float)"); - private static readonly ProfilerMarker _pm_GetMoveRatesVec = new ProfilerMarker("MoveRates.GetMoveRates(float3, float3, float, float)"); - private static readonly ProfilerMarker _pm_Move = new ProfilerMarker("MoveRates.Move(TransformAccess, TransformPropertiesFlag, float3, float, quaternion, float, float3, float, float, bool)"); - - #endregion - /// /// Rate at which to move Position. /// @@ -114,22 +102,25 @@ public MoveRates(float position, float rotation, float scale, float timeRemainin /// public bool IsScaleInstantValue => Scale == INSTANT_VALUE; + #region Private Profiler Markers + private static readonly ProfilerMarker _pm_GetMoveRatesFull = new("MoveRates.GetMoveRates(Vector3, Vector3, Quaternion, Quaternion, Vector3, Vector3, float, float)"); + private static readonly ProfilerMarker _pm_GetMoveRatesVec = new("MoveRates.GetMoveRates(Vector3, Vector3, float, float)"); + private static readonly ProfilerMarker _pm_Move = new("MoveRates.Move(Transform, TransformPropertiesFlag, Vector3, float, Quaternion, float, Vector3, float, float, bool)"); + #endregion + /// /// Sets all rates to instant. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetInstantRates() => Update(INSTANT_VALUE); /// /// Sets all rates to the same value. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float value) => Update(value, value, value); /// /// Sets rates for each property. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale) { Position = position; @@ -142,7 +133,6 @@ public void Update(float position, float rotation, float scale) /// /// Sets rates for each property. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale, float timeRemaining) { Position = position; @@ -156,13 +146,11 @@ public void Update(float position, float rotation, float scale, float timeRemain /// /// Updates to new values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(MoveRates moveRates) => Update(moveRates.Position, moveRates.Rotation, moveRates.Scale, moveRates.TimeRemaining); /// /// Updates to new values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(MoveRatesCls moveRates) => Update(moveRates.Position, moveRates.Rotation, moveRates.Scale, moveRates.TimeRemaining); /// @@ -178,91 +166,38 @@ public void ResetState() /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetWorldMoveRates(Transform from, Transform to, float duration, float teleportThreshold) { - from.GetPositionAndRotation(out var fromPos, out var fromRot); - to.GetPositionAndRotation(out var toPos, out var toRot); - return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); - } - - /// - /// Returns a new MoveRates based on previous values, and a transforms current position. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MoveRates GetWorldMoveRates(TransformAccess from, TransformAccess to, float duration, float teleportThreshold) - { - from.GetPositionAndRotation(out var fromPos, out var fromRot); - to.GetPositionAndRotation(out var toPos, out var toRot); - return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); + return GetMoveRates(from.position, to.position, from.rotation, to.rotation, from.localScale, to.localScale, duration, teleportThreshold); } /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetLocalMoveRates(Transform from, Transform to, float duration, float teleportThreshold) { - from.GetPositionAndRotation(out var fromPos, out var fromRot); - to.GetPositionAndRotation(out var toPos, out var toRot); - return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); - } - - /// - /// Returns a new MoveRates based on previous values, and a transforms current position. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MoveRates GetLocalMoveRates(TransformAccess from, TransformAccess to, float duration, float teleportThreshold) - { - from.GetPositionAndRotation(out var fromPos, out var fromRot); - to.GetPositionAndRotation(out var toPos, out var toRot); - return GetMoveRates(fromPos, toPos, fromRot, toRot, from.localScale, to.localScale, duration, teleportThreshold); + return GetMoveRates(from.localPosition, to.localPosition, from.localRotation, to.localRotation, from.localScale, to.localScale, duration, teleportThreshold); } - + /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetWorldMoveRates(TransformProperties prevValues, Transform t, float duration, float teleportThreshold) { - t.GetPositionAndRotation(out var pos, out var rot); - return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); - } - - /// - /// Returns a new MoveRates based on previous values, and a transforms current position. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MoveRates GetWorldMoveRates(TransformProperties prevValues, TransformAccess t, float duration, float teleportThreshold) - { - t.GetPositionAndRotation(out var pos, out var rot); - return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); + return GetMoveRates(prevValues.Position, t.position, prevValues.Rotation, t.rotation, prevValues.Scale, t.localScale, duration, teleportThreshold); } /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetLocalMoveRates(TransformProperties prevValues, Transform t, float duration, float teleportThreshold) { - t.GetLocalPositionAndRotation(out var pos, out var rot); - return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); - } - - /// - /// Returns a new MoveRates based on previous values, and a transforms current position. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MoveRates GetLocalMoveRates(TransformProperties prevValues, TransformAccess t, float duration, float teleportThreshold) - { - t.GetCorrectLocalPositionAndRotation(out var pos, out var rot); - return GetMoveRates(prevValues.Position, pos, prevValues.Rotation, rot, prevValues.Scale, t.localScale, duration, teleportThreshold); + return GetMoveRates(prevValues.Position, t.localPosition, prevValues.Rotation, t.localRotation, prevValues.Scale, t.localScale, duration, teleportThreshold); } /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MoveRates GetMoveRates(TransformProperties prevValues, TransformProperties nextValues, float duration, float teleportThreshold) { return GetMoveRates(prevValues.Position, nextValues.Position, prevValues.Rotation, nextValues.Rotation, prevValues.Scale, nextValues.Scale, duration, teleportThreshold); @@ -271,8 +206,7 @@ public static MoveRates GetMoveRates(TransformProperties prevValues, TransformPr /// /// Returns a new MoveRates based on previous values, and a transforms current position. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MoveRates GetMoveRates(float3 fromPosition, float3 toPosition, quaternion fromRotation, quaternion toRotation, float3 fromScale, float3 toScale, float duration, float teleportThreshold) + public static MoveRates GetMoveRates(Vector3 fromPosition, Vector3 toPosition, Quaternion fromRotation, Quaternion toRotation, Vector3 fromScale, Vector3 toScale, float duration, float teleportThreshold) { using (_pm_GetMoveRatesFull.Auto()) { @@ -298,8 +232,7 @@ public static MoveRates GetMoveRates(float3 fromPosition, float3 toPosition, qua /// /// Gets a move rate for two Vector3s. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float GetMoveRate(float3 fromPosition, float3 toPosition, float duration, float teleportThreshold) + public static float GetMoveRate(Vector3 fromPosition, Vector3 toPosition, float duration, float teleportThreshold) { using (_pm_GetMoveRatesVec.Auto()) { @@ -325,8 +258,7 @@ public static float GetMoveRate(float3 fromPosition, float3 toPosition, float du /// /// Gets a move rate for two Quaternions. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float GetMoveRate(quaternion fromRotation, quaternion toRotation, float duration) + public static float GetMoveRate(Quaternion fromRotation, Quaternion toRotation, float duration) { float rate = toRotation.GetRate(fromRotation, duration, out _); float rotationRate = rate.SetIfUnderTolerance(0.2f, INSTANT_VALUE); @@ -336,7 +268,6 @@ public static float GetMoveRate(quaternion fromRotation, quaternion toRotation, /// /// Moves transform to target values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) { if (!IsValid) @@ -345,24 +276,10 @@ public void Move(Transform movingTransform, TransformProperties goalProperties, Move(movingTransform, TransformPropertiesFlag.Everything, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); TimeRemaining -= delta; } - - /// - /// Moves transform to target values. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Move(TransformAccess movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) - { - if (!IsValid) - return; - - Move(movingTransform, TransformPropertiesFlag.Everything, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); - TimeRemaining -= delta; - } /// /// Moves transform to target values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) { if (!IsValid) @@ -371,24 +288,11 @@ public void Move(Transform movingTransform, TransformProperties goalProperties, Move(movingTransform, movedProperties, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); TimeRemaining -= delta; } - - /// - /// Moves transform to target values. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Move(TransformAccess movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) - { - if (!IsValid) - return; - - Move(movingTransform, movedProperties, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace); - TimeRemaining -= delta; - } /// /// Moves transform to target values. /// - public static void Move(Transform movingTransform, TransformPropertiesFlag movedProperties, float3 posGoal, float posRate, quaternion rotGoal, float rotRate, float3 scaleGoal, float scaleRate, float delta, bool useWorldSpace) + public static void Move(Transform movingTransform, TransformPropertiesFlag movedProperties, Vector3 posGoal, float posRate, Quaternion rotGoal, float rotRate, Vector3 scaleGoal, float scaleRate, float delta, bool useWorldSpace) { using (_pm_Move.Auto()) { @@ -398,117 +302,80 @@ public static void Move(Transform movingTransform, TransformPropertiesFlag moved bool containsRotation = movedProperties.FastContains(TransformPropertiesFlag.Rotation); bool containsScale = movedProperties.FastContains(TransformPropertiesFlag.Scale); - Vector3 pos; - Quaternion rot; + //World space. if (useWorldSpace) - t.GetPositionAndRotation(out pos, out rot); - else t.GetLocalPositionAndRotation(out pos, out rot); - - if (containsPosition) - pos = MoveTowardsFast(pos, posGoal, posRate, delta); - - if (containsRotation) - rot = RotateTowardsFast(rot, rotGoal, rotRate, delta); - - if (containsPosition || containsRotation) - ApplyPosRot(t, useWorldSpace, pos, rot); - - if (containsScale) { - var scale = t.localScale; - t.localScale = MoveTowardsFast(scale, scaleGoal, scaleRate, delta); + if (containsPosition) + { + if (posRate == INSTANT_VALUE) + { + t.position = posGoal; + } + else if (posRate == UNSET_VALUE) { } + else + { + t.position = Vector3.MoveTowards(t.position, posGoal, posRate * delta); + } + } + + if (containsRotation) + { + if (rotRate == INSTANT_VALUE) + { + t.rotation = rotGoal; + } + else if (rotRate == UNSET_VALUE) { } + else + { + t.rotation = Quaternion.RotateTowards(t.rotation, rotGoal, rotRate * delta); + } + } + } + //Local space. + else + { + if (containsPosition) + { + if (posRate == INSTANT_VALUE) + { + t.localPosition = posGoal; + } + else if (posRate == UNSET_VALUE) { } + else + { + t.localPosition = Vector3.MoveTowards(t.localPosition, posGoal, posRate * delta); + } + } + + if (containsRotation) + { + if (rotRate == INSTANT_VALUE) + { + t.localRotation = rotGoal; + } + else if (rotRate == UNSET_VALUE) { } + else + { + t.localRotation = Quaternion.RotateTowards(t.localRotation, rotGoal, rotRate * delta); + } + } } - } - } - - /// - /// Moves transform to target values. - /// - public static void Move(TransformAccess movingTransform, TransformPropertiesFlag movedProperties, float3 posGoal, float posRate, quaternion rotGoal, float rotRate, float3 scaleGoal, float scaleRate, float delta, bool useWorldSpace) - { - using (_pm_Move.Auto()) - { - TransformAccess t = movingTransform; - - bool containsPosition = movedProperties.FastContains(TransformPropertiesFlag.Position); - bool containsRotation = movedProperties.FastContains(TransformPropertiesFlag.Rotation); - bool containsScale = movedProperties.FastContains(TransformPropertiesFlag.Scale); - Vector3 pos; - Quaternion rot; - if (useWorldSpace) - t.GetPositionAndRotation(out pos, out rot); - else t.GetCorrectLocalPositionAndRotation(out pos, out rot); - - if (containsPosition) - pos = MoveTowardsFast(pos, posGoal, posRate, delta); - - if (containsRotation) - rot = RotateTowardsFast(rot, rotGoal, rotRate, delta); - - if (containsPosition || containsRotation) - ApplyPosRot(t, useWorldSpace, pos, rot); - + //Scale always uses local. if (containsScale) { - var scale = t.localScale; - t.localScale = MoveTowardsFast(scale, scaleGoal, scaleRate, delta); + if (scaleRate == INSTANT_VALUE) + { + t.localScale = scaleGoal; + } + else if (scaleRate == UNSET_VALUE) { } + else + { + t.localScale = Vector3.MoveTowards(t.localScale, scaleGoal, scaleRate * delta); + } } } } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static float3 MoveTowardsFast(float3 current, float3 goal, float rate, float delta) - { - if (rate == INSTANT_VALUE) return goal; - if (rate == UNSET_VALUE) return current; - - float3 diff = goal - current; - float maxDelta = math.max(0f, rate * delta); - - float lenSq = math.lengthsq(diff); - if (lenSq <= maxDelta * maxDelta) return goal; - - float invLen = math.rsqrt(lenSq); // 1 / sqrt(lenSq) - float t = math.min(maxDelta * invLen, 1f); - return current + diff * t; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static quaternion RotateTowardsFast(quaternion current, quaternion goal, float rate, float delta) - { - if (rate == INSTANT_VALUE) return goal; - if (rate == UNSET_VALUE) return current; - - float maxDelta = math.max(0f, rate * delta); - - float dot = math.dot(current.value, goal.value); - float c = math.saturate(math.abs(dot)); // min(|dot|, 1) - - float angle = math.degrees(2f * math.acos(c)); - if (angle <= maxDelta) return goal; - - float t = math.min(1f, maxDelta / angle); - return math.slerp(current, goal, t); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void ApplyPosRot(Transform t, bool worldSpace, float3 pos, quaternion rot) - { - if (worldSpace) - t.SetPositionAndRotation(pos, rot); - else - t.SetLocalPositionAndRotation(pos, rot); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void ApplyPosRot(TransformAccess t, bool worldSpace, float3 pos, quaternion rot) - { - if (worldSpace) - t.SetPositionAndRotation(pos, rot); - else - t.SetCorrectLocalPositionAndRotation(pos, rot); - } } /// @@ -562,60 +429,39 @@ public class MoveRatesCls : IResettable /// /// Sets all rates to instant. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetInstantRates() => _moveRates.SetInstantRates(); /// /// Sets all rates to the same value. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float value) => _moveRates.Update(value); /// /// Updates values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale) => _moveRates.Update(position, rotation, scale); /// /// Updates values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float position, float rotation, float scale, float timeRemaining) => _moveRates.Update(position, rotation, scale, timeRemaining); /// /// Updaes values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(MoveRatesCls mr) => _moveRates.Update(mr.Position, mr.Rotation, mr.Scale); - - /// - /// Moves transform to target values. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Move(Transform movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, delta, useWorldSpace); /// /// Moves transform to target values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Move(TransformAccess movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, delta, useWorldSpace); + public void Move(Transform movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, delta, useWorldSpace); /// /// Moves transform to target values. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Move(Transform movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, movedProperties, delta, useWorldSpace); - - /// - /// Moves transform to target values. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Move(TransformAccess movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, movedProperties, delta, useWorldSpace); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ResetState() => _moveRates.ResetState(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void InitializeState() { } } -} +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Object/TransformProperties.cs b/Assets/FishNet/Runtime/Object/TransformProperties.cs index 34f26d0c..7ee1d7ca 100644 --- a/Assets/FishNet/Runtime/Object/TransformProperties.cs +++ b/Assets/FishNet/Runtime/Object/TransformProperties.cs @@ -1,8 +1,6 @@ using System; using GameKit.Dependencies.Utilities; -using Unity.Mathematics; using UnityEngine; -using UnityEngine.Jobs; namespace FishNet.Object { @@ -15,17 +13,17 @@ public static class TransformPropertiesExtensions /// public static TransformProperties CreateDirections(this TransformProperties prevProperties, TransformProperties nextProperties, uint divisor = 1) { - float3 position = (nextProperties.Position - prevProperties.Position) / divisor; + Vector3 position = (nextProperties.Position - prevProperties.Position) / divisor; - quaternion rotation = nextProperties.Rotation.Subtract(prevProperties.Rotation); + Quaternion rotation = nextProperties.Rotation.Subtract(prevProperties.Rotation); //If more than 1 tick span then get a portion of the rotation. if (divisor > 1) { float percent = 1f / (float)divisor; - rotation = math.nlerp(quaternion.identity, nextProperties.Rotation, percent); + rotation = Quaternion.Lerp(Quaternion.identity, nextProperties.Rotation, percent); } - float3 scale = (nextProperties.Scale - prevProperties.Scale) / divisor; + Vector3 scale = (nextProperties.Scale - prevProperties.Scale) / divisor; return new(position, rotation, scale); } @@ -54,12 +52,12 @@ public static void SetWorldProperties(this TransformProperties tp, Transform t) [Serializable] public class TransformPropertiesCls : IResettable { - public float3 Position; - public quaternion Rotation; - public float3 LocalScale; + public Vector3 Position; + public Quaternion Rotation; + public Vector3 LocalScale; public TransformPropertiesCls() { } - public TransformPropertiesCls(float3 position, quaternion rotation, float3 localScale) + public TransformPropertiesCls(Vector3 position, Quaternion rotation, Vector3 localScale) { Position = position; Rotation = rotation; @@ -70,7 +68,7 @@ public void InitializeState() { } public void ResetState() { - Update(float3.zero, quaternion.identity, float3.zero); + Update(Vector3.zero, Quaternion.identity, Vector3.zero); } public void Update(Transform t) @@ -88,12 +86,12 @@ public void Update(TransformProperties tp) Update(tp.Position, tp.Rotation, tp.Scale); } - public void Update(float3 position, quaternion rotation) + public void Update(Vector3 position, Quaternion rotation) { Update(position, rotation, LocalScale); } - public void Update(float3 position, quaternion rotation, float3 localScale) + public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) { Position = position; Rotation = rotation; @@ -105,7 +103,7 @@ public void Update(float3 position, quaternion rotation, float3 localScale) /// public bool ValuesEquals(TransformPropertiesCls properties) { - return Position.Equals(properties.Position) && Rotation.Equals(properties.Rotation) && LocalScale.Equals(properties.LocalScale); + return Position == properties.Position && Rotation == properties.Rotation && LocalScale == properties.LocalScale; } /// @@ -122,64 +120,29 @@ public TransformProperties ToStruct() [Serializable] public struct TransformProperties { - public float3 Position; - public quaternion Rotation; + public Vector3 Position; + public Quaternion Rotation; [Obsolete("Use Scale.")] //Remove V5 - public float3 LocalScale => Scale; - public float3 Scale; - public byte IsValidByte; - + public Vector3 LocalScale => Scale; + public Vector3 Scale; /// /// Becomes true when values are set through update or constructor. /// - public bool IsValid - { - get => IsValidByte != 0; - set => IsValidByte = (byte)(value ? 1 : 0); - } + public bool IsValid; - public TransformProperties(float3 position, quaternion rotation, float3 localScale) + public TransformProperties(Vector3 position, Quaternion rotation, Vector3 localScale) { Position = position; Rotation = rotation; Scale = localScale; - IsValidByte = 1; + IsValid = true; } /// - /// Creates a TransformProperties with default position and rotation, with float3.one scale. + /// Creates a TransformProperties with default position and rotation, with Vector3.one scale. /// - public static TransformProperties GetTransformDefault() => new(float3.zero, quaternion.identity, new float3(1f, 1f, 1f)); - public static TransformProperties GetOffsetDefault() => new(float3.zero, quaternion.identity, float3.zero); + public static TransformProperties GetTransformDefault() => new(Vector3.zero, Quaternion.identity, Vector3.one); - public static TransformProperties operator +(TransformProperties a, TransformProperties b) - { - if (!a.IsValid) return b; - if (!b.IsValid) return a; - return new TransformProperties( - a.Position + b.Position, - math.mul(a.Rotation, b.Rotation), - a.Scale * b.Scale); - } - - public static TransformProperties operator -(TransformProperties a, TransformProperties b) - { - if (!a.IsValid) return -b; - if (!b.IsValid) return a; - return new TransformProperties( - a.Position - b.Position, - math.mul(a.Rotation, math.inverse(b.Rotation)), - a.Scale / b.Scale); - } - - public static TransformProperties operator -(TransformProperties a) - { - return new TransformProperties( - -a.Position, - math.inverse(a.Rotation), - 1f / a.Scale); - } - public override string ToString() { return $"Position: {Position.ToString()}, Rotation {Rotation.ToString()}, Scale {Scale.ToString()}"; @@ -192,20 +155,13 @@ public TransformProperties(Transform t) : this(t.position, t.rotation, t.localSc public void ResetState() { - Update(float3.zero, quaternion.identity, float3.zero); + Update(Vector3.zero, Quaternion.identity, Vector3.zero); IsValid = false; } public void Update(Transform t) { - t.GetPositionAndRotation(out var pos, out var rot); - Update(pos, rot, t.localScale); - } - - public void Update(TransformAccess t) - { - t.GetPositionAndRotation(out var pos, out var rot); - Update(pos, rot, t.localScale); + Update(t.position, t.rotation, t.localScale); } public void Update(TransformProperties tp) @@ -213,12 +169,12 @@ public void Update(TransformProperties tp) Update(tp.Position, tp.Rotation, tp.Scale); } - public void Update(float3 position, quaternion rotation) + public void Update(Vector3 position, Quaternion rotation) { Update(position, rotation, Scale); } - public void Update(float3 position, quaternion rotation, float3 localScale) + public void Update(Vector3 position, Quaternion rotation, Vector3 localScale) { Position = position; Rotation = rotation; @@ -233,7 +189,7 @@ public void Update(float3 position, quaternion rotation, float3 localScale) public void Add(TransformProperties tp) { Position += tp.Position; - Rotation = math.mul(Rotation, tp.Rotation); + Rotation *= tp.Rotation; Scale += tp.Scale; } @@ -244,7 +200,7 @@ public void Add(TransformProperties tp) public void Subtract(TransformProperties tp) { Position -= tp.Position; - Rotation = math.mul(Rotation, math.inverse(tp.Rotation)); + Rotation *= Quaternion.Inverse(tp.Rotation); Scale -= tp.Scale; } @@ -253,7 +209,7 @@ public void Subtract(TransformProperties tp) /// public bool ValuesEquals(TransformProperties properties) { - return Position.Equals(properties.Position) && Rotation.Equals(properties.Rotation) && Scale.Equals(properties.Scale); + return Position == properties.Position && Rotation == properties.Rotation && Scale == properties.Scale; } } } \ 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 751eaca6..438b416f 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef @@ -2,10 +2,7 @@ "name": "GameKit.Dependencies", "rootNamespace": "", "references": [ - "GUID:6055be8ebefd69e48b49212b09b47b2f", - "GUID:d8b63aba1907145bea998dd612889d6b", - "GUID:2665a8d13d1b3f18800f46e256720795", - "GUID:e0cd26848372d4e5c891c569017e11f1" + "GUID:6055be8ebefd69e48b49212b09b47b2f" ], "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 d7d65070..b6a56652 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 IReadOnlyDictionary dict, TKey key, out TValue value) + public static bool TryGetValueIL2CPP(this IDictionary 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 IReadOnlyDictionary /// - public static List ValuesToList(this IReadOnlyDictionary dict, bool useCache) + public static List ValuesToList(this IDictionary dict, bool useCache) { List result = useCache ? CollectionCaches.RetrieveList() : new(dict.Count); @@ -43,7 +43,7 @@ public static List ValuesToList(this IReadOnlyDictionary /// Adds values to a list. /// - public static void ValuesToList(this IReadOnlyDictionary dict, ref List result, bool clearLst) + public static void ValuesToList(this IDictionary dict, ref List result, bool clearLst) { if (clearLst) result.Clear(); @@ -55,7 +55,7 @@ public static void ValuesToList(this IReadOnlyDictionary /// Returns keys as a list. /// - public static List KeysToList(this IReadOnlyDictionary dict, bool useCache) + public static List KeysToList(this IDictionary dict, bool useCache) { List result = useCache ? CollectionCaches.RetrieveList() : new(dict.Count); @@ -68,7 +68,7 @@ public static List KeysToList(this IReadOnlyDictionary /// Adds keys to a list. /// - public static void KeysToList(this IReadOnlyDictionary dict, ref List result, bool clearLst) + public static void KeysToList(this IDictionary dict, ref List result, bool clearLst) { result.Clear(); diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs index 5b7a908f..c082ac25 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Floats.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.CompilerServices; using UnityEngine; namespace GameKit.Dependencies.Utilities @@ -17,7 +16,6 @@ public static class Floats /// Float to check against tolerance. /// Tolerance float must be equal to or greater than to change to value. /// Value source is set to when breaking tolerance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SetIfOverTolerance(this float source, float tolerance, float value) { if (source >= tolerance) @@ -32,7 +30,6 @@ public static float SetIfOverTolerance(this float source, float tolerance, float /// Float to check against tolerance. /// Tolerance float must be equal to or less than to change to value. /// Value source is set to when breaking tolerance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SetIfUnderTolerance(this float source, float tolerance, float value) { if (source <= tolerance) @@ -45,7 +42,6 @@ public static float SetIfUnderTolerance(this float source, float tolerance, floa /// Returns how much time is left on an endTime. Returns -1 if no time is left. /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float TimeRemainingValue(this float endTime) { float remaining = endTime - Time.time; @@ -60,7 +56,6 @@ public static float TimeRemainingValue(this float endTime) /// Returns how much time is left on an endTime. Returns -1 if no time is left. /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TimeRemainingValue(this float endTime, bool useFloor = true) { float remaining = endTime - Time.time; @@ -141,7 +136,6 @@ public static float Random01() /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Near(this float a, float b, float tolerance = 0.01f) { return Mathf.Abs(a - b) <= tolerance; @@ -155,7 +149,6 @@ public static bool Near(this float a, float b, float tolerance = 0.01f) /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Clamp(float value, float min, float max, ref bool clamped) { clamped = value < min; @@ -199,7 +192,6 @@ public static void Variance(this float source, float variance, ref float result) /// /// Value to sign. /// Precise sign. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float PreciseSign(float value) { if (value == 0f) @@ -215,7 +207,6 @@ public static float PreciseSign(float value) /// Minimum of range. /// Maximum of range. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InRange(this float source, float rangeMin, float rangeMax) { return source >= rangeMin && source <= rangeMax; diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs index bfac65d1..d302deb4 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Quaternions.cs @@ -1,5 +1,4 @@ -using Unity.Mathematics; -using UnityEngine; +using UnityEngine; namespace GameKit.Dependencies.Utilities { @@ -17,39 +16,16 @@ public static float GetRate(this Quaternion a, Quaternion goal, float duration, angle = a.Angle(goal, true); return angle / (duration * interval); } - - /// - /// Returns how fast an object must rotate over duration to reach goal. - /// - /// Quaternion to measure distance against. - /// How long it should take to move to goal. - /// A multiplier applied towards interval. Typically this is used for ticks passed. - /// - public static float GetRate(this quaternion a, quaternion goal, float duration, out float angle, uint interval = 1, float tolerance = 0f) - { - angle = a.Angle(goal, true); - return angle / (duration * interval); - } /// /// Subtracts b quaternion from a. /// public static Quaternion Subtract(this Quaternion a, Quaternion b) => Quaternion.Inverse(b) * a; - - /// - /// Subtracts b quaternion from a. - /// - public static quaternion Subtract(this quaternion a, quaternion b) => math.mul(math.inverse(b), a); /// /// Adds quaternion b onto quaternion a. /// public static Quaternion Add(this Quaternion a, Quaternion b) => a * b; - - /// - /// Adds quaternion b onto quaternion a. - /// - public static quaternion Add(this quaternion a, quaternion b) => math.mul(a, b); /// /// Returns if two quaternions match. @@ -82,26 +58,5 @@ public static float Angle(this Quaternion a, Quaternion b, bool precise = false) return Quaternion.Angle(a, b); } } - - /// - /// Returns the angle between two quaterions. - /// - /// True to use a custom implementation with no error tolerance. False to use Unity's implementation which may return 0f due to error tolerance, even while there is a difference. - /// - public static float Angle(this quaternion a, quaternion b, bool precise = false) - { - if (!precise) - { - float d = math.dot(a.value, b.value); - float c = math.saturate(math.abs(d)); - return math.degrees(2f * math.acos(c)); - } - - quaternion an = math.normalize(a); - quaternion bn = math.normalize(b); - float dn = math.dot(an.value, bn.value); - float cn = math.saturate(math.abs(dn)); - return math.degrees(2f * math.acos(cn)); - } } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs deleted file mode 100644 index eccd2387..00000000 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/StripedRingQueue.cs +++ /dev/null @@ -1,370 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; -using UnityEngine; - -namespace GameKit.Dependencies.Utilities.Types -{ - /// - /// A striped ring-buffer that stores N independent queues addressed by i = 0..N-1. - /// Backing storage uses NativeList-based stripes with a fixed per-queue capacity. - /// Designed for job-friendly, per-index parallel work without cross contention. - /// - public struct StripedRingQueue : IDisposable - where T : unmanaged - { - /// - /// Backing storage for all stripes; length equals _queueCount * _capacity. - /// - [NativeDisableParallelForRestriction] private NativeList _data; - /// - /// Per-queue head (read index) - /// Advances on dequeue operations modulo _capacity. - /// - [NativeDisableParallelForRestriction] private NativeList _head; - /// - /// Per-queue item count. - /// Always clamped to the range [0.._capacity]. - /// - [NativeDisableParallelForRestriction] private NativeList _count; - /// - /// Compact metadata buffer stored in native memory: - /// [0] = fixed per-queue capacity, [1] = current queue count. - /// - [NativeDisableParallelForRestriction] private NativeArray _meta; - - /// - /// True when internal lists are allocated and usable. - /// - public bool IsCreated - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _data.IsCreated && _head.IsCreated && _count.IsCreated && _meta.IsCreated; - } - /// - /// Fixed capacity per queue (ring size). - /// - public int Capacity - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _meta[0]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private set => _meta[0] = value; - } - /// - /// Number of independent queues (stripes). - /// - public int QueueCount - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _meta[1]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private set => _meta[1] = value; - } - /// - /// Total addressable storage, equal to QueueCount * Capacity. - /// - public int TotalCapacity - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => QueueCount * Capacity; - } - - /// - /// Indexer for direct access by queue index and raw ring index (0..Capacity-1). - /// Does not account for head/count; use GetCount/Clear/Enqueue/Dequeue for logical queue semantics. - /// - public T this[int queueIndex, int simulatedIndex] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - int offset = GetRealOffset(queueIndex, simulatedIndex); - return _data[offset]; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - int offset = GetRealOffset(queueIndex, simulatedIndex); - _data[offset] = value; - } - } - - /// - /// Constructs the striped ring with an initial queue count and per-queue capacity. - /// Allocates NativeList storage and zeroes head/count for all stripes. - /// - public StripedRingQueue(int initialQueueCount, int capacity, Allocator allocator) - { - if (initialQueueCount < 0) throw new ArgumentOutOfRangeException(nameof(initialQueueCount)); - if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); - - _meta = new NativeArray(2, allocator, NativeArrayOptions.UninitializedMemory); - _meta[0] = capacity; - _meta[1] = initialQueueCount; - - _data = new NativeList(math.max(1, initialQueueCount * capacity), allocator); - _head = new NativeList(math.max(1, initialQueueCount), allocator); - _count = new NativeList(math.max(1, initialQueueCount), allocator); - - _data.ResizeUninitialized(initialQueueCount * capacity); - _head.ResizeUninitialized(initialQueueCount); - _count.ResizeUninitialized(initialQueueCount); - - for (int i = 0; i < initialQueueCount; i++) - { - _head[i] = 0; - _count[i] = 0; - } - } - - /// - /// Disposes all internal lists synchronously. - /// Ensure that no jobs are accessing this storage when disposing. - /// - public void Dispose() - { - if (_data.IsCreated) _data.Dispose(); - if (_head.IsCreated) _head.Dispose(); - if (_count.IsCreated) _count.Dispose(); - if (_meta.IsCreated) _meta.Dispose(); - } - - /// - /// Schedules disposal of internal lists and returns a combined JobHandle. - /// Use this to free storage once dependent jobs have completed. - /// - public JobHandle Dispose(JobHandle inputDeps) - { - JobHandle h = inputDeps; - if (_data.IsCreated) h = _data.Dispose(h); - if (_head.IsCreated) h = _head.Dispose(h); - if (_count.IsCreated) h = _count.Dispose(h); - if (_meta.IsCreated) h = _meta.Dispose(h); - return h; - } - - /// - /// Adds a new empty queue (stripe) and returns its index. - /// Grows the data buffer by Capacity and zeroes the stripe's head/count. - /// - public int AddQueue() - { - int capacity = Capacity; - int queueCount = QueueCount; - - int newIndex = queueCount; - - int newDataLen = (newIndex + 1) * capacity; - if (_data.Capacity < newDataLen) _data.Capacity = newDataLen; - _data.ResizeUninitialized(newDataLen); - - _head.Add(0); - _count.Add(0); - - QueueCount = newIndex + 1; - return newIndex; - } - - /// - /// Removes the queue at the given index by swapping with the last stripe, - /// then shrinking storage by one stripe. Data swap is O(Capacity). - /// - public void RemoveQueueAtSwapBack(int index) - { - int queueCount = QueueCount; - int capacity = Capacity; - - int last = queueCount - 1; - if ((uint)index >= (uint)queueCount) - throw new ArgumentOutOfRangeException(nameof(index)); - if (last < 0) - return; - - if (index != last) - { - int a = index * capacity; - int b = last * capacity; - - for (int k = 0; k < capacity; k++) - { - (_data[a + k], _data[b + k]) = (_data[b + k], _data[a + k]); - } - } - - _data.ResizeUninitialized(_data.Length - capacity); - _head.RemoveAtSwapBack(index); - _count.RemoveAtSwapBack(index); - - QueueCount = last; - } - - /// - /// Returns the current number of items in queue i. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetCount(int i) => _count[i]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int BaseOffset(int i) => i * Capacity; - - /// - /// Returns the real index of the collection using a simulated index. - /// - /// - /// - /// True to allow an index be returned from an unused portion of the buffer so long as it is within bounds. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetRealOffset(int queueIndex, int simulatedIndex, bool allowUnusedBuffer = false) - { - int capacity = Capacity; - int queueCount = QueueCount; - - if ((uint)queueIndex >= (uint)queueCount) - throw new ArgumentOutOfRangeException(nameof(queueIndex)); - if ((uint)simulatedIndex >= (uint)capacity) - throw new ArgumentOutOfRangeException(nameof(simulatedIndex)); - - int count = _count[queueIndex]; - if (simulatedIndex >= count && !allowUnusedBuffer) - throw new ArgumentOutOfRangeException( - nameof(simulatedIndex), - $"Index {simulatedIndex} >= item count {count} in queue {queueIndex}"); - - int head = _head[queueIndex]; - int offset = (head + simulatedIndex) % capacity; - return BaseOffset(queueIndex) + offset; - } - - /// - /// Clears queue i by resetting head and count to zero. - /// Stored values remain but are considered invalid. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(int i) - { - _head[i] = 0; - _count[i] = 0; - } - - /// - /// Enqueues 'value' into queue i; overwrites the oldest item when full. - /// Main-thread only unless no concurrent access to the same i is guaranteed. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Enqueue(int i, in T value) - { - int capacity = Capacity; - - int h = _head[i]; - int c = _count[i]; - int baseOff = BaseOffset(i); - int tail = (h + c) % capacity; - - _data[baseOff + tail] = value; - - if (c < capacity) - { - _count[i] = c + 1; - } - else - { - _head[i] = (h + 1) % capacity; // overwrite oldest - } - } - - /// - /// Tries to dequeue one item from queue i into 'value'. - /// Returns false when the queue is empty. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryDequeue(int i, out T value) - { - int c = _count[i]; - if (c == 0) - { - value = default; - return false; - } - - int capacity = Capacity; - - int h = _head[i]; - int baseOff = BaseOffset(i); - value = _data[baseOff + h]; - - _head[i] = (h + 1) % capacity; - _count[i] = c - 1; - return true; - } - - /// - /// Dequeues up to 'n' items from queue i and returns how many were removed. - /// The last removed item (if any) is written to 'last'. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int DequeueUpTo(int i, int n, out T last) - { - int c = _count[i]; - int drop = math.clamp(n, 0, c); - if (drop == 0) - { - last = default; - return 0; - } - - int capacity = Capacity; - - int h = _head[i]; - int baseOff = BaseOffset(i); - int lastIdx = (h + drop - 1) % capacity; - - last = _data[baseOff + lastIdx]; - - _head[i] = (h + drop) % capacity; - _count[i] = c - drop; - return drop; - } - - /// - /// Peeks the next entry from i queue. - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T Peek(int i) - { - int c = _count[i]; - if (c == 0) - throw new InvalidOperationException($"{nameof(StripedRingQueue)} of type {typeof(T).Name} is empty."); - - int h = _head[i]; - int baseOff = BaseOffset(i); - return _data[baseOff + h]; - } - - /// - /// Tries to peek the next entry from queue i. - /// - /// - /// Peeked entry. - /// True if an entry existed to peek. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryPeek(int i, out T result) - { - int c = _count[i]; - if (c == 0) - { - result = default; - return false; - } - - int h = _head[i]; - int baseOff = BaseOffset(i); - result = _data[baseOff + h]; - return true; - } - } -} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs index db6ac79c..18fa0224 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Vectors.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.CompilerServices; -using Unity.Mathematics; using UnityEngine; namespace GameKit.Dependencies.Utilities @@ -24,31 +23,15 @@ public static class Vectors /// How long it should take to move to goal. /// A multiplier applied towards interval. Typically this is used for ticks passed. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetRate(this Vector3 a, Vector3 b, float duration, out float distance, uint interval = 1) { distance = Vector3.Distance(a, b); return distance / (duration * interval); } - - /// - /// Returns how fast an object must move over duration to reach goal. - /// - /// Vector3 to measure distance against. - /// How long it should take to move to goal. - /// A multiplier applied towards interval. Typically this is used for ticks passed. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float GetRate(this float3 a, float3 b, float duration, out float distance, uint interval = 1) - { - distance = math.distance(a, b); - return distance / (duration * interval); - } /// /// Adds a Vector2 X/Y onto a Vector3. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Add(this Vector3 v3, Vector2 v2) { return v3 + new Vector3(v2.x, v2.y, 0f); @@ -57,7 +40,6 @@ public static Vector3 Add(this Vector3 v3, Vector2 v2) /// /// Subtracts a Vector2 X/Y from a Vector3. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Subtract(this Vector3 v3, Vector2 v2) { return v3 - new Vector3(v2.x, v2.y, 0f); @@ -70,7 +52,6 @@ public static Vector3 Subtract(this Vector3 v3, Vector2 v2) /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float InverseLerp(Vector3 a, Vector3 b, Vector3 value) { Vector3 ab = b - a; @@ -85,7 +66,6 @@ public static float InverseLerp(Vector3 a, Vector3 b, Vector3 value) /// Target vector. /// How close the target vector must be to be considered close. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Near(this Vector3 a, Vector3 b, float tolerance = 0.01f) { return Vector3.Distance(a, b) <= tolerance; @@ -96,7 +76,6 @@ public static bool Near(this Vector3 a, Vector3 b, float tolerance = 0.01f) /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNan(this Vector3 source) { return float.IsNaN(source.x) || float.IsNaN(source.y) || float.IsNaN(source.z); @@ -106,7 +85,6 @@ public static bool IsNan(this Vector3 source) /// Lerp between three Vector3 values. /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Lerp3(Vector3 a, Vector3 b, Vector3 c, float percent) { Vector3 r0 = Vector3.Lerp(a, b, percent); @@ -120,7 +98,6 @@ public static Vector3 Lerp3(Vector3 a, Vector3 b, Vector3 c, float percent) /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Lerp3(Vector3[] vectors, float percent) { if (vectors.Length < 3) @@ -138,7 +115,6 @@ public static Vector3 Lerp3(Vector3[] vectors, float percent) /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Multiply(this Vector3 src, Vector3 multiplier) { return new(src.x * multiplier.x, src.y * multiplier.y, src.z * multiplier.z); @@ -225,7 +201,6 @@ public static Vector3 FastNormalize(Vector3 value) /// How long it should take to move to goal. /// A multiplier applied towards interval. Typically this is used for ticks passed. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetRate(this Vector2 a, Vector2 goal, float duration, out float distance, uint interval = 1) { distance = Vector2.Distance(a, goal); @@ -240,7 +215,6 @@ public static float GetRate(this Vector2 a, Vector2 goal, float duration, out fl /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Lerp3(Vector2 a, Vector2 b, Vector2 c, float percent) { Vector2 r0 = Vector2.Lerp(a, b, percent); @@ -254,7 +228,6 @@ public static Vector2 Lerp3(Vector2 a, Vector2 b, Vector2 c, float percent) /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Lerp2(Vector2[] vectors, float percent) { if (vectors.Length < 3) @@ -272,7 +245,6 @@ public static Vector2 Lerp2(Vector2[] vectors, float percent) /// /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Multiply(this Vector2 src, Vector2 multiplier) { return new(src.x * multiplier.x, src.y * multiplier.y); diff --git a/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs b/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs index b92f9bd9..301b71e2 100644 --- a/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs +++ b/Assets/FishNet/Runtime/Utility/Extension/Transforms.cs @@ -3,7 +3,6 @@ using FishNet.Object; using System.Runtime.CompilerServices; using UnityEngine; -using UnityEngine.Jobs; namespace FishNet.Utility.Extension { @@ -11,124 +10,48 @@ namespace FishNet.Utility.Extension public static class TransformFN { /// - /// 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. + /// Sets values of TransformProperties to a transforms world properties. /// public static TransformProperties GetWorldProperties(this Transform t) { - 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); + TransformProperties tp = new(t.position, t.rotation, t.localScale); return tp; } /// - /// Gets values of TransformProperties from the transforms world properties. + /// Sets values of TransformProperties to a transforms world properties. /// public static TransformProperties GetWorldProperties(this Transform t, TransformProperties offset) { - 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); + TransformProperties tp = new(t.position, t.rotation, t.localScale); tp.Add(offset); return tp; } /// - /// Gets values of TransformProperties from the transforms world properties. + /// Sets values of TransformProperties to a transforms world properties. /// public static TransformPropertiesCls GetWorldPropertiesCls(this Transform t) { - 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); + TransformPropertiesCls tp = new(t.position, t.rotation, t.localScale); return tp; } /// - /// Gets values of TransformProperties from the transforms world properties. + /// Sets values of TransformProperties to a transforms world properties. /// public static TransformProperties GetLocalProperties(this Transform t) { - 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); + TransformProperties tp = new(t.localPosition, t.localRotation, t.localScale); return tp; } /// - /// Gets values of TransformProperties from the transforms world properties. + /// Sets values of TransformProperties to a transforms world properties. /// public static TransformPropertiesCls GetLocalPropertiesCls(this Transform t) { - 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); + TransformPropertiesCls tp = new(t.localPosition, t.localRotation, t.localScale); return tp; } @@ -147,10 +70,8 @@ public static void SetTransformOffsets(this Transform t, Transform target, ref V { if (target == null) return; - t.GetPositionAndRotation(out var tPos, out var tRot); - target.GetPositionAndRotation(out var targetPos, out var targetRot); - pos = targetPos - tPos; - rot = targetRot * Quaternion.Inverse(tRot); + pos = target.position - t.position; + rot = target.rotation * Quaternion.Inverse(t.rotation); } /// @@ -162,9 +83,7 @@ public static TransformProperties GetTransformOffsets(this Transform t, Transfor if (target == null) return default; - 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); + return new(target.position - t.position, target.rotation * Quaternion.Inverse(t.rotation), target.localScale - t.localScale); } /// @@ -172,16 +91,8 @@ public static TransformProperties GetTransformOffsets(this Transform t, Transfor /// public static void SetLocalProperties(this Transform t, TransformPropertiesCls tp) { - 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.localPosition = tp.Position; + t.localRotation = tp.Rotation; t.localScale = tp.LocalScale; } @@ -190,16 +101,8 @@ public static void SetLocalProperties(this TransformAccess t, TransformPropertie /// public static void SetLocalProperties(this Transform t, TransformProperties tp) { - 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.localPosition = tp.Position; + t.localRotation = tp.Rotation; t.localScale = tp.Scale; } @@ -208,16 +111,8 @@ public static void SetLocalProperties(this TransformAccess t, TransformPropertie /// public static void SetWorldProperties(this Transform t, TransformPropertiesCls tp) { - 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.position = tp.Position; + t.rotation = tp.Rotation; t.localScale = tp.LocalScale; } @@ -226,16 +121,8 @@ public static void SetWorldProperties(this TransformAccess t, TransformPropertie /// public static void SetWorldProperties(this Transform t, TransformProperties tp) { - 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.position = tp.Position; + t.rotation = tp.Rotation; t.localScale = tp.Scale; } @@ -244,15 +131,8 @@ public static void SetWorldProperties(this TransformAccess t, TransformPropertie /// public static void SetLocalPositionAndRotation(this Transform t, Vector3 pos, Quaternion 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); + t.localPosition = pos; + t.localRotation = rot; } /// @@ -260,16 +140,8 @@ public static void SetLocalPositionAndRotation(this TransformAccess t, Vector3 p /// public static void SetLocalPositionRotationAndScale(this Transform t, Vector3 pos, Quaternion rot, Vector3 scale) { - 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.localPosition = pos; + t.localRotation = rot; t.localScale = scale; } @@ -279,29 +151,8 @@ public static void SetLocalPositionRotationAndScale(this TransformAccess t, Vect public static void SetLocalPositionRotationAndScale(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale) { if (nullablePos.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.localPosition = nullablePos.Value; + if (nullableRot.HasValue) t.localRotation = nullableRot.Value; if (nullableScale.HasValue) t.localScale = nullableScale.Value; @@ -313,29 +164,8 @@ public static void SetLocalPositionRotationAndScale(this TransformAccess t, Vect public static void SetWorldPositionRotationAndScale(this Transform 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; - } - - /// - /// 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.position = nullablePos.Value; + if (nullableRot.HasValue) t.rotation = nullableRot.Value; if (nullableScale.HasValue) t.localScale = nullableScale.Value; @@ -346,56 +176,8 @@ public static void SetWorldPositionRotationAndScale(this TransformAccess t, Vect /// public static void OutLocalPropertyValues(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale) { - 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; - } - + pos = nullablePos == null ? t.localPosition : nullablePos.Value; + rot = nullableRot == null ? t.localRotation : nullableRot.Value; scale = nullableScale == null ? t.localScale : nullableScale.Value; } @@ -404,56 +186,8 @@ public static void OutLocalPropertyValues(this TransformAccess t, Vector3? nulla /// public static void OutWorldPropertyValues(this Transform 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; - } - - /// - /// 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; - } - + pos = nullablePos == null ? t.position : nullablePos.Value; + rot = nullableRot == null ? t.rotation : nullableRot.Value; scale = nullableScale == null ? t.localScale : nullableScale.Value; } } From 74871d084f595046752687b94fea3ec42b1e151f Mon Sep 17 00:00:00 2001 From: Mocretion <33748512+Mocretion@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:08:19 +0100 Subject: [PATCH 23/23] Fix typo in TimeManagerEditor --- .../Runtime/Managing/Timing/Editor/TimeManagerEditor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Assets/FishNet/Runtime/Managing/Timing/Editor/TimeManagerEditor.cs b/Assets/FishNet/Runtime/Managing/Timing/Editor/TimeManagerEditor.cs index c5b2efac..77971d8f 100644 --- a/Assets/FishNet/Runtime/Managing/Timing/Editor/TimeManagerEditor.cs +++ b/Assets/FishNet/Runtime/Managing/Timing/Editor/TimeManagerEditor.cs @@ -59,7 +59,7 @@ public override void OnInspectorGUI() EditorGUILayout.LabelField("Physics", EditorStyles.boldLabel); EditorGUI.indentLevel++; if (_physicsMode.intValue == (int)PhysicsMode.TimeManager) - EditorGUILayout.HelpBox($"Time.fixedDeltaTime will be overriden with TimeManager.TickDelta ({(1f / (float)_tickRate.intValue).ToString("0.###")})", MessageType.Info); + EditorGUILayout.HelpBox($"Time.fixedDeltaTime will be overridden with TimeManager.TickDelta ({(1f / (float)_tickRate.intValue).ToString("0.###")})", MessageType.Info); else EditorGUILayout.HelpBox("If you are using physics interactions be sure to change the PhysicsMode to TimeManager and implement physics within the TimeManager tick events. NetworkTransform may also jitter when not using PhysicsMode.TimeManager.", MessageType.Warning); EditorGUILayout.PropertyField(_physicsMode); @@ -75,4 +75,5 @@ public override void OnInspectorGUI() } } } -#endif \ No newline at end of file + +#endif