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/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);
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..5b400d53
--- /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.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
+{
+ 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..4d21bd49
--- /dev/null
+++ b/Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs
@@ -0,0 +1,1337 @@
+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
+{
+ 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 && 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();
+ _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 && 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
diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef
index 438b416f..751eaca6 100644
--- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef
+++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/GameKit.Dependencies.asmdef
@@ -2,7 +2,10 @@
"name": "GameKit.Dependencies",
"rootNamespace": "",
"references": [
- "GUID:6055be8ebefd69e48b49212b09b47b2f"
+ "GUID:6055be8ebefd69e48b49212b09b47b2f",
+ "GUID:d8b63aba1907145bea998dd612889d6b",
+ "GUID:2665a8d13d1b3f18800f46e256720795",
+ "GUID:e0cd26848372d4e5c891c569017e11f1"
],
"includePlatforms": [],
"excludePlatforms": [],
diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs
index b6a56652..d7d65070 100644
--- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs
+++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Dictionaries.cs
@@ -8,7 +8,7 @@ public static class DictionaryFN
/// Uses a hacky way to TryGetValue on a dictionary when using IL2CPP and on mobile.
/// This is to support older devices that don't properly handle IL2CPP builds.
///
- public static bool TryGetValueIL2CPP(this IDictionary dict, TKey key, out TValue value)
+ public static bool TryGetValueIL2CPP(this IReadOnlyDictionary dict, TKey key, out TValue value)
{
#if ENABLE_IL2CPP && UNITY_IOS || UNITY_ANDROID
if (dict.ContainsKey(key))
@@ -30,7 +30,7 @@ public static bool TryGetValueIL2CPP(this IDictionary
///
- public static List ValuesToList(this IDictionary dict, bool useCache)
+ public static List ValuesToList(this IReadOnlyDictionary dict, bool useCache)
{
List result = useCache ? CollectionCaches.RetrieveList() : new(dict.Count);
@@ -43,7 +43,7 @@ public static List ValuesToList(this IDictionary
/// Adds values to a list.
///
- public static void ValuesToList(this IDictionary dict, ref List result, bool clearLst)
+ public static void ValuesToList(this IReadOnlyDictionary dict, ref List result, bool clearLst)
{
if (clearLst)
result.Clear();
@@ -55,7 +55,7 @@ public static void ValuesToList(this IDictionary dic
///
/// Returns keys as a list.
///
- public static List KeysToList(this IDictionary dict, bool useCache)
+ public static List KeysToList(this IReadOnlyDictionary dict, bool useCache)
{
List result = useCache ? CollectionCaches.RetrieveList() : new(dict.Count);
@@ -68,7 +68,7 @@ public static List KeysToList(this IDictionary
///
/// Adds keys to a list.
///
- public static void KeysToList(this IDictionary dict, ref List result, bool clearLst)
+ public static void KeysToList(this IReadOnlyDictionary dict, ref List result, bool clearLst)
{
result.Clear();
diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/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..eccd2387
--- /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.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 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;
}
}