[Add] FishNet

This commit is contained in:
2026-03-30 20:11:57 +07:00
parent ee793a3361
commit c22c08753a
1797 changed files with 197950 additions and 1 deletions
@@ -0,0 +1,34 @@
namespace FishNet.Component.Transforming
{
public enum AdaptiveInterpolationType
{
/// <summary>
/// Adaptive interpolation is disabled. An exact interpolation value is used.
/// </summary>
Off = 0,
/// <summary>
/// Visual disturbances caused by desynchronization are definite without predicting future states.
/// </summary>
ExtremelyLow = 1,
/// <summary>
/// Visual disturbances caused by desynchronization are likely without predicting future states.
/// </summary>
VeryLow = 2,
/// <summary>
/// Visual disturbances caused by desynchronization are still possible but less likely.
/// </summary>
Low = 3,
/// <summary>
/// Visual disturbances caused by desynchronization are likely without predicting a small amount of future states.
/// </summary>
Moderate = 4,
/// <summary>
/// Visual disturbances caused by desynchronization are very unlikely. Graphics are using a generous amount interpolation.
/// </summary>
High = 5,
/// <summary>
/// Visual disturbances caused by desynchronization are extremely unlikely. Graphics are using a generous amount interpolation.
/// </summary>
VeryHigh = 6
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 612b76499aa863b4b8a1b773a466cc7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/AdaptiveInterpolationType.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 43bf94bbbaa20b6489f07cd1abe42972
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,51 @@
#if UNITY_EDITOR && THREADED_TICKSMOOTHERS
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEditor;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta.Editing
{
[CustomPropertyDrawer(typeof(MovementSettings))]
public class MovementSettingsDrawer : PropertyDrawer
{
private PropertyDrawerTool _propertyDrawer;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
_propertyDrawer = new(position);
// _propertyDrawer.DrawLabel(label, FontStyle.Bold);
EditorGUI.indentLevel++;
SerializedProperty enableTeleport = property.FindPropertyRelative("EnableTeleport");
SerializedProperty teleportThreshold = property.FindPropertyRelative("TeleportThreshold");
SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue");
SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue");
SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties");
SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties");
_propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport");
if (enableTeleport.boolValue == true)
_propertyDrawer.DrawProperty(teleportThreshold, "Teleport Threshold", indent: 1);
_propertyDrawer.DrawProperty(adaptiveInterpolationValue, "Adaptive Interpolation");
if ((AdaptiveInterpolationType)adaptiveInterpolationValue.intValue == AdaptiveInterpolationType.Off)
_propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1);
_propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties");
if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything)
_propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1);
_propertyDrawer.SetIndentToStarting();
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight();
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4cb0116961f72144f83da97d61e3aa2d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.Threaded.cs
uploadId: 866910
@@ -0,0 +1,51 @@
#if UNITY_EDITOR && !THREADED_TICKSMOOTHERS
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEditor;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta.Editing
{
[CustomPropertyDrawer(typeof(MovementSettings))]
public class MovementSettingsDrawer : PropertyDrawer
{
private PropertyDrawerTool _propertyDrawer;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
_propertyDrawer = new(position);
// _propertyDrawer.DrawLabel(label, FontStyle.Bold);
EditorGUI.indentLevel++;
SerializedProperty enableTeleport = property.FindPropertyRelative("EnableTeleport");
SerializedProperty teleportThreshold = property.FindPropertyRelative("TeleportThreshold");
SerializedProperty adaptiveInterpolationValue = property.FindPropertyRelative("AdaptiveInterpolationValue");
SerializedProperty interpolationValue = property.FindPropertyRelative("InterpolationValue");
SerializedProperty smoothedProperties = property.FindPropertyRelative("SmoothedProperties");
SerializedProperty snapNonSmoothedProperties = property.FindPropertyRelative("SnapNonSmoothedProperties");
_propertyDrawer.DrawProperty(enableTeleport, "Enable Teleport");
if (enableTeleport.boolValue == true)
_propertyDrawer.DrawProperty(teleportThreshold, "Teleport Threshold", indent: 1);
_propertyDrawer.DrawProperty(adaptiveInterpolationValue, "Adaptive Interpolation");
if ((AdaptiveInterpolationType)adaptiveInterpolationValue.intValue == AdaptiveInterpolationType.Off)
_propertyDrawer.DrawProperty(interpolationValue, "Interpolation Value", indent: 1);
_propertyDrawer.DrawProperty(smoothedProperties, "Smoothed Properties");
if ((uint)smoothedProperties.intValue != (uint)TransformPropertiesFlag.Everything)
_propertyDrawer.DrawProperty(snapNonSmoothedProperties, "Snap Non-Smoothed Properties", indent: 1);
_propertyDrawer.SetIndentToStarting();
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => _propertyDrawer.GetPropertyHeight();
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: aa57faea7c2b2ce4b95e4865a64f3551
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/MovementSettingsDrawer.cs
uploadId: 866910
@@ -0,0 +1,54 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[CustomEditor(typeof(NetworkTickSmoother), true)]
[CanEditMultipleObjects]
public class NetworkTickSmootherEditor : Editor
{
private SerializedProperty _initializationSettings;
private SerializedProperty _controllerMovementSettings;
private SerializedProperty _favorPredictionNetworkTransform;
private SerializedProperty _spectatorMovementSettings;
private bool _showControllerSmoothingSettings;
private bool _showSpectatorSmoothingSettings;
protected virtual void OnEnable()
{
_initializationSettings = serializedObject.FindProperty(nameof(_initializationSettings));
_favorPredictionNetworkTransform = serializedObject.FindProperty(nameof(_favorPredictionNetworkTransform));
_controllerMovementSettings = serializedObject.FindProperty(nameof(_controllerMovementSettings));
_spectatorMovementSettings = serializedObject.FindProperty(nameof(_spectatorMovementSettings));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((NetworkTickSmoother)target), typeof(NetworkTickSmoother), false);
GUI.enabled = true;
EditorGUILayout.LabelField("Initialization Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_initializationSettings);
_showControllerSmoothingSettings = EditorGUILayout.Foldout(_showControllerSmoothingSettings, new GUIContent("Controller", "Smoothing applied when object controller. This would be the owner, or if there is no owner and are also server."));
if (_showControllerSmoothingSettings)
EditorGUILayout.PropertyField(_controllerMovementSettings);
_showSpectatorSmoothingSettings = EditorGUILayout.Foldout(_showSpectatorSmoothingSettings, new GUIContent("Spectator Smoothing", "Smoothing applied when object not the owner. This is when server and there is an owner, or when client and not the owner."));
if (_showSpectatorSmoothingSettings)
{
EditorGUILayout.PropertyField(_spectatorMovementSettings);
}
// EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f51697e093480ab429a4a2f47f545f92
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/NetworkTickSmootherEditor.cs
uploadId: 866910
@@ -0,0 +1,46 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[CustomEditor(typeof(OfflineTickSmoother), true)]
[CanEditMultipleObjects]
public class OfflineTickSmootherEditor : Editor
{
private SerializedProperty _automaticallyInitialize;
private SerializedProperty _initializationSettings;
private SerializedProperty _movementSettings;
private bool _showMovementSettings;
protected virtual void OnEnable()
{
_automaticallyInitialize = serializedObject.FindProperty(nameof(_automaticallyInitialize));
_initializationSettings = serializedObject.FindProperty(nameof(_initializationSettings));
_movementSettings = serializedObject.FindProperty(nameof(_movementSettings));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((OfflineTickSmoother)target), typeof(OfflineTickSmoother), false);
GUI.enabled = true;
// EditorGUILayout.LabelField("Initialization Settings", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_automaticallyInitialize);
EditorGUILayout.PropertyField(_initializationSettings);
_showMovementSettings = EditorGUILayout.Foldout(_showMovementSettings, "Smoothing");
if (_showMovementSettings)
EditorGUILayout.PropertyField(_movementSettings);
// EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 80812403004e23f44ae42bceba1b6733
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/Editor/OfflineTickSmootherEditor.cs
uploadId: 866910
@@ -0,0 +1,75 @@
using FishNet.Managing.Timing;
using FishNet.Object;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[System.Serializable]
public struct InitializationSettings
{
/// <summary>
/// While this script is typically placed on a nested graphical object, the targetTransform would be the object which moves every tick; the TargetTransform can be the same object this script resides but may not be a rigidbody if true;
/// </summary>
[Tooltip("While this script is typically placed on a nested graphical object, the targetTransform would be the object which moves every tick; the TargetTransform can be the same object this script resides but may not be a rigidbody if true;")]
[SerializeField]
public Transform TargetTransform;
/// <summary>
/// The transform which is smoothed.
/// </summary>
[Tooltip("The transform which is smoothed.")]
[System.NonSerialized]
internal Transform GraphicalTransform;
/// <summary>
/// True to detacth this object from its parent on client start.
/// </summary>
[Tooltip("True to detach this object from it's parent on client start.")]
public bool DetachOnStart;
/// <summary>
/// True to re-attach this object to it's parent on client stop.
/// </summary>
[Tooltip("True to re-attach this object to it's parent on client stop.")]
public bool AttachOnStop;
/// <summary>
/// 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.
/// </summary>
/// <remarks>This is not yet used.</remarks>
[Tooltip("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.")]
public bool MoveImmediately => false;
/// <summary>
/// NetworkBehaviour which initialized these settings. This value may be null if not initialized from a NetworkBehaviour.
/// </summary>
[System.NonSerialized]
internal NetworkBehaviour InitializingNetworkBehaviour;
/// <summary>
/// TimeManager initializing these settings.
/// </summary>
[System.NonSerialized]
internal TimeManager InitializingTimeManager;
/// <summary>
/// True to disable smoothing when the NetworkObject enables prediction, specifies a NetworkTransform to use, and that NetworkTransform is currently smoothing.
/// </summary>
[System.NonSerialized]
internal bool FavorPredictionNetworkTransform;
public void SetNetworkedRuntimeValues(NetworkBehaviour initializingNetworkBehaviour, Transform graphicalTransform, bool favorPredictionNetworkTransform)
{
InitializingNetworkBehaviour = initializingNetworkBehaviour;
InitializingTimeManager = initializingNetworkBehaviour.TimeManager;
GraphicalTransform = graphicalTransform;
FavorPredictionNetworkTransform = favorPredictionNetworkTransform;
}
/// <summary>
/// Sets values used at runtime. NetworkBehaviour is nullified when calling this method.
/// </summary>
public void SetOfflineRuntimeValues(TimeManager timeManager, Transform graphicalTransform)
{
GraphicalTransform = graphicalTransform;
InitializingTimeManager = timeManager;
InitializingNetworkBehaviour = null;
FavorPredictionNetworkTransform = false;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 0c6e8313aa3aa964585e4ec54b733627
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/InitializationSettings.cs
uploadId: 866910
@@ -0,0 +1,60 @@
#if FISHNET_THREADED_TICKSMOOTHERS
using FishNet.Object;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[System.Serializable]
public struct MovementSettings
{
/// <summary>
/// True to enable teleport threshold.
/// </summary>
[Tooltip("True to enable teleport threshold.")]
public bool EnableTeleport;
/// <summary>
/// How far the object must move between ticks to teleport rather than smooth.
/// </summary>
[Tooltip("How far the object must move between ticks to teleport rather than smooth.")]
[Range(0f, ushort.MaxValue)]
public float TeleportThreshold;
/// <summary>
/// Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases.
/// In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.
/// </summary>
[Tooltip("Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.")]
public AdaptiveInterpolationType AdaptiveInterpolationValue;
/// <summary>
/// Number of ticks to smooth over when not using adaptive interpolation.
/// </summary>
[Tooltip("Number of ticks to smooth over when not using adaptive interpolation.")]
public byte InterpolationValue;
/// <summary>
/// Properties to smooth. Any value not selected will become offset with every movement.
/// </summary>
[Tooltip("Properties to smooth. Any value not selected will become offset with every movement.")]
public TransformPropertiesFlag SmoothedProperties;
/// <summary>
/// True to apply smoothing in local space for position and rotation. False to use world space.
/// </summary>
[Tooltip("True to apply smoothing in local space for position and rotation. False to use world space.")]
public bool UseLocalSpace;
/// <summary>
/// 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.
/// </summary>
[Tooltip("True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.")]
public bool SnapNonSmoothedProperties;
public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersOnStructsAlready)
{
EnableTeleport = false;
TeleportThreshold = 0f;
AdaptiveInterpolationValue = AdaptiveInterpolationType.Off;
InterpolationValue = 2;
SmoothedProperties = TransformPropertiesFlag.Everything;
UseLocalSpace = false;
SnapNonSmoothedProperties = false;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7186c545fe7ae6148bec3847de098c65
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.Threaded.cs
uploadId: 866910
@@ -0,0 +1,54 @@
#if !FISHNET_THREADED_TICKSMOOTHERS
using FishNet.Object;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
[System.Serializable]
public struct MovementSettings
{
/// <summary>
/// True to enable teleport threshold.
/// </summary>
[Tooltip("True to enable teleport threshold.")]
public bool EnableTeleport;
/// <summary>
/// How far the object must move between ticks to teleport rather than smooth.
/// </summary>
[Tooltip("How far the object must move between ticks to teleport rather than smooth.")]
[Range(0f, ushort.MaxValue)]
public float TeleportThreshold;
/// <summary>
/// Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases.
/// In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.
/// </summary>
[Tooltip("Amount of adaptive interpolation to use. Adaptive interpolation increases interpolation with the local client's latency. Lower values of adaptive interpolation results in smaller interpolation increases. In most cases adaptive interpolation is only used with prediction where objects might be affected by other moving objects.")]
public AdaptiveInterpolationType AdaptiveInterpolationValue;
/// <summary>
/// Number of ticks to smooth over when not using adaptive interpolation.
/// </summary>
[Tooltip("Number of ticks to smooth over when not using adaptive interpolation.")]
public byte InterpolationValue;
/// <summary>
/// Properties to smooth. Any value not selected will become offset with every movement.
/// </summary>
[Tooltip("Properties to smooth. Any value not selected will become offset with every movement.")]
public TransformPropertiesFlag SmoothedProperties;
/// <summary>
/// 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.
/// </summary>
[Tooltip("True to keep non-smoothed properties at their original localspace every tick. A false value will keep the properties in the same world space as they were before each tick.")]
public bool SnapNonSmoothedProperties;
public MovementSettings(bool unityReallyNeedsToSupportParameterlessInitializersOnStructsAlready)
{
EnableTeleport = false;
TeleportThreshold = 0f;
AdaptiveInterpolationValue = AdaptiveInterpolationType.Off;
InterpolationValue = 2;
SmoothedProperties = TransformPropertiesFlag.Everything;
SnapNonSmoothedProperties = false;
}
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c01e6529f1dd28f4fa6d97ba1a480e22
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/MovementSettings.cs
uploadId: 866910
@@ -0,0 +1,91 @@
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// Smoothes this object between ticks.
/// </summary>
/// <remarks>This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction.</remarks>
public class NetworkTickSmoother : NetworkBehaviour
{
#region Public.
/// <summary>
/// Logic for owner smoothing.
/// </summary>
public TickSmootherController SmootherController { get; private set; }
#endregion
/// <summary>
/// Settings required to initialize the smoother.
/// </summary>
[Tooltip("Settings required to initialize the smoother.")]
[SerializeField]
private InitializationSettings _initializationSettings = new();
/// <summary>
/// How smoothing occurs when the controller of the object.
/// </summary>
[Tooltip("How smoothing occurs when the controller of the object.")]
[SerializeField]
private MovementSettings _controllerMovementSettings = new(true);
/// <summary>
/// True to disable smoothing when the NetworkObject enables prediction, specifies a NetworkTransform to use, and that NetworkTransform is currently smoothing.
/// </summary>
[Tooltip("True to disable smoothing when the NetworkObject enables prediction, specifies a NetworkTransform to use, and that NetworkTransform is currently smoothing.")]
[SerializeField]
private bool _favorPredictionNetworkTransform = true;
/// <summary>
/// How smoothing occurs when spectating the object.
/// </summary>
[Tooltip("How smoothing occurs when spectating the object.")]
[SerializeField]
private MovementSettings _spectatorMovementSettings = new(true);
private void OnDestroy()
{
if (SmootherController != null)
SmootherController.OnDestroy();
StoreControllers();
}
public override void OnStartClient()
{
RetrieveControllers();
_initializationSettings.SetNetworkedRuntimeValues(initializingNetworkBehaviour: this, graphicalTransform: transform, _favorPredictionNetworkTransform);
SmootherController.Initialize(_initializationSettings, _controllerMovementSettings, _spectatorMovementSettings);
SmootherController.StartSmoother();
}
public override void OnStopClient()
{
if (SmootherController == null)
return;
SmootherController.StopSmoother();
}
/// <summary>
/// Stores smoothers if they have value.
/// </summary>
private void StoreControllers()
{
if (SmootherController == null)
return;
ResettableObjectCaches<TickSmootherController>.Store(SmootherController);
SmootherController = null;
}
/// <summary>
/// Stores current smoothers and retrieves new ones.
/// </summary>
private void RetrieveControllers()
{
StoreControllers();
SmootherController = ResettableObjectCaches<TickSmootherController>.Retrieve();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 453481867b26f7c43b5bf38802a5f50e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/NetworkTickSmoother.cs
uploadId: 866910
@@ -0,0 +1,143 @@
using FishNet.Managing;
using FishNet.Managing.Timing;
using GameKit.Dependencies.Utilities;
using UnityEngine;
using UnityEngine.Serialization;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// Smoothes this object between ticks.
/// </summary>
/// <remarks>This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction.</remarks>
public class OfflineTickSmoother : MonoBehaviour
{
#region Public.
/// <summary>
/// Logic for owner smoothing.
/// </summary>
public TickSmootherController SmootherController { get; private set; }
/// <summary>
/// True if this component is initialized.
/// </summary>
/// <remarks>This API is for internal use and may change at any time.</remarks>
public bool IsInitialized { get; private set; }
#endregion
#region Serialized.
/// <summary>
/// True to automatically initialize in Awake using InstanceFinder. When false you will need to manually call Initialize.
/// </summary>
[Tooltip("True to automatically initialize in Awake using InstanceFinder. When false you will need to manually call Initialize.")]
[SerializeField]
private bool _automaticallyInitialize = true;
/// <summary>
/// Settings required to initialize the smoother.
/// </summary>
[Tooltip("Settings required to initialize the smoother.")]
[SerializeField]
private InitializationSettings _initializationSettings = new();
/// <summary>
/// How smoothing occurs when the controller of the object.
/// </summary>
[FormerlySerializedAs("_controllerMovementSettings")]
[Tooltip("How smoothing occurs when the controller of the object.")]
[SerializeField]
private MovementSettings _movementSettings = new(true);
#endregion
private void Awake()
{
RetrieveControllers();
AutomaticallyInitialize();
}
private void OnDestroy()
{
if (SmootherController != null)
{
SmootherController.StopSmoother();
SmootherController.OnDestroy();
}
StoreControllers();
IsInitialized = false;
}
/// <summary>
/// Automatically initializes if feature is enabled.
/// </summary>
private void AutomaticallyInitialize()
{
if (!_automaticallyInitialize)
return;
TimeManager tm = InstanceFinder.TimeManager;
if (tm == null)
{
NetworkManagerExtensions.LogWarning($"Automatic initialization failed on {gameObject.name}. You must manually call Initialize.");
return;
}
Initialize(tm);
}
/// <summary>
/// Initializes using a specified TimeManager.
/// </summary>
/// <param name = "timeManager"></param>
public void Initialize(TimeManager timeManager)
{
if (timeManager == null)
{
NetworkManagerExtensions.LogError($"TimeManager cannot be null when initializing.");
return;
}
SmootherController.SetTimeManager(timeManager);
_initializationSettings.SetOfflineRuntimeValues(timeManager, graphicalTransform: transform);
SmootherController.Initialize(_initializationSettings, _movementSettings, default);
SmootherController.StartSmoother();
IsInitialized = true;
}
/// <summary>
/// Sets a transform as the target to follow.
/// </summary>
/// <param name = "value">New value.</param>
public void SetTargetTransform(Transform value)
{
if (IsInitialized)
{
NetworkManagerExtensions.LogError($"Target can only be set before Initialize is called.");
return;
}
_initializationSettings.TargetTransform = value;
}
/// <summary>
/// Stores smoothers if they have value.
/// </summary>
private void StoreControllers()
{
if (SmootherController == null)
return;
ResettableObjectCaches<TickSmootherController>.Store(SmootherController);
SmootherController = null;
}
/// <summary>
/// Stores current smoothers and retrieves new ones.
/// </summary>
private void RetrieveControllers()
{
StoreControllers();
SmootherController = ResettableObjectCaches<TickSmootherController>.Retrieve();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2361c564b0f77b94dbc549ff9caa72a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/OfflineTickSmoother.cs
uploadId: 866910
@@ -0,0 +1,332 @@
#if FISHNET_THREADED_TICKSMOOTHERS
using FishNet.Managing.Timing;
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEngine;
using Unity.Profiling;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// Smoothes this object between ticks.
/// </summary>
/// <remarks>This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction.</remarks>
public class TickSmootherController : IResettable
{
#region Public.
// /// <summary>
// /// Logic for owner smoothing.
// /// </summary>
// public UniversalTickSmoother UniversalSmoother { get; private set; }
#endregion
#region Private.
#region Private Profiler Markers
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
/// <summary>
/// </summary>
private InitializationSettings _initializationSettings = new();
/// <summary>
/// </summary>
private MovementSettings _ownerMovementSettings = new();
/// <summary>
/// </summary>
private MovementSettings _spectatorMovementSettings = new();
/// <summary>
/// True if OnDestroy has been called.
/// </summary>
private bool _destroyed;
/// <summary>
/// Cached timeManager reference.
/// </summary>
private TimeManager _timeManager;
/// <summary>
/// NetworkBehaviour which initialized this object. Value may be null when initialized for an Offline smoother.
/// </summary>
private NetworkBehaviour _initializingNetworkBehaviour;
/// <summary>
/// TickSmoothingManager.
/// </summary>
private TickSmoothingManager _tickSmoothingManager;
/// <summary>
/// Transform which initialized this object.
/// </summary>
private Transform _graphicalTransform;
/// <summary>
/// True if initialized with a null NetworkBehaviour.
/// </summary>
private bool _initializedOffline;
/// <summary>
/// True if subscribed to events used for adaptiveInterpolation.
/// </summary>
private bool _subscribedToAdaptiveEvents;
/// <summary>
/// True if currently subscribed to events.
/// </summary>
private bool _subscribed;
/// <summary>
/// True if initialized.
/// </summary>
private bool _isInitialized;
#endregion
public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings)
{
_tickSmoothingManager =
initializationSettings.InitializingNetworkBehaviour?.NetworkManager.TickSmoothingManager ??
InstanceFinder.NetworkManager.TickSmoothingManager;
_initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour;
_graphicalTransform = initializationSettings.GraphicalTransform;
_initializationSettings = initializationSettings;
_ownerMovementSettings = ownerSettings;
_spectatorMovementSettings = spectatorSettings;
_initializedOffline = initializationSettings.InitializingNetworkBehaviour == null;
_isInitialized = true;
}
public void OnDestroy()
{
TickSmoothingManager tsm = _tickSmoothingManager;
if (tsm != null)
tsm.Unregister(this);
// ChangeSubscriptions(false);
// StoreSmoother();
_destroyed = true;
_isInitialized = false;
}
public void StartSmoother()
{
if (!_isInitialized)
return;
bool canStart = _initializedOffline ? StartOffline() : StartOnline();
if (!canStart)
return;
// RetrieveSmoothers();
//
// UniversalSmoother.Initialize(_initializationSettings, _ownerMovementSettings, _spectatorMovementSettings);
//
// UniversalSmoother.StartSmoother();
TickSmoothingManager tsm = _tickSmoothingManager;
if (tsm != null)
tsm.Register(this, _initializationSettings, _ownerMovementSettings, _spectatorMovementSettings);
bool StartOnline()
{
NetworkBehaviour nb = _initializingNetworkBehaviour;
SetTimeManager(nb.TimeManager);
return true;
}
bool StartOffline()
{
if (_timeManager == null)
return false;
return true;
}
}
public void StopSmoother()
{
TickSmoothingManager tsm = _tickSmoothingManager;
if (tsm != null)
tsm.Unregister(this);
if (!_initializedOffline)
StopOnline();
// if (UniversalSmoother != null)
// UniversalSmoother.StopSmoother();
void StopOnline()
{
SetTimeManager(tm: null);
}
// Intentionally left blank.
// void StopOffline() { }
}
// public void TimeManager_OnUpdate()
// {
// using (_pm_OnUpdate.Auto())
// {
// UniversalSmoother.OnUpdate(Time.deltaTime);
// }
// }
//
// public void TimeManager_OnPreTick()
// {
// using (_pm_OnPreTick.Auto())
// {
// UniversalSmoother.OnPreTick();
// }
// }
//
// /// <summary>
// /// Called after a tick completes.
// /// </summary>
// 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();
// }
//
// /// <summary>
// /// Stores smoothers if they have value.
// /// </summary>
// private void StoreSmoother()
// {
// if (UniversalSmoother == null)
// return;
//
// ResettableObjectCaches<UniversalTickSmoother>.Store(UniversalSmoother);
// UniversalSmoother = null;
// }
//
// /// <summary>
// /// Stores current smoothers and retrieves new ones.
// /// </summary>
// private void RetrieveSmoothers()
// {
// StoreSmoother();
// UniversalSmoother = ResettableObjectCaches<UniversalTickSmoother>.Retrieve();
// }
// /// <summary>
// /// Sets a target transform to follow.
// /// </summary>
// public void SetTargetTransform(Transform value)
// {
// Transform currentTargetTransform = _initializationSettings.TargetTransform;
//
// if (value == currentTargetTransform)
// return;
//
// bool clientStartCalled = (_initializedOffline && _timeManager != null) || (_initializingNetworkBehaviour != null && _initializingNetworkBehaviour.OnStartClientCalled);
//
// bool previousTargetTransformIsValid = (currentTargetTransform != null);
//
// // If target is different and old is not null then reset.
// if (previousTargetTransformIsValid && clientStartCalled)
// OnStopClient();
//
// _initializationSettings.TargetTransform = value;
// if (previousTargetTransformIsValid && clientStartCalled)
// OnStartClient();
// }
/// <summary>
/// Sets a new PredictionManager to use.
/// </summary>
public void SetTimeManager(TimeManager tm)
{
if (tm == _timeManager)
return;
// Unsub from current.
// ChangeSubscriptions(false);
//Sub to newest.
_timeManager = tm;
// ChangeSubscriptions(true);
}
// /// <summary>
// /// Changes the subscription to the TimeManager.
// /// </summary>
// private void ChangeSubscriptions(bool subscribe)
// {
// if (_destroyed)
// return;
// TimeManager tm = _timeManager;
// if (tm == null)
// return;
//
// if (subscribe == _subscribed)
// return;
// _subscribed = subscribe;
//
// bool adaptiveIsOff = _ownerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off;
//
// if (subscribe)
// {
// tm.OnUpdate += TimeManager_OnUpdate;
// tm.OnPreTick += TimeManager_OnPreTick;
// tm.OnPostTick += TimeManager_OnPostTick;
//
// if (!adaptiveIsOff)
// {
// tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated;
// PredictionManager pm = tm.NetworkManager.PredictionManager;
// pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay;
// _subscribedToAdaptiveEvents = true;
// }
// }
// else
// {
// tm.OnUpdate -= TimeManager_OnUpdate;
// tm.OnPreTick -= TimeManager_OnPreTick;
// tm.OnPostTick -= TimeManager_OnPostTick;
//
// if (_subscribedToAdaptiveEvents)
// {
// tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated;
// PredictionManager pm = tm.NetworkManager.PredictionManager;
// pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay;
// }
// }
// }
public void ResetState()
{
_initializationSettings = default;
_ownerMovementSettings = default;
_spectatorMovementSettings = default;
_destroyed = false;
_timeManager = null;
_initializingNetworkBehaviour = null;
_graphicalTransform = null;
_subscribed = false;
_subscribedToAdaptiveEvents = false;
_isInitialized = false;
}
public void InitializeState() { }
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 08a69e2de19910a4c848f7c34af11e41
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.Threaded.cs
uploadId: 866910
@@ -0,0 +1,309 @@
#if !FISHNET_THREADED_TICKSMOOTHERS
using FishNet.Managing.Predicting;
using FishNet.Managing.Timing;
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using Unity.Profiling;
using UnityEngine;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// Smoothes this object between ticks.
/// </summary>
/// <remarks>This can be configured to smooth over a set interval of time, or to smooth adaptively and make path corrections for prediction.</remarks>
public class TickSmootherController : IResettable
{
#region Public.
/// <summary>
/// Logic for owner smoothing.
/// </summary>
public UniversalTickSmoother UniversalSmoother { get; private set; }
#endregion
#region Private.
/// <summary>
/// </summary>
private InitializationSettings _initializationSettings = new();
/// <summary>
/// </summary>
private MovementSettings _controllerMovementSettings = new();
/// <summary>
/// </summary>
private MovementSettings _spectatorMovementSettings = new();
/// <summary>
/// True if OnDestroy has been called.
/// </summary>
private bool _destroyed;
/// <summary>
/// Cached timeManager reference.
/// </summary>
private TimeManager _timeManager;
/// <summary>
/// NetworkBehaviour which initialized this object. Value may be null when initialized for an Offline smoother.
/// </summary>
private NetworkBehaviour _initializingNetworkBehaviour;
/// <summary>
/// Transform which initialized this object.
/// </summary>
private Transform _graphicalTransform;
/// <summary>
/// True if initialized with a null NetworkBehaviour.
/// </summary>
private bool _initializedOffline;
/// <summary>
/// True if subscribed to events used for adaptiveInterpolation.
/// </summary>
private bool _subscribedToAdaptiveEvents;
/// <summary>
/// True if currently subscribed to events.
/// </summary>
private bool _subscribed;
/// <summary>
/// True if initialized.
/// </summary>
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 controllerSettings, MovementSettings spectatorSettings)
{
_initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour;
_graphicalTransform = initializationSettings.GraphicalTransform;
_initializationSettings = initializationSettings;
_controllerMovementSettings = controllerSettings;
_spectatorMovementSettings = spectatorSettings;
_initializedOffline = initializationSettings.InitializingNetworkBehaviour == null;
_isInitialized = true;
}
public void OnDestroy()
{
ChangeSubscriptions(false);
StoreSmoother();
_destroyed = true;
_isInitialized = false;
}
public void StartSmoother()
{
if (!_isInitialized)
return;
bool canStart = _initializedOffline ? StartOffline() : StartOnline();
if (!canStart)
return;
RetrieveSmoothers();
UniversalSmoother.Initialize(_initializationSettings, _controllerMovementSettings, _spectatorMovementSettings);
UniversalSmoother.StartSmoother();
bool StartOnline()
{
NetworkBehaviour nb = _initializingNetworkBehaviour;
SetTimeManager(nb.TimeManager);
return true;
}
bool StartOffline()
{
if (_timeManager == null)
return false;
return true;
}
}
public void StopSmoother()
{
ChangeSubscriptions(subscribe: false);
if (!_initializedOffline)
StopOnline();
if (UniversalSmoother != null)
UniversalSmoother.StopSmoother();
void StopOnline()
{
SetTimeManager(tm: null);
}
// Intentionally left blank.
// void StopOffline() { }
}
public void TimeManager_OnUpdate()
{
using (_pm_OnUpdate.Auto())
{
UniversalSmoother.OnUpdate(Time.deltaTime);
}
}
public void TimeManager_OnPreTick()
{
using (_pm_OnPreTick.Auto())
{
UniversalSmoother.OnPreTick();
}
}
/// <summary>
/// Called after a tick completes.
/// </summary>
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();
}
/// <summary>
/// Stores smoothers if they have value.
/// </summary>
private void StoreSmoother()
{
if (UniversalSmoother == null)
return;
ResettableObjectCaches<UniversalTickSmoother>.Store(UniversalSmoother);
UniversalSmoother = null;
}
/// <summary>
/// Stores current smoothers and retrieves new ones.
/// </summary>
private void RetrieveSmoothers()
{
StoreSmoother();
UniversalSmoother = ResettableObjectCaches<UniversalTickSmoother>.Retrieve();
}
// /// <summary>
// /// Sets a target transform to follow.
// /// </summary>
// public void SetTargetTransform(Transform value)
// {
// Transform currentTargetTransform = _initializationSettings.TargetTransform;
//
// if (value == currentTargetTransform)
// return;
//
// bool clientStartCalled = (_initializedOffline && _timeManager != null) || (_initializingNetworkBehaviour != null && _initializingNetworkBehaviour.OnStartClientCalled);
//
// bool previousTargetTransformIsValid = (currentTargetTransform != null);
//
// // If target is different and old is not null then reset.
// if (previousTargetTransformIsValid && clientStartCalled)
// OnStopClient();
//
// _initializationSettings.TargetTransform = value;
// if (previousTargetTransformIsValid && clientStartCalled)
// OnStartClient();
// }
/// <summary>
/// Sets a new PredictionManager to use.
/// </summary>
public void SetTimeManager(TimeManager tm)
{
if (tm == _timeManager)
return;
// Unsub from current.
ChangeSubscriptions(false);
//Sub to newest.
_timeManager = tm;
ChangeSubscriptions(true);
}
/// <summary>
/// Changes the subscription to the TimeManager.
/// </summary>
private void ChangeSubscriptions(bool subscribe)
{
if (_destroyed)
return;
TimeManager tm = _timeManager;
if (tm == null)
return;
if (subscribe == _subscribed)
return;
_subscribed = subscribe;
bool adaptiveIsOff = _controllerMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off && _spectatorMovementSettings.AdaptiveInterpolationValue == AdaptiveInterpolationType.Off;
if (subscribe)
{
tm.OnUpdate += TimeManager_OnUpdate;
tm.OnPreTick += TimeManager_OnPreTick;
tm.OnPostTick += TimeManager_OnPostTick;
if (!adaptiveIsOff)
{
tm.OnRoundTripTimeUpdated += TimeManager_OnRoundTripTimeUpdated;
PredictionManager pm = tm.NetworkManager.PredictionManager;
pm.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay;
_subscribedToAdaptiveEvents = true;
}
}
else
{
tm.OnUpdate -= TimeManager_OnUpdate;
tm.OnPreTick -= TimeManager_OnPreTick;
tm.OnPostTick -= TimeManager_OnPostTick;
if (_subscribedToAdaptiveEvents)
{
tm.OnRoundTripTimeUpdated -= TimeManager_OnRoundTripTimeUpdated;
PredictionManager pm = tm.NetworkManager.PredictionManager;
pm.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay;
}
}
}
public void ResetState()
{
_initializationSettings = default;
_controllerMovementSettings = default;
_spectatorMovementSettings = default;
_destroyed = false;
_timeManager = null;
_initializingNetworkBehaviour = null;
_graphicalTransform = null;
_subscribed = false;
_subscribedToAdaptiveEvents = false;
_isInitialized = false;
}
public void InitializeState() { }
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c189fd5371510434a9a879e928422705
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmootherController.cs
uploadId: 866910
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 59458bff2dda41a46a1b86c5512f9ff4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.Types.cs
uploadId: 866910
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2c0b6e6e7dbd4bb4f8352f0507f62bdd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/TickSmoothingManager.cs
uploadId: 866910
@@ -0,0 +1,983 @@
#if FISHNET_THREADED_TICKSMOOTHERS
using System;
using FishNet.Managing;
using FishNet.Managing.Timing;
using FishNet.Object;
using FishNet.Object.Prediction;
using FishNet.Utility.Extension;
using GameKit.Dependencies.Utilities;
using UnityEngine;
using UnityEngine.Profiling;
using Unity.Profiling;
using UnityEngine.Scripting;
namespace FishNet.Component.Transforming.Beta
{
/// <summary>
/// This class is under regular development and it's API may change at any time.
/// </summary>
public sealed class UniversalTickSmoother : IResettable
{
#region Public.
/// <summary>
/// True if currently initialized.
/// </summary>
public bool IsInitialized { get; private set; }
#endregion
#region Private.
#region Private Profiler Markers
private static readonly ProfilerMarker _pm_UpdateRealtimeInterpolation = new ProfilerMarker("UniversalTickSmoother.UpdateRealtimeInterpolation()");
private static readonly ProfilerMarker _pm_OnUpdate = new ProfilerMarker("UniversalTickSmoother.OnUpdate(float)");
private static readonly ProfilerMarker _pm_OnPreTick = new ProfilerMarker("UniversalTickSmoother.OnPreTick()");
private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new ProfilerMarker("UniversalTickSmoother.OnPostReplicateReplay(uint)");
private static readonly ProfilerMarker _pm_OnPostTick = new ProfilerMarker("UniversalTickSmoother.OnPostTick(uint)");
private static readonly ProfilerMarker _pm_ClearTPQ = new ProfilerMarker("UniversalTickSmoother.ClearTransformPropertiesQueue()");
private static readonly ProfilerMarker _pm_DiscardTPQ = new ProfilerMarker("UniversalTickSmoother.DiscardExcessiveTransformPropertiesQueue()");
private static readonly ProfilerMarker _pm_AddTP = new ProfilerMarker("UniversalTickSmoother.AddTransformProperties(uint, TransformProperties, TransformProperties)");
private static readonly ProfilerMarker _pm_ModifyTP = new ProfilerMarker("UniversalTickSmoother.ModifyTransformProperties(uint, uint)");
private static readonly ProfilerMarker _pm_SetMoveRates = new ProfilerMarker("UniversalTickSmoother.SetMoveRates(in TransformProperties)");
private static readonly ProfilerMarker _pm_MoveToTarget = new ProfilerMarker("UniversalTickSmoother.MoveToTarget(float)");
#endregion
/// <summary>
/// How quickly to move towards goal values.
/// </summary>
private MoveRates _moveRates = new();
/// <summary>
/// True if a pretick occurred since last postTick.
/// </summary>
private bool _preTicked;
/// <summary>
/// World values of the graphical after it's been aligned to initialized values in PreTick.
/// </summary>
private TransformProperties _trackerPreTickWorldValues;
/// <summary>
/// World values of the graphical after it's been aligned to initialized values in PreTick.
/// </summary>
private TransformProperties _graphicsPreTickWorldValues;
/// <summary>
/// Cached value of adaptive interpolation value.
/// </summary>
private AdaptiveInterpolationType _cachedAdaptiveInterpolationValue;
/// <summary>
/// Cached value of flat interpolation value.
/// </summary>
private byte _cachedInterpolationValue;
/// <summary>
/// Cached properties to smooth of the graphical.
/// </summary>
private TransformPropertiesFlag _cachedSmoothedProperties;
/// <summary>
/// Cached value of snapping non-smoothed properties.
/// </summary>
private bool _cachedSnapNonSmoothedProperties;
/// <summary>
/// Squared distance target must travel to cause a teleport.
/// </summary>
private float _cachedTeleportThreshold;
/// <summary>
/// True if to detach on network start.
/// </summary>
private bool _detachOnStart;
/// <summary>
/// True to re-attach on network stop.
/// </summary>
private bool _attachOnStop;
/// <summary>
/// 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.
/// </summary>
private bool _moveImmediately;
/// <summary>
/// Transform the graphics should follow.
/// </summary>
private Transform _targetTransform;
/// <summary>
/// Cached value of the object to smooth.
/// </summary>
private Transform _graphicalTransform;
/// <summary>
/// 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.
/// </summary>
private Transform _trackerTransform;
/// <summary>
/// TimeManager tickDelta.
/// </summary>
private float _tickDelta;
/// <summary>
/// NetworkBehaviour this is initialized for. Value may be null.
/// </summary>
private NetworkBehaviour _initializingNetworkBehaviour;
/// <summary>
/// TimeManager this is initialized for.
/// </summary>
private TimeManager _initializingTimeManager;
/// <summary>
/// Value to multiply movement by. This is used to reduce or increase the rate the movement buffer is consumed.
/// </summary>
private float _movementMultiplier = 1f;
/// <summary>
/// TransformProperties to move towards.
/// </summary>
private BasicQueue<TickSmoothingManager.TickTransformProperties> _transformProperties;
/// <summary>
/// True if to smooth using owner settings, false for spectator settings.
/// This is only used for performance gains.
/// </summary>
private bool _useOwnerSettings;
/// <summary>
/// Last tick this was teleported on.
/// </summary>
private uint _teleportedTick = TimeManager.UNSET_TICK;
/// <summary>
/// Current interpolation value, be it a flat value or adaptive.
/// </summary>
private byte _realtimeInterpolation;
/// <summary>
/// Settings to use for owners.
/// </summary>
private MovementSettings _controllerMovementSettings;
/// <summary>
/// Settings to use for spectators.
/// </summary>
private MovementSettings _spectatorMovementSettings;
/// <summary>
/// True if moving has started and has not been stopped.
/// </summary>
private bool _isMoving;
/// <summary>
/// NetworkTransform used when prediction type is set to other.
/// </summary>
private NetworkTransform _predictionNetworkTransform;
#endregion
#region Const.
/// <summary>
/// Maximum allowed entries to be queued over the interpolation amount.
/// </summary>
private const int MAXIMUM_QUEUED_OVER_INTERPOLATION = 3;
#endregion
[Preserve]
public UniversalTickSmoother() { }
~UniversalTickSmoother()
{
// This is a last resort for if something didnt deinitialize right.
ResetState();
}
[Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5
public void SetGraphicalInitializedOffsetValues(TransformProperties value) { }
[Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5
public TransformProperties GetGraphicalInitializedOffsetValues() => default;
/// <summary>
/// Tries to set local properties for the graphical tracker transform.
/// </summary>
/// <param name = "localValues">New values.</param>
/// <returns>Returns true if the tracker has been setup and values have been applied to teh tracker transform.</returns>
/// <remarks>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.</remarks>
public bool TrySetGraphicalTrackerLocalProperties(TransformProperties? localValues)
{
if (_trackerTransform == null || localValues == null)
{
_queuedTrackerProperties = localValues;
return false;
}
_trackerTransform.SetLocalProperties(localValues.Value);
return true;
}
[Obsolete("This method is no longer used. Use TrySetGraphicalTrackerLocalProperties(TransformProperties).")] // Remove V5
public void SetAdditionalGraphicalOffsetValues(TransformProperties localValues) { }
[Obsolete("This method is no longer used. Use GetGraphicalTrackerLocalProperties.")] // Remove V5
public TransformProperties GetAdditionalGraphicalOffsetValues() => default;
public TransformProperties GetGraphicalTrackerLocalProperties()
{
if (_trackerTransform != null)
return new(_trackerTransform.localPosition, _trackerTransform.localRotation, _trackerTransform.localScale);
if (_queuedTrackerProperties != null)
return _queuedTrackerProperties.Value;
// Fall through.
NetworkManager manager = _initializingNetworkBehaviour == null ? null : _initializingNetworkBehaviour.NetworkManager;
manager.LogWarning($"Graphical tracker properties cannot be returned because tracker is not setup yet, and no setup properties have been specified. Use TrySetGraphicalTrackerProperties to set setup properties or call this method after IsInitialized is true.");
return default;
}
/// <summary>
/// Properties for the tracker which are queued to be set when the tracker is setup.
/// </summary>
private TransformProperties? _queuedTrackerProperties;
/// <summary>
/// Updates the smoothedProperties value.
/// </summary>
/// <param name = "value">New value.</param>
/// <param name = "forController">True if updating owner smoothing settings, or updating settings on an offline smoother. False to update spectator settings</param>
public void SetSmoothedProperties(TransformPropertiesFlag value, bool forController)
{
_controllerMovementSettings.SmoothedProperties = value;
SetCaches(forController);
}
/// <summary>
/// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation.
/// </summary>
/// <param name = "value"></param>
public void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother) => SetInterpolationValue(value, forOwnerOrOfflineSmoother, unsetAdaptiveInterpolation: true);
/// <summary>
/// Updates the interpolationValue when not using adaptive interpolation. Calling this method will also disable adaptive interpolation.
/// </summary>
private void SetInterpolationValue(byte value, bool forOwnerOrOfflineSmoother, bool unsetAdaptiveInterpolation)
{
if (value < 1)
value = 1;
if (forOwnerOrOfflineSmoother)
_controllerMovementSettings.InterpolationValue = value;
else
_spectatorMovementSettings.InterpolationValue = value;
if (unsetAdaptiveInterpolation)
SetAdaptiveInterpolation(AdaptiveInterpolationType.Off, forOwnerOrOfflineSmoother);
}
/// <summary>
/// Updates the adaptiveInterpolation value.
/// </summary>
/// <param name = "adaptiveInterpolation">New value.</param>
public void SetAdaptiveInterpolation(AdaptiveInterpolationType value, bool forOwnerOrOfflineSmoother)
{
if (forOwnerOrOfflineSmoother)
_controllerMovementSettings.AdaptiveInterpolationValue = value;
else
_spectatorMovementSettings.AdaptiveInterpolationValue = value;
UpdateRealtimeInterpolation();
}
public void Initialize(InitializationSettings initializationSettings, MovementSettings ownerSettings, MovementSettings spectatorSettings)
{
ResetState();
Transform graphicalTransform = initializationSettings.GraphicalTransform;
Transform targetTransform = initializationSettings.TargetTransform;
if (!TransformsAreValid(graphicalTransform, targetTransform))
return;
_transformProperties = CollectionCaches<TickSmoothingManager.TickTransformProperties>.RetrieveBasicQueue();
_controllerMovementSettings = ownerSettings;
_spectatorMovementSettings = spectatorSettings;
/* Unset scale smoothing if not detaching. This is to prevent
* the scale from changing with the parent if nested, as that
* would result in the scale being modified twice, once on the parent
* and once on the graphical. Thanks deo_wh for find! */
if (!initializationSettings.DetachOnStart)
{
_controllerMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale;
_spectatorMovementSettings.SmoothedProperties &= ~TransformPropertiesFlag.Scale;
}
_initializingNetworkBehaviour = initializationSettings.InitializingNetworkBehaviour;
_initializingTimeManager = initializationSettings.InitializingTimeManager;
_targetTransform = targetTransform;
_graphicalTransform = graphicalTransform;
_tickDelta = (float)initializationSettings.InitializingTimeManager.TickDelta;
_detachOnStart = initializationSettings.DetachOnStart;
_attachOnStop = initializationSettings.AttachOnStop;
_moveImmediately = initializationSettings.MoveImmediately;
if (initializationSettings.FavorPredictionNetworkTransform && _initializingNetworkBehaviour != null)
{
NetworkObject networkObject = _initializingNetworkBehaviour.NetworkObject;
if (!networkObject.IsRigidbodyPredictionType)
_predictionNetworkTransform = networkObject.PredictionNetworkTransform;
else
_predictionNetworkTransform = null;
}
else
{
_predictionNetworkTransform = null;
}
SetCaches(GetUseOwnerSettings());
//Use set method as it has sanity checks.
SetInterpolationValue(_controllerMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: true, unsetAdaptiveInterpolation: false);
SetInterpolationValue(_spectatorMovementSettings.InterpolationValue, forOwnerOrOfflineSmoother: false, unsetAdaptiveInterpolation: false);
SetAdaptiveInterpolation(_controllerMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: true);
SetAdaptiveInterpolation(_spectatorMovementSettings.AdaptiveInterpolationValue, forOwnerOrOfflineSmoother: false);
SetupTrackerTransform();
/* This is called after setting up the tracker transform in the scenario
* the user set additional offsets before this was initialized. */
if (_queuedTrackerProperties != null)
TrySetGraphicalTrackerLocalProperties(_queuedTrackerProperties.Value);
void SetupTrackerTransform()
{
_trackerTransform = new GameObject($"{_graphicalTransform.name}_Tracker").transform;
if (_detachOnStart)
{
_trackerTransform.SetParent(_targetTransform);
}
else
{
Transform trackerParent = _graphicalTransform.IsChildOf(targetTransform) ? _graphicalTransform.parent : targetTransform;
_trackerTransform.SetParent(trackerParent);
}
_trackerTransform.SetWorldPositionRotationAndScale(_targetTransform.position, _targetTransform.rotation, graphicalTransform.localScale);
}
IsInitialized = true;
}
/// <summary>
/// Returns if configured transforms are valid.
/// </summary>
/// <returns></returns>
private bool TransformsAreValid(Transform graphicalTransform, Transform targetTransform)
{
if (graphicalTransform == null)
{
NetworkManagerExtensions.LogError($"Graphical transform cannot be null.");
return false;
}
if (targetTransform == null)
{
NetworkManagerExtensions.LogError($"Target transform on {graphicalTransform} cannot be null.");
return false;
}
if (targetTransform == graphicalTransform)
{
NetworkManagerExtensions.LogError($"Target transform cannot be the same as graphical transform on {graphicalTransform}.");
return false;
}
return true;
}
/// <summary>
/// Returns true if to use adaptive interpolation.
/// </summary>
/// <returns></returns>
private bool GetUseAdaptiveInterpolation()
{
if (_cachedAdaptiveInterpolationValue == AdaptiveInterpolationType.Off || _initializingTimeManager.NetworkManager.IsServerOnlyStarted)
return false;
return true;
}
/// <summary>
/// Gets if to use owner values.
/// </summary>
/// <remarks>OwnerSettings can be used to read determine this as both owner and spectator settings will have the name InitializingNetworkBehaviour.</remarks>
/// <returns></returns>
private bool GetUseOwnerSettings()
{
/* No networkBehaviour indicates an offline smoother.
* The offline smoothers use owner settings. */
if (_initializingNetworkBehaviour == null)
return true;
if (_initializingNetworkBehaviour.IsController)
return true;
return false;
// return _initializingNetworkBehaviour.IsOwner || !_initializingNetworkBehaviour.Owner.IsValid;
}
/// <summary>
/// Updates OwnerDuringPreTick value and caches if needed.
/// </summary>
private void SetCaches(bool useOwnerSettings)
{
MovementSettings movementSettings = useOwnerSettings ? _controllerMovementSettings : _spectatorMovementSettings;
_cachedSmoothedProperties = movementSettings.SmoothedProperties;
_cachedSnapNonSmoothedProperties = movementSettings.SnapNonSmoothedProperties;
_cachedAdaptiveInterpolationValue = movementSettings.AdaptiveInterpolationValue;
_cachedInterpolationValue = movementSettings.InterpolationValue;
_cachedTeleportThreshold = movementSettings.EnableTeleport ? movementSettings.TeleportThreshold * movementSettings.TeleportThreshold : MoveRates.UNSET_VALUE;
}
/// <summary>
/// Deinitializes this smoother resetting values.
/// </summary>
public void Deinitialize()
{
ResetState();
IsInitialized = false;
}
/// <summary>
/// Updates interpolation based on localClient latency when using adaptive interpolation, or uses set value when adaptive interpolation is off.
/// </summary>
public void UpdateRealtimeInterpolation()
{
using (_pm_UpdateRealtimeInterpolation.Auto())
{
/* If not networked, server is started, or if not
* using adaptive interpolation then use
* flat interpolation.*/
if (!GetUseAdaptiveInterpolation())
{
_realtimeInterpolation = _cachedInterpolationValue;
return;
}
/* If here then adaptive interpolation is being calculated. */
TimeManager tm = _initializingTimeManager;
//Calculate roughly what client state tick would be.
uint localTick = tm.LocalTick;
//This should never be the case; this is a precautionary against underflow.
if (localTick == TimeManager.UNSET_TICK)
return;
//Ensure at least 1 tick.
long rttTime = tm.RoundTripTime;
uint rttTicks = tm.TimeToTicks(rttTime) + 1;
uint clientStateTick = localTick - rttTicks;
float interpolation = localTick - clientStateTick;
//Minimum interpolation is that of adaptive interpolation level.
interpolation += (byte)_cachedAdaptiveInterpolationValue;
//Ensure interpolation is not more than a second.
if (interpolation > tm.TickRate)
interpolation = tm.TickRate;
else if (interpolation > byte.MaxValue)
interpolation = byte.MaxValue;
/* Only update realtime interpolation if it changed more than 1
* tick. This is to prevent excessive changing of interpolation value, which
* could result in noticeable speed ups/slow downs given movement multiplier
* may change when buffer is too full or short. */
if (_realtimeInterpolation == 0 || Math.Abs(_realtimeInterpolation - interpolation) > 1)
_realtimeInterpolation = (byte)Math.Ceiling(interpolation);
}
}
/// <summary>
/// This should be called when OnStartClient is invoked on the initializing NetworkBehaviour.
/// </summary>
/// <remarks>This does not need to be called if there is no initializing NetworkBehaviour.</remarks>
public void StartSmoother()
{
DetachOnStart();
}
/// <summary>
/// This should be called when OnStopClient is invoked on the initializing NetworkBehaviour.
/// </summary>
/// <remarks>This does not need to be called if there is no initializing NetworkBehaviour.</remarks>
internal void StopSmoother()
{
AttachOnStop();
}
/// <summary>
/// Called every frame.
/// </summary>
public void OnUpdate(float delta)
{
using (_pm_OnUpdate.Auto())
{
if (!CanSmooth())
return;
MoveToTarget(delta);
}
}
/// <summary>
/// Called when the TimeManager invokes OnPreTick.
/// </summary>
public void OnPreTick()
{
using (_pm_OnPreTick.Auto())
{
if (!CanSmooth())
return;
SetCaches(GetUseOwnerSettings());
_preTicked = true;
DiscardExcessiveTransformPropertiesQueue();
_graphicsPreTickWorldValues = _graphicalTransform.GetWorldProperties();
_trackerPreTickWorldValues = GetTrackerWorldProperties();
}
}
/// <summary>
/// Called when the TimeManager invokes OnPostReplay.
/// </summary>
/// <param name = "clientTick">Replay tick for the local client.</param>
/// <remarks>This is dependent on the initializing NetworkBehaviour being set.</remarks>
public void OnPostReplicateReplay(uint clientTick)
{
using (_pm_OnPostReplicateReplay.Auto())
{
if (!NetworkObjectIsReconciling())
return;
if (_transformProperties.Count == 0)
return;
if (clientTick <= _teleportedTick)
return;
uint firstTick = _transformProperties.Peek().Tick;
//Already in motion to first entry, or first entry passed tick.
if (clientTick <= firstTick)
return;
ModifyTransformProperties(clientTick, firstTick);
}
}
/// <summary>
/// Called when TimeManager invokes OnPostTick.
/// </summary>
/// <param name = "clientTick">Local tick of the client.</param>
public void OnPostTick(uint clientTick)
{
using (_pm_OnPostTick.Auto())
{
if (!CanSmooth())
return;
if (clientTick <= _teleportedTick)
return;
//If preticked then previous transform values are known.
if (_preTicked)
{
var trackerProps = GetTrackerWorldProperties();
//Only needs to be put to pretick position if not detached.
if (!_detachOnStart)
_graphicalTransform.SetWorldProperties(_graphicsPreTickWorldValues);
DiscardExcessiveTransformPropertiesQueue();
//SnapNonSmoothedProperties();
AddTransformProperties(clientTick, trackerProps);
}
//If did not pretick then the only thing we can do is snap to instantiated values.
else
{
//Only set to position if not to detach.
if (!_detachOnStart)
_graphicalTransform.SetWorldProperties(GetTrackerWorldProperties());
}
}
}
/// <summary>
/// Snaps non-smoothed properties to original positoin if setting is enabled.
/// </summary>
private void SnapNonSmoothedProperties()
{
//Feature is not enabled.
if (!_cachedSnapNonSmoothedProperties)
return;
TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties;
//Everything is smoothed.
if (smoothedProperties == TransformPropertiesFlag.Everything)
return;
TransformProperties goalValeus = GetTrackerWorldProperties();
if (!smoothedProperties.FastContains(TransformPropertiesFlag.Position))
_graphicalTransform.position = goalValeus.Position;
if (!smoothedProperties.FastContains(TransformPropertiesFlag.Rotation))
_graphicalTransform.rotation = goalValeus.Rotation;
if (!smoothedProperties.FastContains(TransformPropertiesFlag.Scale))
_graphicalTransform.localScale = goalValeus.Scale;
}
/// <summary>
/// Returns if the initialized NetworkBehaviour's NetworkObject is reconcilling.
/// </summary>
private bool NetworkObjectIsReconciling() => _initializingNetworkBehaviour == null || _initializingNetworkBehaviour.NetworkObject.IsObjectReconciling;
/// <summary>
/// Teleports the graphical to it's starting position and clears the internal movement queue.
/// </summary>
public void Teleport()
{
if (_initializingTimeManager == null)
return;
//If using adaptive interpolation then set the tick which was teleported.
if (_controllerMovementSettings.AdaptiveInterpolationValue != AdaptiveInterpolationType.Off)
{
TimeManager tm = _initializingTimeManager == null ? InstanceFinder.TimeManager : _initializingTimeManager;
if (tm != null)
_teleportedTick = tm.LocalTick;
}
ClearTransformPropertiesQueue();
_graphicalTransform.SetWorldProperties(_trackerTransform.GetWorldProperties());
}
/// <summary>
/// Clears the pending movement queue.
/// </summary>
private void ClearTransformPropertiesQueue()
{
using (_pm_ClearTPQ.Auto())
{
_transformProperties.Clear();
//Also unset move rates since there is no more queue.
_moveRates = new(MoveRates.UNSET_VALUE);
}
}
/// <summary>
/// Discards datas over interpolation limit from movement queue.
/// </summary>
private void DiscardExcessiveTransformPropertiesQueue()
{
using (_pm_DiscardTPQ.Auto())
{
int propertiesCount = _transformProperties.Count;
int dequeueCount = propertiesCount - (_realtimeInterpolation + MAXIMUM_QUEUED_OVER_INTERPOLATION);
//If there are entries to dequeue.
if (dequeueCount > 0)
{
TickSmoothingManager.TickTransformProperties ttp = default;
for (int i = 0; i < dequeueCount; i++)
{
ttp = _transformProperties.Dequeue();
}
var nextValues = ttp.Properties;
SetMoveRates(nextValues);
}
}
}
/// <summary>
/// Adds a new transform properties and sets move rates if needed.
/// </summary>
private void AddTransformProperties(uint tick, TransformProperties properties)
{
using (_pm_AddTP.Auto())
{
TickSmoothingManager.TickTransformProperties ttp = new(tick, properties);
_transformProperties.Enqueue(ttp);
//If first entry then set move rates.
if (_transformProperties.Count == 1)
{
TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties();
SetMoveRates(gfxWorldProperties);
}
}
}
/// <summary>
/// Modifies a transform property for a tick. This does not error check for empty collections.
/// </summary>
/// <param name = "firstTick">First tick in the queue. If 0 this will be looked up.</param>
private void ModifyTransformProperties(uint clientTick, uint firstTick)
{
using (_pm_ModifyTP.Auto())
{
int queueCount = _transformProperties.Count;
uint tick = clientTick;
/*Ticks will always be added incremental by 1 so it's safe to jump ahead the difference
* of tick and firstTick. */
int index = (int)(tick - firstTick);
//Replace with new data.
if (index < queueCount)
{
if (tick != _transformProperties[index].Tick)
{
//Should not be possible.
}
else
{
TransformProperties newProperties = GetTrackerWorldProperties();
/* Adjust transformProperties to ease into any corrections.
* The corrected value is used the more the index is to the end
* of the queue. */
/* We want to be fully eased in by the last entry of the queue. */
int lastPossibleIndex = queueCount - 1;
int adjustedQueueCount = lastPossibleIndex - 1;
if (adjustedQueueCount < 1)
adjustedQueueCount = 1;
float easePercent = (float)index / adjustedQueueCount;
//If easing.
if (easePercent < 1f)
{
if (easePercent < 1f)
easePercent = (float)Math.Pow(easePercent, adjustedQueueCount - index);
TransformProperties oldProperties = _transformProperties[index].Properties;
newProperties.Position = Vector3.Lerp(oldProperties.Position, newProperties.Position, easePercent);
newProperties.Rotation = Quaternion.Lerp(oldProperties.Rotation, newProperties.Rotation, easePercent);
newProperties.Scale = Vector3.Lerp(oldProperties.Scale, newProperties.Scale, easePercent);
}
_transformProperties[index] = new(tick, newProperties);
}
}
else
{
//This should never happen.
}
}
}
/// <summary>
/// Gets properties of the tracker.
/// </summary>
private TransformProperties GetTrackerWorldProperties()
{
/* Return lossyScale if graphical is not attached. Otherwise,
* graphical should retain the tracker localScale so it changes
* with root. */
Vector3 scale = _detachOnStart ? _trackerTransform.lossyScale : _trackerTransform.localScale;
return new(_trackerTransform.position, _trackerTransform.rotation, scale);
}
/// <summary>
/// Returns if prediction can be used on this rigidbody.
/// </summary>
/// <returns></returns>
private bool CanSmooth()
{
//No graphical object is set.
if (_graphicalTransform == null)
return false;
/* When this is the case the prediction networkTransform exist and is
* configured in a way to smooth the object, therefor this component should not be smoothing. */
if (_predictionNetworkTransform != null && _predictionNetworkTransform.DoSettingsAllowSmoothing())
return false;
return _initializingTimeManager.NetworkManager.IsClientStarted;
}
/// <summary>
/// Sets new rates based on next entries in transformProperties queue, against a supplied TransformProperties.
/// </summary>
private void SetMoveRates(in TransformProperties prevValues)
{
using (_pm_SetMoveRates.Auto())
{
if (_transformProperties.Count == 0)
{
_moveRates = new(MoveRates.UNSET_VALUE);
return;
}
TransformProperties nextValues = _transformProperties.Peek().Properties;
float duration = _tickDelta;
_moveRates = MoveRates.GetMoveRates(prevValues, nextValues, duration, _cachedTeleportThreshold);
_moveRates.TimeRemaining = duration;
SetMovementMultiplier();
}
}
private void SetMovementMultiplier()
{
if (_moveImmediately)
{
float percent = Mathf.InverseLerp(0, _realtimeInterpolation, _transformProperties.Count);
_movementMultiplier = percent;
_movementMultiplier = Mathf.Clamp(_movementMultiplier, 0.5f, 1.05f);
}
//For the time being, not moving immediately uses these multiplier calculations.
else
{
/* If there's more in queue than interpolation then begin to move faster based on overage.
* Move 5% faster for every overage. */
int overInterpolation = _transformProperties.Count - _realtimeInterpolation;
//If needs to be adjusted.
if (overInterpolation != 0)
{
_movementMultiplier += 0.015f * overInterpolation;
}
//If does not need to be adjusted.
else
{
//If interpolation is 1 then slow down just barely to accomodate for frame delta variance.
if (_realtimeInterpolation == 1)
_movementMultiplier = 1f;
}
_movementMultiplier = Mathf.Clamp(_movementMultiplier, 0.95f, 1.05f);
}
}
/// <summary>
/// Moves transform to target values.
/// </summary>
private void MoveToTarget(float delta)
{
using (_pm_MoveToTarget.Auto())
{
int tpCount = _transformProperties.Count;
//No data.
if (tpCount == 0)
return;
if (_moveImmediately)
{
_isMoving = true;
}
else
{
//Enough in buffer to move.
if (tpCount >= _realtimeInterpolation)
{
_isMoving = true;
}
else if (!_isMoving)
{
return;
}
/* If buffer is considerably under goal then halt
* movement. This will allow the buffer to grow. */
else if (tpCount - _realtimeInterpolation < -4)
{
_isMoving = false;
return;
}
}
TickSmoothingManager.TickTransformProperties ttp = _transformProperties.Peek();
TransformPropertiesFlag smoothedProperties = _cachedSmoothedProperties;
_moveRates.Move(_graphicalTransform, ttp.Properties, smoothedProperties, delta * _movementMultiplier, useWorldSpace: true);
float tRemaining = _moveRates.TimeRemaining;
//if TimeLeft is <= 0f then transform is at goal. Grab a new goal if possible.
if (tRemaining <= 0f)
{
//Dequeue current entry and if there's another call a move on it.
_transformProperties.Dequeue();
//If there are entries left then setup for the next.
if (_transformProperties.Count > 0)
{
SetMoveRates(ttp.Properties);
//If delta is negative then call move again with abs.
if (tRemaining < 0f)
MoveToTarget(Mathf.Abs(tRemaining));
}
//No remaining, set to snap.
else
{
ClearTransformPropertiesQueue();
}
}
}
}
private void DetachOnStart()
{
if (!_detachOnStart)
return;
TransformProperties gfxWorldProperties = _graphicalTransform.GetWorldProperties();
_graphicalTransform.SetParent(null);
_graphicalTransform.SetWorldProperties(gfxWorldProperties);
}
/// <summary>
/// Attachs to Target transform is possible.
/// </summary>
private void AttachOnStop()
{
//Never detached.
if (!_detachOnStart)
return;
//Graphical is null, nothing can be moved.
if (_graphicalTransform == null)
return;
if (ApplicationState.IsQuitting())
return;
/* If not to re-attach or if there's no target to reference
* then the graphical must be destroyed. */
bool destroy = !_attachOnStop || _targetTransform == null;
//If not to re-attach then destroy graphical if needed.
if (destroy)
{
UnityEngine.Object.Destroy(_graphicalTransform.gameObject);
return;
}
_graphicalTransform.SetParent(_targetTransform.parent);
_graphicalTransform.SetLocalProperties(_trackerTransform.GetLocalProperties());
}
public void ResetState()
{
if (!IsInitialized)
return;
AttachOnStop();
_initializingNetworkBehaviour = null;
_initializingTimeManager = null;
_graphicalTransform = null;
_targetTransform = null;
_teleportedTick = TimeManager.UNSET_TICK;
_movementMultiplier = 1f;
CollectionCaches<TickSmoothingManager.TickTransformProperties>.StoreAndDefault(ref _transformProperties);
_moveRates = default;
_preTicked = default;
_queuedTrackerProperties = null;
_trackerPreTickWorldValues = default;
_graphicsPreTickWorldValues = default;
_realtimeInterpolation = default;
_isMoving = default;
_predictionNetworkTransform = null;
if (_trackerTransform != null)
UnityEngine.Object.Destroy(_trackerTransform.gameObject);
}
public void InitializeState() { }
}
}
#endif
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5dff0ab5f613e944b9ca9c62e6783e7f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.Threaded.cs
uploadId: 866910
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3c9b47c92bed992428c9dbcb60f80916
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Generated/Component/TickSmoothing/UniversalTickSmoother.cs
uploadId: 866910